@canaryai/cli 0.1.13 → 0.2.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.
@@ -0,0 +1,371 @@
1
+ // src/local-browser/host.ts
2
+ import {
3
+ PlaywrightClient
4
+ } from "@chatsdet/browser-core";
5
+ var HEARTBEAT_INTERVAL_MS = 3e4;
6
+ var RECONNECT_DELAY_MS = 1e3;
7
+ var MAX_RECONNECT_DELAY_MS = 3e4;
8
+ var MAX_RECONNECT_ATTEMPTS = 10;
9
+ var LocalBrowserHost = class {
10
+ options;
11
+ ws = null;
12
+ client;
13
+ heartbeatTimer = null;
14
+ reconnectAttempts = 0;
15
+ isShuttingDown = false;
16
+ constructor(options) {
17
+ this.options = options;
18
+ const logger = {
19
+ debug: (msg, data) => this.log("debug", msg, data),
20
+ info: (msg, data) => this.log("info", msg, data),
21
+ warn: (msg, data) => this.log("warn", msg, data),
22
+ error: (msg, data) => this.log("error", msg, data)
23
+ };
24
+ this.client = new PlaywrightClient({ logger });
25
+ }
26
+ log(level, message, data) {
27
+ if (this.options.onLog) {
28
+ this.options.onLog(level, message, data);
29
+ } else {
30
+ const fn = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
31
+ fn(`[LocalBrowserHost] ${message}`, data ?? "");
32
+ }
33
+ }
34
+ // =========================================================================
35
+ // Lifecycle
36
+ // =========================================================================
37
+ async start() {
38
+ this.log("info", "Starting local browser host", {
39
+ browserMode: this.options.browserMode,
40
+ sessionId: this.options.sessionId
41
+ });
42
+ await this.connectWebSocket();
43
+ const { browserMode, cdpUrl, headless = true, storageStatePath } = this.options;
44
+ await this.client.connect({
45
+ browserMode: headless ? "headless" : "headed",
46
+ cdpUrl: browserMode === "cdp" ? cdpUrl : void 0,
47
+ storageStatePath
48
+ });
49
+ this.sendSessionEvent("browser_ready");
50
+ }
51
+ async stop() {
52
+ this.isShuttingDown = true;
53
+ this.log("info", "Stopping local browser host");
54
+ this.stopHeartbeat();
55
+ if (this.ws) {
56
+ try {
57
+ this.ws.close(1e3, "Shutdown");
58
+ } catch {
59
+ }
60
+ this.ws = null;
61
+ }
62
+ await this.client.disconnect();
63
+ this.log("info", "Local browser host stopped");
64
+ }
65
+ // =========================================================================
66
+ // WebSocket Connection
67
+ // =========================================================================
68
+ async connectWebSocket() {
69
+ return new Promise((resolve, reject) => {
70
+ const wsUrl = `${this.options.apiUrl.replace("http", "ws")}/local-browser/sessions/${this.options.sessionId}/connect?token=${this.options.wsToken}`;
71
+ this.log("info", "Connecting to cloud API", { url: wsUrl.replace(/token=.*/, "token=***") });
72
+ const ws = new WebSocket(wsUrl);
73
+ ws.onopen = () => {
74
+ this.log("info", "Connected to cloud API");
75
+ this.ws = ws;
76
+ this.reconnectAttempts = 0;
77
+ this.startHeartbeat();
78
+ resolve();
79
+ };
80
+ ws.onmessage = (event) => {
81
+ this.handleMessage(event.data);
82
+ };
83
+ ws.onerror = (event) => {
84
+ this.log("error", "WebSocket error", event);
85
+ };
86
+ ws.onclose = () => {
87
+ this.log("info", "WebSocket closed");
88
+ this.stopHeartbeat();
89
+ this.ws = null;
90
+ if (!this.isShuttingDown) {
91
+ this.scheduleReconnect();
92
+ }
93
+ };
94
+ setTimeout(() => {
95
+ if (!this.ws) {
96
+ reject(new Error("WebSocket connection timeout"));
97
+ }
98
+ }, 3e4);
99
+ });
100
+ }
101
+ scheduleReconnect() {
102
+ if (this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
103
+ this.log("error", "Max reconnection attempts reached, giving up");
104
+ this.stop();
105
+ return;
106
+ }
107
+ const delay = Math.min(
108
+ RECONNECT_DELAY_MS * Math.pow(2, this.reconnectAttempts),
109
+ MAX_RECONNECT_DELAY_MS
110
+ );
111
+ this.reconnectAttempts++;
112
+ this.log("info", `Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
113
+ setTimeout(async () => {
114
+ try {
115
+ await this.connectWebSocket();
116
+ this.sendSessionEvent("connected");
117
+ this.sendSessionEvent("browser_ready");
118
+ } catch (error) {
119
+ this.log("error", "Reconnection failed", error);
120
+ this.scheduleReconnect();
121
+ }
122
+ }, delay);
123
+ }
124
+ // =========================================================================
125
+ // Heartbeat
126
+ // =========================================================================
127
+ startHeartbeat() {
128
+ this.stopHeartbeat();
129
+ this.heartbeatTimer = setInterval(() => {
130
+ if (this.ws?.readyState === WebSocket.OPEN) {
131
+ const ping = {
132
+ type: "heartbeat",
133
+ id: crypto.randomUUID(),
134
+ timestamp: Date.now(),
135
+ direction: "pong"
136
+ };
137
+ this.ws.send(JSON.stringify(ping));
138
+ }
139
+ }, HEARTBEAT_INTERVAL_MS);
140
+ }
141
+ stopHeartbeat() {
142
+ if (this.heartbeatTimer) {
143
+ clearInterval(this.heartbeatTimer);
144
+ this.heartbeatTimer = null;
145
+ }
146
+ }
147
+ // =========================================================================
148
+ // Message Handling
149
+ // =========================================================================
150
+ handleMessage(data) {
151
+ try {
152
+ const message = JSON.parse(data);
153
+ if (message.type === "heartbeat" && message.direction === "ping") {
154
+ const pong = {
155
+ type: "heartbeat",
156
+ id: crypto.randomUUID(),
157
+ timestamp: Date.now(),
158
+ direction: "pong"
159
+ };
160
+ this.ws?.send(JSON.stringify(pong));
161
+ return;
162
+ }
163
+ if (message.type === "command") {
164
+ this.handleCommand(message);
165
+ return;
166
+ }
167
+ this.log("debug", "Received unknown message type", message);
168
+ } catch (error) {
169
+ this.log("error", "Failed to parse message", { error, data });
170
+ }
171
+ }
172
+ async handleCommand(command) {
173
+ const startTime = Date.now();
174
+ this.log("debug", `Executing command: ${command.method}`, { id: command.id });
175
+ try {
176
+ const result = await this.executeMethod(command.method, command.args);
177
+ const response = {
178
+ type: "response",
179
+ id: crypto.randomUUID(),
180
+ timestamp: Date.now(),
181
+ requestId: command.id,
182
+ success: true,
183
+ result,
184
+ contextId: command.contextId
185
+ };
186
+ this.ws?.send(JSON.stringify(response));
187
+ this.log("debug", `Command completed: ${command.method}`, {
188
+ id: command.id,
189
+ durationMs: Date.now() - startTime
190
+ });
191
+ } catch (error) {
192
+ const errorMessage = error instanceof Error ? error.message : String(error);
193
+ const response = {
194
+ type: "response",
195
+ id: crypto.randomUUID(),
196
+ timestamp: Date.now(),
197
+ requestId: command.id,
198
+ success: false,
199
+ error: errorMessage,
200
+ stack: error instanceof Error ? error.stack : void 0,
201
+ contextId: command.contextId
202
+ };
203
+ this.ws?.send(JSON.stringify(response));
204
+ this.log("error", `Command failed: ${command.method}`, {
205
+ id: command.id,
206
+ error: errorMessage
207
+ });
208
+ }
209
+ }
210
+ sendSessionEvent(event, error) {
211
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
212
+ const message = {
213
+ type: "session",
214
+ id: crypto.randomUUID(),
215
+ timestamp: Date.now(),
216
+ event,
217
+ browserMode: this.options.browserMode,
218
+ error
219
+ };
220
+ this.ws.send(JSON.stringify(message));
221
+ }
222
+ // =========================================================================
223
+ // Method Execution — Delegate to PlaywrightClient
224
+ // =========================================================================
225
+ /**
226
+ * Maps incoming WebSocket command method names to PlaywrightClient methods.
227
+ * The client implements IBrowserClient, so all standard browser operations
228
+ * are available and behave identically to the cloud environment.
229
+ */
230
+ async executeMethod(method, args) {
231
+ const client = this.client;
232
+ switch (method) {
233
+ // Lifecycle
234
+ case "connect":
235
+ return client.connect(args[0]);
236
+ case "disconnect":
237
+ return client.disconnect();
238
+ // Navigation
239
+ case "navigate":
240
+ return client.navigate(args[0], args[1]);
241
+ case "navigateBack":
242
+ return client.navigateBack(args[0]);
243
+ // Page Inspection
244
+ case "snapshot":
245
+ return client.snapshot(args[0]);
246
+ case "takeScreenshot":
247
+ return client.takeScreenshot(args[0]);
248
+ case "evaluate":
249
+ return client.evaluate(args[0], args[1]);
250
+ case "runCode":
251
+ return client.runCode(args[0], args[1]);
252
+ case "consoleMessages":
253
+ return client.consoleMessages(args[0]);
254
+ case "networkRequests":
255
+ return client.networkRequests(args[0]);
256
+ // Interaction
257
+ case "click":
258
+ return client.click(args[0], args[1], args[2]);
259
+ case "clickAtCoordinates":
260
+ return client.clickAtCoordinates(
261
+ args[0],
262
+ args[1],
263
+ args[2],
264
+ args[3]
265
+ );
266
+ case "moveToCoordinates":
267
+ return client.moveToCoordinates(
268
+ args[0],
269
+ args[1],
270
+ args[2],
271
+ args[3]
272
+ );
273
+ case "dragCoordinates":
274
+ return client.dragCoordinates(
275
+ args[0],
276
+ args[1],
277
+ args[2],
278
+ args[3],
279
+ args[4],
280
+ args[5]
281
+ );
282
+ case "hover":
283
+ return client.hover(args[0], args[1], args[2]);
284
+ case "drag":
285
+ return client.drag(
286
+ args[0],
287
+ args[1],
288
+ args[2],
289
+ args[3],
290
+ args[4]
291
+ );
292
+ case "type":
293
+ return client.type(
294
+ args[0],
295
+ args[1],
296
+ args[2],
297
+ args[3],
298
+ args[4]
299
+ );
300
+ case "pressKey":
301
+ return client.pressKey(args[0], args[1]);
302
+ case "fillForm":
303
+ return client.fillForm(args[0], args[1]);
304
+ case "selectOption":
305
+ return client.selectOption(
306
+ args[0],
307
+ args[1],
308
+ args[2],
309
+ args[3]
310
+ );
311
+ case "fileUpload":
312
+ return client.fileUpload(args[0], args[1]);
313
+ // Scroll
314
+ case "scroll":
315
+ return client.scroll(args[0], args[1], args[2], args[3], args[4]);
316
+ // Dialogs
317
+ case "handleDialog":
318
+ return client.handleDialog(args[0], args[1], args[2]);
319
+ // Waiting
320
+ case "waitFor":
321
+ return client.waitFor(args[0]);
322
+ // Browser Management
323
+ case "close":
324
+ return client.close(args[0]);
325
+ case "resize":
326
+ return client.resize(args[0], args[1], args[2]);
327
+ case "tabs":
328
+ return client.tabs(args[0], args[1], args[2]);
329
+ // Context Management
330
+ case "swapContext":
331
+ return client.swapContext?.(args[0]);
332
+ // Storage & Page Info
333
+ case "getStorageState":
334
+ return client.getStorageState(args[0]);
335
+ case "getCurrentUrl":
336
+ return client.getCurrentUrl(args[0]);
337
+ case "getTitle":
338
+ return client.getTitle(args[0]);
339
+ case "getLinks":
340
+ return client.getLinks(args[0]);
341
+ case "getElementBoundingBox":
342
+ return client.getElementBoundingBox(args[0], args[1]);
343
+ // Tracing
344
+ case "startTracing":
345
+ return client.startTracing?.(args[0]);
346
+ case "stopTracing":
347
+ return client.stopTracing?.(args[0]);
348
+ // Video
349
+ case "isVideoRecordingEnabled":
350
+ return client.isVideoRecordingEnabled?.() ?? false;
351
+ case "saveVideo":
352
+ return client.saveVideo?.() ?? null;
353
+ case "getVideoPath":
354
+ return null;
355
+ // Screencast
356
+ case "startScreencast":
357
+ return client.startScreencast?.(args[0], args[1]);
358
+ case "stopScreencast":
359
+ return client.stopScreencast?.();
360
+ case "isScreencastActive":
361
+ return client.isScreencastActive?.() ?? false;
362
+ default:
363
+ throw new Error(`Unknown method: ${method}`);
364
+ }
365
+ }
366
+ };
367
+
368
+ export {
369
+ LocalBrowserHost
370
+ };
371
+ //# sourceMappingURL=chunk-UEOXNF5X.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/local-browser/host.ts"],"sourcesContent":["/**\n * Local Browser Host\n *\n * Manages a local browser instance and handles commands from the cloud API\n * via WebSocket. Delegates all browser automation to PlaywrightClient from\n * @chatsdet/browser-core, ensuring the agent experience is identical\n * regardless of whether the browser is local or cloud.\n *\n * @module local-browser-host\n */\n\nimport {\n PlaywrightClient,\n type IBrowserClient,\n type BrowserLogger,\n} from \"@chatsdet/browser-core\";\nimport type {\n BrowserCommand,\n BrowserResponse,\n HeartbeatMessage,\n SessionMessage,\n LocalBrowserMode,\n} from \"./protocol\";\n\nconst HEARTBEAT_INTERVAL_MS = 30_000;\nconst RECONNECT_DELAY_MS = 1000;\nconst MAX_RECONNECT_DELAY_MS = 30_000;\nconst MAX_RECONNECT_ATTEMPTS = 10;\n\nexport interface LocalBrowserHostOptions {\n apiUrl: string;\n wsToken: string;\n sessionId: string;\n browserMode: LocalBrowserMode;\n cdpUrl?: string;\n headless?: boolean;\n storageStatePath?: string;\n onLog?: (level: \"info\" | \"warn\" | \"error\" | \"debug\", message: string, data?: unknown) => void;\n}\n\n/**\n * LocalBrowserHost manages the WebSocket connection to the cloud API and\n * delegates all browser operations to a shared PlaywrightClient instance.\n */\nexport class LocalBrowserHost {\n private options: LocalBrowserHostOptions;\n private ws: WebSocket | null = null;\n private client: PlaywrightClient;\n private heartbeatTimer: NodeJS.Timeout | null = null;\n private reconnectAttempts = 0;\n private isShuttingDown = false;\n\n constructor(options: LocalBrowserHostOptions) {\n this.options = options;\n const logger: BrowserLogger = {\n debug: (msg: string, data?: Record<string, unknown>) => this.log(\"debug\", msg, data),\n info: (msg: string, data?: Record<string, unknown>) => this.log(\"info\", msg, data),\n warn: (msg: string, data?: Record<string, unknown>) => this.log(\"warn\", msg, data),\n error: (msg: string, data?: Record<string, unknown>) => this.log(\"error\", msg, data),\n };\n this.client = new PlaywrightClient({ logger });\n }\n\n private log(level: \"info\" | \"warn\" | \"error\" | \"debug\", message: string, data?: unknown) {\n if (this.options.onLog) {\n this.options.onLog(level, message, data);\n } else {\n const fn = level === \"error\" ? console.error : level === \"warn\" ? console.warn : console.log;\n fn(`[LocalBrowserHost] ${message}`, data ?? \"\");\n }\n }\n\n // =========================================================================\n // Lifecycle\n // =========================================================================\n\n async start(): Promise<void> {\n this.log(\"info\", \"Starting local browser host\", {\n browserMode: this.options.browserMode,\n sessionId: this.options.sessionId,\n });\n\n // Connect to WebSocket first\n await this.connectWebSocket();\n\n // Launch browser via PlaywrightClient\n const { browserMode, cdpUrl, headless = true, storageStatePath } = this.options;\n await this.client.connect({\n browserMode: headless ? \"headless\" : \"headed\",\n cdpUrl: browserMode === \"cdp\" ? cdpUrl : undefined,\n storageStatePath,\n });\n\n // Notify cloud that browser is ready\n this.sendSessionEvent(\"browser_ready\");\n }\n\n async stop(): Promise<void> {\n this.isShuttingDown = true;\n this.log(\"info\", \"Stopping local browser host\");\n\n this.stopHeartbeat();\n\n if (this.ws) {\n try {\n this.ws.close(1000, \"Shutdown\");\n } catch {}\n this.ws = null;\n }\n\n await this.client.disconnect();\n this.log(\"info\", \"Local browser host stopped\");\n }\n\n // =========================================================================\n // WebSocket Connection\n // =========================================================================\n\n private async connectWebSocket(): Promise<void> {\n return new Promise((resolve, reject) => {\n const wsUrl = `${this.options.apiUrl.replace(\"http\", \"ws\")}/local-browser/sessions/${this.options.sessionId}/connect?token=${this.options.wsToken}`;\n\n this.log(\"info\", \"Connecting to cloud API\", { url: wsUrl.replace(/token=.*/, \"token=***\") });\n\n const ws = new WebSocket(wsUrl);\n\n ws.onopen = () => {\n this.log(\"info\", \"Connected to cloud API\");\n this.ws = ws;\n this.reconnectAttempts = 0;\n this.startHeartbeat();\n resolve();\n };\n\n ws.onmessage = (event) => {\n this.handleMessage(event.data as string);\n };\n\n ws.onerror = (event) => {\n this.log(\"error\", \"WebSocket error\", event);\n };\n\n ws.onclose = () => {\n this.log(\"info\", \"WebSocket closed\");\n this.stopHeartbeat();\n this.ws = null;\n\n if (!this.isShuttingDown) {\n this.scheduleReconnect();\n }\n };\n\n // Timeout after 30 seconds\n setTimeout(() => {\n if (!this.ws) {\n reject(new Error(\"WebSocket connection timeout\"));\n }\n }, 30_000);\n });\n }\n\n private scheduleReconnect(): void {\n if (this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {\n this.log(\"error\", \"Max reconnection attempts reached, giving up\");\n this.stop();\n return;\n }\n\n const delay = Math.min(\n RECONNECT_DELAY_MS * Math.pow(2, this.reconnectAttempts),\n MAX_RECONNECT_DELAY_MS\n );\n\n this.reconnectAttempts++;\n this.log(\"info\", `Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);\n\n setTimeout(async () => {\n try {\n await this.connectWebSocket();\n this.sendSessionEvent(\"connected\");\n this.sendSessionEvent(\"browser_ready\");\n } catch (error) {\n this.log(\"error\", \"Reconnection failed\", error);\n this.scheduleReconnect();\n }\n }, delay);\n }\n\n // =========================================================================\n // Heartbeat\n // =========================================================================\n\n private startHeartbeat(): void {\n this.stopHeartbeat();\n this.heartbeatTimer = setInterval(() => {\n if (this.ws?.readyState === WebSocket.OPEN) {\n const ping: HeartbeatMessage = {\n type: \"heartbeat\",\n id: crypto.randomUUID(),\n timestamp: Date.now(),\n direction: \"pong\",\n };\n this.ws.send(JSON.stringify(ping));\n }\n }, HEARTBEAT_INTERVAL_MS);\n }\n\n private stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n\n // =========================================================================\n // Message Handling\n // =========================================================================\n\n private handleMessage(data: string): void {\n try {\n const message = JSON.parse(data);\n\n if (message.type === \"heartbeat\" && message.direction === \"ping\") {\n const pong: HeartbeatMessage = {\n type: \"heartbeat\",\n id: crypto.randomUUID(),\n timestamp: Date.now(),\n direction: \"pong\",\n };\n this.ws?.send(JSON.stringify(pong));\n return;\n }\n\n if (message.type === \"command\") {\n this.handleCommand(message as BrowserCommand);\n return;\n }\n\n this.log(\"debug\", \"Received unknown message type\", message);\n } catch (error) {\n this.log(\"error\", \"Failed to parse message\", { error, data });\n }\n }\n\n private async handleCommand(command: BrowserCommand): Promise<void> {\n const startTime = Date.now();\n this.log(\"debug\", `Executing command: ${command.method}`, { id: command.id });\n\n try {\n const result = await this.executeMethod(command.method, command.args);\n const response: BrowserResponse = {\n type: \"response\",\n id: crypto.randomUUID(),\n timestamp: Date.now(),\n requestId: command.id,\n success: true,\n result,\n contextId: command.contextId,\n };\n this.ws?.send(JSON.stringify(response));\n\n this.log(\"debug\", `Command completed: ${command.method}`, {\n id: command.id,\n durationMs: Date.now() - startTime,\n });\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n const response: BrowserResponse = {\n type: \"response\",\n id: crypto.randomUUID(),\n timestamp: Date.now(),\n requestId: command.id,\n success: false,\n error: errorMessage,\n stack: error instanceof Error ? error.stack : undefined,\n contextId: command.contextId,\n };\n this.ws?.send(JSON.stringify(response));\n\n this.log(\"error\", `Command failed: ${command.method}`, {\n id: command.id,\n error: errorMessage,\n });\n }\n }\n\n private sendSessionEvent(\n event: \"connected\" | \"disconnected\" | \"browser_ready\" | \"browser_closed\" | \"error\",\n error?: string\n ): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;\n\n const message: SessionMessage = {\n type: \"session\",\n id: crypto.randomUUID(),\n timestamp: Date.now(),\n event,\n browserMode: this.options.browserMode,\n error,\n };\n this.ws.send(JSON.stringify(message));\n }\n\n // =========================================================================\n // Method Execution — Delegate to PlaywrightClient\n // =========================================================================\n\n /**\n * Maps incoming WebSocket command method names to PlaywrightClient methods.\n * The client implements IBrowserClient, so all standard browser operations\n * are available and behave identically to the cloud environment.\n */\n private async executeMethod(method: string, args: unknown[]): Promise<unknown> {\n const client = this.client as IBrowserClient;\n\n switch (method) {\n // Lifecycle\n case \"connect\":\n return client.connect(args[0] as any);\n case \"disconnect\":\n return client.disconnect();\n\n // Navigation\n case \"navigate\":\n return client.navigate(args[0] as string, args[1] as any);\n case \"navigateBack\":\n return client.navigateBack(args[0] as any);\n\n // Page Inspection\n case \"snapshot\":\n return client.snapshot(args[0] as any);\n case \"takeScreenshot\":\n return client.takeScreenshot(args[0] as any);\n case \"evaluate\":\n return client.evaluate(args[0] as string, args[1] as any);\n case \"runCode\":\n return client.runCode(args[0] as string, args[1] as any);\n case \"consoleMessages\":\n return client.consoleMessages(args[0] as any);\n case \"networkRequests\":\n return client.networkRequests(args[0] as any);\n\n // Interaction\n case \"click\":\n return client.click(args[0] as string, args[1] as string, args[2] as any);\n case \"clickAtCoordinates\":\n return client.clickAtCoordinates(\n args[0] as number, args[1] as number, args[2] as string, args[3] as any\n );\n case \"moveToCoordinates\":\n return client.moveToCoordinates(\n args[0] as number, args[1] as number, args[2] as string, args[3] as any\n );\n case \"dragCoordinates\":\n return client.dragCoordinates(\n args[0] as number, args[1] as number, args[2] as number,\n args[3] as number, args[4] as string, args[5] as any\n );\n case \"hover\":\n return client.hover(args[0] as string, args[1] as string, args[2] as any);\n case \"drag\":\n return client.drag(\n args[0] as string, args[1] as string,\n args[2] as string, args[3] as string, args[4] as any\n );\n case \"type\":\n return client.type(\n args[0] as string, args[1] as string, args[2] as string,\n args[3] as boolean, args[4] as any\n );\n case \"pressKey\":\n return client.pressKey(args[0] as string, args[1] as any);\n case \"fillForm\":\n return client.fillForm(args[0] as any[], args[1] as any);\n case \"selectOption\":\n return client.selectOption(\n args[0] as string, args[1] as string, args[2] as string, args[3] as any\n );\n case \"fileUpload\":\n return client.fileUpload(args[0] as string[], args[1] as any);\n\n // Scroll\n case \"scroll\":\n return client.scroll(args[0] as any, args[1] as any, args[2] as any, args[3] as any, args[4] as any);\n\n // Dialogs\n case \"handleDialog\":\n return client.handleDialog(args[0] as \"accept\" | \"dismiss\", args[1] as string, args[2] as any);\n\n // Waiting\n case \"waitFor\":\n return client.waitFor(args[0] as any);\n\n // Browser Management\n case \"close\":\n return client.close(args[0] as any);\n case \"resize\":\n return client.resize(args[0] as number, args[1] as number, args[2] as any);\n case \"tabs\":\n return client.tabs(args[0] as \"list\" | \"new\" | \"close\" | \"select\", args[1] as number, args[2] as any);\n\n // Context Management\n case \"swapContext\":\n return client.swapContext?.(args[0] as any);\n\n // Storage & Page Info\n case \"getStorageState\":\n return client.getStorageState(args[0] as any);\n case \"getCurrentUrl\":\n return client.getCurrentUrl(args[0] as any);\n case \"getTitle\":\n return client.getTitle(args[0] as any);\n case \"getLinks\":\n return client.getLinks(args[0] as any);\n case \"getElementBoundingBox\":\n return client.getElementBoundingBox(args[0] as string, args[1] as any);\n\n // Tracing\n case \"startTracing\":\n return client.startTracing?.(args[0] as any);\n case \"stopTracing\":\n return client.stopTracing?.(args[0] as any);\n\n // Video\n case \"isVideoRecordingEnabled\":\n return client.isVideoRecordingEnabled?.() ?? false;\n case \"saveVideo\":\n return client.saveVideo?.() ?? null;\n case \"getVideoPath\":\n return null;\n\n // Screencast\n case \"startScreencast\":\n return client.startScreencast?.(args[0] as any, args[1] as any);\n case \"stopScreencast\":\n return client.stopScreencast?.();\n case \"isScreencastActive\":\n return client.isScreencastActive?.() ?? false;\n\n default:\n throw new Error(`Unknown method: ${method}`);\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -20,6 +20,24 @@ function getArgValue(argv, key) {
20
20
  function hasFlag(argv, ...flags) {
21
21
  return flags.some((flag) => argv.includes(flag));
22
22
  }
23
+ function toLifecycleLabel(stage) {
24
+ switch (stage) {
25
+ case "deprecated":
26
+ return "deprecated";
27
+ case "ready_for_cleanup":
28
+ return "ready_for_cleanup";
29
+ default:
30
+ return "active";
31
+ }
32
+ }
33
+ function parseLifecycleStage(argv) {
34
+ const stage = getArgValue(argv, "--stage");
35
+ if (!stage || !["active", "deprecated", "ready_for_cleanup"].includes(stage)) {
36
+ console.error("Error: --stage is required and must be one of: active, deprecated, ready_for_cleanup");
37
+ process.exit(1);
38
+ }
39
+ return stage;
40
+ }
23
41
  async function resolveConfig(argv) {
24
42
  const storedApiUrl = await readStoredApiUrl();
25
43
  const env = getArgValue(argv, "--env");
@@ -54,8 +72,7 @@ async function apiRequest(apiUrl, token, method, path, body) {
54
72
  const json = await res.json();
55
73
  return json;
56
74
  }
57
- async function handleList(argv, apiUrl, token) {
58
- const jsonOutput = hasFlag(argv, "--json");
75
+ async function fetchFlags(apiUrl, token) {
59
76
  const res = await fetch(`${apiUrl}/superadmin/feature-flags`, {
60
77
  headers: { Authorization: `Bearer ${token}` }
61
78
  });
@@ -69,7 +86,11 @@ async function handleList(argv, apiUrl, token) {
69
86
  console.error(`Error: ${json.error}`);
70
87
  process.exit(1);
71
88
  }
72
- const flags = json.flags ?? [];
89
+ return json.flags ?? [];
90
+ }
91
+ async function handleList(argv, apiUrl, token) {
92
+ const jsonOutput = hasFlag(argv, "--json");
93
+ const flags = await fetchFlags(apiUrl, token);
73
94
  if (jsonOutput) {
74
95
  console.log(JSON.stringify(flags, null, 2));
75
96
  return;
@@ -79,9 +100,10 @@ async function handleList(argv, apiUrl, token) {
79
100
  return;
80
101
  }
81
102
  for (const flag of flags) {
82
- const gates = flag.gates ?? [];
83
- const gateStr = gates.length > 0 ? gates.map((g) => `${g.gateType}:${g.gateValue}`).join(", ") : "(no gates)";
84
- console.log(` ${flag.name} ${flag.description ?? ""} [${gateStr}]`);
103
+ const orgCount = flag.organizations?.length ?? 0;
104
+ const lifecycle = `lifecycle=${toLifecycleLabel(flag.lifecycleStage)}`;
105
+ const finalValue = flag.lifecycleStage === "active" ? "" : ` final=${flag.finalValue === true ? "true" : "false"}`;
106
+ console.log(` ${flag.name} ${flag.description ?? ""} (${lifecycle}${finalValue}) [orgs=${orgCount}]`);
85
107
  }
86
108
  }
87
109
  async function handleCreate(argv, apiUrl, token) {
@@ -171,23 +193,84 @@ async function handleDisable(argv, apiUrl, token) {
171
193
  }
172
194
  console.log(`Disabled ${name} for org ${orgId}`);
173
195
  }
196
+ async function handleLifecycle(argv, apiUrl, token) {
197
+ const name = argv[0];
198
+ if (!name || name.startsWith("--")) {
199
+ console.error("Error: Missing flag name.");
200
+ console.error(
201
+ "Usage: canary feature-flag lifecycle <name> --stage <active|deprecated|ready_for_cleanup> [--final-value true|false]"
202
+ );
203
+ process.exit(1);
204
+ }
205
+ const stage = parseLifecycleStage(argv);
206
+ const rawFinalValue = getArgValue(argv, "--final-value");
207
+ const clearFinalValue = hasFlag(argv, "--clear-final-value");
208
+ if (rawFinalValue !== void 0 && clearFinalValue) {
209
+ console.error("Error: use either --final-value or --clear-final-value, not both.");
210
+ process.exit(1);
211
+ }
212
+ let finalValue = void 0;
213
+ if (stage === "active") {
214
+ if (rawFinalValue !== void 0) {
215
+ console.error("Error: active stage does not accept --final-value.");
216
+ process.exit(1);
217
+ }
218
+ finalValue = void 0;
219
+ } else {
220
+ if (clearFinalValue) {
221
+ console.error("Error: --clear-final-value can only be used with --stage active.");
222
+ process.exit(1);
223
+ }
224
+ if (!rawFinalValue) {
225
+ console.error("Error: --final-value true|false is required for deprecated or ready_for_cleanup.");
226
+ process.exit(1);
227
+ }
228
+ if (rawFinalValue !== "true" && rawFinalValue !== "false") {
229
+ console.error("Error: --final-value must be true or false.");
230
+ process.exit(1);
231
+ }
232
+ finalValue = rawFinalValue === "true";
233
+ }
234
+ const result = await apiRequest(
235
+ apiUrl,
236
+ token,
237
+ "POST",
238
+ `/superadmin/feature-flags/${encodeURIComponent(name)}/lifecycle`,
239
+ {
240
+ stage,
241
+ finalValue
242
+ }
243
+ );
244
+ if (!result.ok || !result.flag) {
245
+ console.error(`Error: ${result.error ?? "Failed to update lifecycle"}`);
246
+ process.exit(1);
247
+ }
248
+ const final = result.flag.lifecycleStage === "active" ? "(none)" : result.flag.finalValue === true ? "true" : "false";
249
+ console.log(`Updated lifecycle for ${name}: stage=${toLifecycleLabel(result.flag.lifecycleStage)}, final=${final}`);
250
+ }
174
251
  function printFeatureFlagHelp() {
175
252
  console.log(
176
253
  [
177
254
  "Usage: canary feature-flag <sub-command> [options]",
178
255
  "",
179
256
  "Sub-commands:",
180
- " list List all feature flags",
181
- " create <name> [--description <text>] Create a new flag",
182
- " delete <name> Delete a flag and all its gates",
183
- " enable <name> --org <orgId> Enable a flag for an organization",
184
- " disable <name> --org <orgId> Disable a flag for an organization",
257
+ " list List all feature flags",
258
+ " create <name> [--description <text>] Create a new flag",
259
+ " delete <name> Delete a flag and all its gates",
260
+ " enable <name> --org <orgId> Enable a flag for an organization",
261
+ " disable <name> --org <orgId> Disable a flag for an organization",
262
+ " lifecycle <name> --stage <stage> [--final-value true|false]",
263
+ " Mark lifecycle + final value",
264
+ "",
265
+ "Stages: active, deprecated, ready_for_cleanup",
185
266
  "",
186
267
  "Options:",
187
- " --env <env> Target environment (prod, dev, local)",
188
- " --json Output as JSON (list only)",
189
- " --api-url <url> API URL override (takes precedence over --env)",
190
- " --token <key> API token override"
268
+ " --final-value <true|false> Final value for deprecated/ready_for_cleanup",
269
+ " --clear-final-value Clear final value (only valid with --stage active)",
270
+ " --env <env> Target environment (prod, dev, local)",
271
+ " --json Output as JSON (list only)",
272
+ " --api-url <url> API URL override (takes precedence over --env)",
273
+ " --token <key> API token override"
191
274
  ].join("\n")
192
275
  );
193
276
  }
@@ -214,6 +297,9 @@ async function runFeatureFlag(argv) {
214
297
  case "disable":
215
298
  await handleDisable(rest, apiUrl, token);
216
299
  break;
300
+ case "lifecycle":
301
+ await handleLifecycle(rest, apiUrl, token);
302
+ break;
217
303
  default:
218
304
  console.error(`Unknown sub-command: ${subCommand}`);
219
305
  printFeatureFlagHelp();
@@ -223,4 +309,4 @@ async function runFeatureFlag(argv) {
223
309
  export {
224
310
  runFeatureFlag
225
311
  };
226
- //# sourceMappingURL=feature-flag-MFTRYRYX.js.map
312
+ //# sourceMappingURL=feature-flag-ESPSOSKG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/feature-flag.ts"],"sourcesContent":["/**\n * CLI Feature Flag Management\n *\n * Allows superadmins to manage feature flags via the CLI.\n */\n\nimport process from \"node:process\";\nimport { readStoredToken, readStoredApiUrl } from \"./auth.js\";\n\nconst ENV_URLS: Record<string, string> = {\n prod: \"https://api.trycanary.ai\",\n production: \"https://api.trycanary.ai\",\n dev: \"https://api.dev.trycanary.ai\",\n local: \"http://localhost:3000\",\n};\n\ntype LifecycleStage = \"active\" | \"deprecated\" | \"ready_for_cleanup\";\n\ntype FeatureFlag = {\n id: string;\n name: string;\n description: string | null;\n lifecycleStage: LifecycleStage;\n finalValue: boolean | null;\n createdAt: string;\n organizations?: Array<{\n gateId: string;\n orgId: string;\n orgName: string;\n createdAt: string;\n }>;\n};\n\ntype FeatureFlagListResponse = {\n ok: boolean;\n flags?: FeatureFlag[];\n error?: string;\n};\n\ntype ApiResponse = {\n ok: boolean;\n error?: string;\n flag?: FeatureFlag;\n};\n\nfunction getArgValue(argv: string[], key: string): string | undefined {\n const index = argv.indexOf(key);\n if (index === -1 || index >= argv.length - 1) return undefined;\n return argv[index + 1];\n}\n\nfunction hasFlag(argv: string[], ...flags: string[]): boolean {\n return flags.some((flag) => argv.includes(flag));\n}\n\nfunction toLifecycleLabel(stage: LifecycleStage): string {\n switch (stage) {\n case \"deprecated\":\n return \"deprecated\";\n case \"ready_for_cleanup\":\n return \"ready_for_cleanup\";\n default:\n return \"active\";\n }\n}\n\nfunction parseLifecycleStage(argv: string[]): LifecycleStage {\n const stage = getArgValue(argv, \"--stage\");\n if (!stage || ![\"active\", \"deprecated\", \"ready_for_cleanup\"].includes(stage)) {\n console.error(\"Error: --stage is required and must be one of: active, deprecated, ready_for_cleanup\");\n process.exit(1);\n }\n return stage as LifecycleStage;\n}\n\nasync function resolveConfig(argv: string[]) {\n const storedApiUrl = await readStoredApiUrl();\n const env = getArgValue(argv, \"--env\");\n\n if (env && !ENV_URLS[env]) {\n console.error(`Unknown environment: ${env}`);\n console.error(\"Valid environments: prod, dev, local\");\n process.exit(1);\n }\n\n const apiUrl =\n getArgValue(argv, \"--api-url\") ??\n (env ? ENV_URLS[env] : undefined) ??\n process.env.CANARY_API_URL ??\n storedApiUrl ??\n \"https://api.trycanary.ai\";\n\n const token =\n getArgValue(argv, \"--token\") ?? process.env.CANARY_API_TOKEN ?? (await readStoredToken());\n\n if (!token) {\n console.error(\"Error: No API token found.\");\n console.error(\"Run: canary login\");\n process.exit(1);\n }\n\n return { apiUrl, token };\n}\n\nasync function apiRequest(\n apiUrl: string,\n token: string,\n method: string,\n path: string,\n body?: Record<string, unknown>\n): Promise<ApiResponse> {\n const res = await fetch(`${apiUrl}${path}`, {\n method,\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n ...(body ? { body: JSON.stringify(body) } : {}),\n });\n\n if (res.status === 401) {\n console.error(\"Error: Unauthorized. Your session may have expired.\");\n console.error(\"Run: canary login\");\n process.exit(1);\n }\n\n const json = (await res.json()) as ApiResponse;\n return json;\n}\n\nasync function fetchFlags(apiUrl: string, token: string): Promise<FeatureFlag[]> {\n const res = await fetch(`${apiUrl}/superadmin/feature-flags`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (res.status === 401) {\n console.error(\"Error: Unauthorized. Your session may have expired.\");\n console.error(\"Run: canary login\");\n process.exit(1);\n }\n\n const json = (await res.json()) as FeatureFlagListResponse;\n\n if (!json.ok) {\n console.error(`Error: ${json.error}`);\n process.exit(1);\n }\n\n return json.flags ?? [];\n}\n\nasync function handleList(argv: string[], apiUrl: string, token: string): Promise<void> {\n const jsonOutput = hasFlag(argv, \"--json\");\n const flags = await fetchFlags(apiUrl, token);\n\n if (jsonOutput) {\n console.log(JSON.stringify(flags, null, 2));\n return;\n }\n\n if (flags.length === 0) {\n console.log(\"No feature flags found.\");\n return;\n }\n\n for (const flag of flags) {\n const orgCount = flag.organizations?.length ?? 0;\n const lifecycle = `lifecycle=${toLifecycleLabel(flag.lifecycleStage)}`;\n const finalValue =\n flag.lifecycleStage === \"active\" ? \"\" : ` final=${flag.finalValue === true ? \"true\" : \"false\"}`;\n console.log(` ${flag.name} ${flag.description ?? \"\"} (${lifecycle}${finalValue}) [orgs=${orgCount}]`);\n }\n}\n\nasync function handleCreate(argv: string[], apiUrl: string, token: string): Promise<void> {\n const name = argv[0];\n if (!name || name.startsWith(\"--\")) {\n console.error(\"Error: Missing flag name.\");\n console.error(\"Usage: canary feature-flag create <name> [--description <text>]\");\n process.exit(1);\n }\n\n const description = getArgValue(argv, \"--description\") ?? null;\n const result = await apiRequest(apiUrl, token, \"POST\", \"/superadmin/feature-flags\", {\n name,\n description,\n });\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n\n console.log(`Created feature flag: ${name}`);\n}\n\nasync function handleDelete(argv: string[], apiUrl: string, token: string): Promise<void> {\n const name = argv[0];\n if (!name || name.startsWith(\"--\")) {\n console.error(\"Error: Missing flag name.\");\n console.error(\"Usage: canary feature-flag delete <name>\");\n process.exit(1);\n }\n\n const result = await apiRequest(\n apiUrl,\n token,\n \"DELETE\",\n `/superadmin/feature-flags/${encodeURIComponent(name)}`\n );\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n\n console.log(`Deleted feature flag: ${name}`);\n}\n\nasync function handleEnable(argv: string[], apiUrl: string, token: string): Promise<void> {\n const name = argv[0];\n const orgId = getArgValue(argv, \"--org\");\n\n if (!name || name.startsWith(\"--\")) {\n console.error(\"Error: Missing flag name.\");\n console.error(\"Usage: canary feature-flag enable <name> --org <orgId>\");\n process.exit(1);\n }\n\n if (!orgId) {\n console.error(\"Error: Missing --org <orgId>.\");\n console.error(\"Usage: canary feature-flag enable <name> --org <orgId>\");\n process.exit(1);\n }\n\n const result = await apiRequest(\n apiUrl,\n token,\n \"POST\",\n `/superadmin/feature-flags/${encodeURIComponent(name)}/organizations/${encodeURIComponent(orgId)}`\n );\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n\n console.log(`Enabled ${name} for org ${orgId}`);\n}\n\nasync function handleDisable(argv: string[], apiUrl: string, token: string): Promise<void> {\n const name = argv[0];\n const orgId = getArgValue(argv, \"--org\");\n\n if (!name || name.startsWith(\"--\")) {\n console.error(\"Error: Missing flag name.\");\n console.error(\"Usage: canary feature-flag disable <name> --org <orgId>\");\n process.exit(1);\n }\n\n if (!orgId) {\n console.error(\"Error: Missing --org <orgId>.\");\n console.error(\"Usage: canary feature-flag disable <name> --org <orgId>\");\n process.exit(1);\n }\n\n const result = await apiRequest(\n apiUrl,\n token,\n \"DELETE\",\n `/superadmin/feature-flags/${encodeURIComponent(name)}/organizations/${encodeURIComponent(orgId)}`\n );\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n\n console.log(`Disabled ${name} for org ${orgId}`);\n}\n\nasync function handleLifecycle(argv: string[], apiUrl: string, token: string): Promise<void> {\n const name = argv[0];\n\n if (!name || name.startsWith(\"--\")) {\n console.error(\"Error: Missing flag name.\");\n console.error(\n \"Usage: canary feature-flag lifecycle <name> --stage <active|deprecated|ready_for_cleanup> [--final-value true|false]\"\n );\n process.exit(1);\n }\n\n const stage = parseLifecycleStage(argv);\n const rawFinalValue = getArgValue(argv, \"--final-value\");\n const clearFinalValue = hasFlag(argv, \"--clear-final-value\");\n\n if (rawFinalValue !== undefined && clearFinalValue) {\n console.error(\"Error: use either --final-value or --clear-final-value, not both.\");\n process.exit(1);\n }\n\n let finalValue: boolean | undefined = undefined;\n\n if (stage === \"active\") {\n if (rawFinalValue !== undefined) {\n console.error(\"Error: active stage does not accept --final-value.\");\n process.exit(1);\n }\n finalValue = undefined;\n } else {\n if (clearFinalValue) {\n console.error(\"Error: --clear-final-value can only be used with --stage active.\");\n process.exit(1);\n }\n if (!rawFinalValue) {\n console.error(\"Error: --final-value true|false is required for deprecated or ready_for_cleanup.\");\n process.exit(1);\n }\n if (rawFinalValue !== \"true\" && rawFinalValue !== \"false\") {\n console.error(\"Error: --final-value must be true or false.\");\n process.exit(1);\n }\n finalValue = rawFinalValue === \"true\";\n }\n\n const result = await apiRequest(\n apiUrl,\n token,\n \"POST\",\n `/superadmin/feature-flags/${encodeURIComponent(name)}/lifecycle`,\n {\n stage,\n finalValue,\n }\n );\n\n if (!result.ok || !result.flag) {\n console.error(`Error: ${result.error ?? \"Failed to update lifecycle\"}`);\n process.exit(1);\n }\n\n const final =\n result.flag.lifecycleStage === \"active\"\n ? \"(none)\"\n : result.flag.finalValue === true\n ? \"true\"\n : \"false\";\n console.log(`Updated lifecycle for ${name}: stage=${toLifecycleLabel(result.flag.lifecycleStage)}, final=${final}`);\n}\n\nfunction printFeatureFlagHelp(): void {\n console.log(\n [\n \"Usage: canary feature-flag <sub-command> [options]\",\n \"\",\n \"Sub-commands:\",\n \" list List all feature flags\",\n \" create <name> [--description <text>] Create a new flag\",\n \" delete <name> Delete a flag and all its gates\",\n \" enable <name> --org <orgId> Enable a flag for an organization\",\n \" disable <name> --org <orgId> Disable a flag for an organization\",\n \" lifecycle <name> --stage <stage> [--final-value true|false]\",\n \" Mark lifecycle + final value\",\n \"\",\n \"Stages: active, deprecated, ready_for_cleanup\",\n \"\",\n \"Options:\",\n \" --final-value <true|false> Final value for deprecated/ready_for_cleanup\",\n \" --clear-final-value Clear final value (only valid with --stage active)\",\n \" --env <env> Target environment (prod, dev, local)\",\n \" --json Output as JSON (list only)\",\n \" --api-url <url> API URL override (takes precedence over --env)\",\n \" --token <key> API token override\",\n ].join(\"\\n\")\n );\n}\n\nexport async function runFeatureFlag(argv: string[]): Promise<void> {\n const [subCommand, ...rest] = argv;\n\n if (!subCommand || subCommand === \"help\" || hasFlag(argv, \"--help\", \"-h\")) {\n printFeatureFlagHelp();\n return;\n }\n\n const { apiUrl, token } = await resolveConfig(argv);\n\n switch (subCommand) {\n case \"list\":\n await handleList(rest, apiUrl, token);\n break;\n case \"create\":\n await handleCreate(rest, apiUrl, token);\n break;\n case \"delete\":\n await handleDelete(rest, apiUrl, token);\n break;\n case \"enable\":\n await handleEnable(rest, apiUrl, token);\n break;\n case \"disable\":\n await handleDisable(rest, apiUrl, token);\n break;\n case \"lifecycle\":\n await handleLifecycle(rest, apiUrl, token);\n break;\n default:\n console.error(`Unknown sub-command: ${subCommand}`);\n printFeatureFlagHelp();\n process.exit(1);\n }\n}\n"],"mappings":"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","names":[]}