@canaryai/cli 0.1.9 → 0.1.12

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 @@
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. This enables cloud agents to control a browser running\n * on the user's local machine.\n */\n\nimport { chromium, type Browser, type BrowserContext, type Page, type Dialog } from \"playwright\";\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\ninterface PageWithAISnapshot extends Page {\n _snapshotForAI(options: { mode: \"full\" | \"compact\" }): Promise<string>;\n}\n\n/**\n * LocalBrowserHost manages the browser and WebSocket connection to the cloud.\n */\ntype ContextSlot = {\n context: BrowserContext;\n page: Page;\n pendingDialogs: Dialog[];\n};\n\nexport class LocalBrowserHost {\n private options: LocalBrowserHostOptions;\n private ws: WebSocket | null = null;\n private browser: Browser | null = null;\n private contexts = new Map<string, ContextSlot>();\n private static DEFAULT_CONTEXT_ID = \"__default__\";\n private heartbeatTimer: NodeJS.Timeout | null = null;\n private reconnectAttempts = 0;\n private isShuttingDown = false;\n private lastSnapshotYaml = \"\";\n\n constructor(options: LocalBrowserHostOptions) {\n this.options = options;\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 // Then launch browser\n await this.launchBrowser();\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 for (const [id, slot] of this.contexts) {\n try {\n await slot.context.close();\n } catch {}\n }\n this.contexts.clear();\n\n if (this.browser) {\n try {\n await this.browser.close();\n } catch {}\n this.browser = null;\n }\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 if (this.page) {\n this.sendSessionEvent(\"browser_ready\");\n }\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 // Browser Management\n // =========================================================================\n\n private async launchBrowser(): Promise<void> {\n const { browserMode, cdpUrl, headless = true, storageStatePath } = this.options;\n\n if (browserMode === \"cdp\" && cdpUrl) {\n this.log(\"info\", \"Connecting to existing Chrome via CDP\", { cdpUrl });\n this.browser = await chromium.connectOverCDP(cdpUrl);\n const existingContexts = this.browser.contexts();\n const ctx = existingContexts[0] ?? (await this.browser.newContext());\n const pages = ctx.pages();\n const pg = pages[0] ?? (await ctx.newPage());\n const slot: ContextSlot = { context: ctx, page: pg, pendingDialogs: [] };\n pg.on(\"dialog\", (dialog) => slot.pendingDialogs.push(dialog));\n this.contexts.set(LocalBrowserHost.DEFAULT_CONTEXT_ID, slot);\n } else {\n this.log(\"info\", \"Launching new Playwright browser\", { headless });\n this.browser = await chromium.launch({\n headless,\n args: [\"--no-sandbox\"],\n });\n\n const contextOptions: { viewport: { width: number; height: number }; storageState?: string } =\n {\n viewport: { width: 1920, height: 1080 },\n };\n\n if (storageStatePath) {\n try {\n await Bun.file(storageStatePath).exists();\n contextOptions.storageState = storageStatePath;\n this.log(\"info\", \"Loading storage state\", { storageStatePath });\n } catch {\n this.log(\"debug\", \"Storage state file not found, starting fresh\");\n }\n }\n\n const ctx = await this.browser.newContext(contextOptions);\n const pg = await ctx.newPage();\n const slot: ContextSlot = { context: ctx, page: pg, pendingDialogs: [] };\n pg.on(\"dialog\", (dialog) => slot.pendingDialogs.push(dialog));\n this.contexts.set(LocalBrowserHost.DEFAULT_CONTEXT_ID, slot);\n }\n\n this.log(\"info\", \"Browser ready\");\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 // Respond to ping with pong\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 const contextId = command.contextId;\n this.log(\"debug\", `Executing command: ${command.method}`, { id: command.id, contextId });\n\n try {\n const result = await this.executeMethod(command.method, command.args, contextId);\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,\n };\n this.ws?.send(JSON.stringify(response));\n\n this.log(\"debug\", `Command completed: ${command.method}`, {\n id: command.id,\n contextId,\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,\n };\n this.ws?.send(JSON.stringify(response));\n\n this.log(\"error\", `Command failed: ${command.method}`, {\n id: command.id,\n contextId,\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\n // =========================================================================\n\n private getSlot(contextId?: string): ContextSlot {\n const id = contextId ?? LocalBrowserHost.DEFAULT_CONTEXT_ID;\n const slot = this.contexts.get(id);\n if (!slot) throw new Error(`Context not found: ${id}`);\n return slot;\n }\n\n private async createContextSlot(\n contextId: string,\n options?: { storageState?: unknown }\n ): Promise<void> {\n if (!this.browser) throw new Error(\"No browser available\");\n if (this.contexts.has(contextId)) throw new Error(`Context already exists: ${contextId}`);\n\n const contextOptions: { viewport: { width: number; height: number }; storageState?: string } = {\n viewport: { width: 1920, height: 1080 },\n };\n\n if (options?.storageState) {\n const tmpPath = `/tmp/storage-state-${crypto.randomUUID()}.json`;\n await Bun.write(tmpPath, JSON.stringify(options.storageState));\n contextOptions.storageState = tmpPath;\n this.log(\"info\", \"Loaded inline storage state for new context\", { contextId });\n }\n\n const ctx = await this.browser.newContext(contextOptions);\n const pg = await ctx.newPage();\n const slot: ContextSlot = { context: ctx, page: pg, pendingDialogs: [] };\n pg.on(\"dialog\", (dialog) => slot.pendingDialogs.push(dialog));\n this.contexts.set(contextId, slot);\n this.log(\"info\", \"Created new context slot\", { contextId });\n }\n\n private async destroyContextSlot(contextId: string): Promise<void> {\n if (contextId === LocalBrowserHost.DEFAULT_CONTEXT_ID) {\n throw new Error(\"Cannot destroy the default context\");\n }\n const slot = this.contexts.get(contextId);\n if (!slot) throw new Error(`Context not found: ${contextId}`);\n\n try {\n await slot.context.close();\n } catch {}\n this.contexts.delete(contextId);\n this.log(\"info\", \"Destroyed context slot\", { contextId });\n }\n\n private async executeMethod(method: string, args: unknown[], contextId?: string): Promise<unknown> {\n // Context lifecycle commands\n switch (method) {\n case \"createContext\":\n return this.createContextSlot(args[0] as string, args[1] as any);\n case \"destroyContext\":\n return this.destroyContextSlot(args[0] as string);\n }\n\n // Route to appropriate handler\n switch (method) {\n // Lifecycle\n case \"connect\":\n return this.connect(args[0] as any);\n case \"disconnect\":\n return this.disconnect();\n\n // Navigation\n case \"navigate\":\n return this.navigate(args[0] as string, args[1] as any, contextId);\n case \"navigateBack\":\n return this.navigateBack(args[0] as any, contextId);\n\n // Page Inspection\n case \"snapshot\":\n return this.snapshot(args[0] as any, contextId);\n case \"takeScreenshot\":\n return this.takeScreenshot(args[0] as any, contextId);\n case \"evaluate\":\n return this.evaluate(args[0] as string, args[1] as any, contextId);\n case \"runCode\":\n return this.runCode(args[0] as string, args[1] as any, contextId);\n case \"consoleMessages\":\n return this.consoleMessages(args[0] as any);\n case \"networkRequests\":\n return this.networkRequests(args[0] as any);\n\n // Interaction\n case \"click\":\n return this.click(args[0] as string, args[1] as string, args[2] as any, contextId);\n case \"clickAtCoordinates\":\n return this.clickAtCoordinates(\n args[0] as number,\n args[1] as number,\n args[2] as string,\n args[3] as any,\n contextId\n );\n case \"moveToCoordinates\":\n return this.moveToCoordinates(\n args[0] as number,\n args[1] as number,\n args[2] as string,\n args[3] as any,\n contextId\n );\n case \"dragCoordinates\":\n return this.dragCoordinates(\n args[0] as number,\n args[1] as number,\n args[2] as number,\n args[3] as number,\n args[4] as string,\n args[5] as any,\n contextId\n );\n case \"hover\":\n return this.hover(args[0] as string, args[1] as string, args[2] as any, contextId);\n case \"drag\":\n return this.drag(\n args[0] as string,\n args[1] as string,\n args[2] as string,\n args[3] as string,\n args[4] as any,\n contextId\n );\n case \"type\":\n return this.type(\n args[0] as string,\n args[1] as string,\n args[2] as string,\n args[3] as boolean,\n args[4] as any,\n contextId\n );\n case \"pressKey\":\n return this.pressKey(args[0] as string, args[1] as any, contextId);\n case \"fillForm\":\n return this.fillForm(args[0] as any[], args[1] as any, contextId);\n case \"selectOption\":\n return this.selectOption(\n args[0] as string,\n args[1] as string,\n args[2] as string,\n args[3] as any,\n contextId\n );\n case \"fileUpload\":\n return this.fileUpload(args[0] as string[], args[1] as any, contextId);\n\n // Dialogs\n case \"handleDialog\":\n return this.handleDialog(args[0] as \"accept\" | \"dismiss\", args[1] as string, args[2] as any, contextId);\n\n // Waiting\n case \"waitFor\":\n return this.waitFor(args[0] as any, contextId);\n\n // Browser Management\n case \"close\":\n return this.closePage(args[0] as any, contextId);\n case \"resize\":\n return this.resize(args[0] as number, args[1] as number, args[2] as any, contextId);\n case \"tabs\":\n return this.tabs(args[0] as any, args[1] as number, args[2] as any, contextId);\n\n // Context Management\n case \"swapContext\":\n return this.handleSwapContext(args[0] as any, contextId);\n\n // Storage\n case \"getStorageState\":\n return this.getStorageState(args[0] as any, contextId);\n case \"getCurrentUrl\":\n return this.getCurrentUrl(args[0] as any, contextId);\n case \"getTitle\":\n return this.getTitle(args[0] as any, contextId);\n case \"getLinks\":\n return this.getLinks(args[0] as any, contextId);\n case \"getElementBoundingBox\":\n return this.getElementBoundingBox(args[0] as string, args[1] as any, contextId);\n\n // Tracing\n case \"startTracing\":\n return this.startTracing(args[0] as any, contextId);\n case \"stopTracing\":\n return this.stopTracing(args[0] as any, contextId);\n\n // Video\n case \"isVideoRecordingEnabled\":\n return false; // Video not supported in CLI host currently\n case \"saveVideo\":\n return null;\n case \"getVideoPath\":\n return null;\n\n default:\n throw new Error(`Unknown method: ${method}`);\n }\n }\n\n // =========================================================================\n // IBrowserClient Method Implementations\n // =========================================================================\n\n private async handleSwapContext(options: {\n storageState?: unknown;\n storageStatePath?: string;\n recordVideo?: boolean;\n }, contextId?: string): Promise<void> {\n if (!this.browser) throw new Error(\"No browser available\");\n\n const slotId = contextId ?? LocalBrowserHost.DEFAULT_CONTEXT_ID;\n const existing = this.contexts.get(slotId);\n\n // Close existing context (and its pages)\n if (existing) {\n await existing.context.close();\n this.contexts.delete(slotId);\n }\n\n // Build context options\n const contextOptions: { viewport: { width: number; height: number }; storageState?: string } = {\n viewport: { width: 1920, height: 1080 },\n };\n\n if (options.storageState) {\n const tmpPath = `/tmp/storage-state-${crypto.randomUUID()}.json`;\n await Bun.write(tmpPath, JSON.stringify(options.storageState));\n contextOptions.storageState = tmpPath;\n this.log(\"info\", \"Loaded inline storage state for context swap\");\n } else if (options.storageStatePath) {\n try {\n const exists = await Bun.file(options.storageStatePath).exists();\n if (exists) {\n contextOptions.storageState = options.storageStatePath;\n this.log(\"info\", \"Loading storage state from file for context swap\", {\n storageStatePath: options.storageStatePath,\n });\n }\n } catch {\n this.log(\"debug\", \"Storage state file not found, starting fresh context\");\n }\n }\n\n const ctx = await this.browser.newContext(contextOptions);\n const pg = await ctx.newPage();\n const slot: ContextSlot = { context: ctx, page: pg, pendingDialogs: [] };\n pg.on(\"dialog\", (dialog) => slot.pendingDialogs.push(dialog));\n this.contexts.set(slotId, slot);\n\n this.log(\"info\", \"Browser context swapped successfully\", { contextId: slotId });\n }\n\n private getPage(contextId?: string): Page {\n return this.getSlot(contextId).page;\n }\n\n private resolveRef(ref: string, contextId?: string) {\n return this.getPage(contextId).locator(`aria-ref=${ref}`);\n }\n\n private async connect(_options: any): Promise<void> {\n // Browser already launched in start()\n return;\n }\n\n private async disconnect(): Promise<void> {\n await this.stop();\n }\n\n private async navigate(url: string, _opts?: any, contextId?: string): Promise<string> {\n const page = this.getPage(contextId);\n await page.goto(url, { waitUntil: \"domcontentloaded\" });\n await page.waitForLoadState(\"load\", { timeout: 5000 }).catch(() => {});\n return this.captureSnapshot(contextId);\n }\n\n private async navigateBack(_opts?: any, contextId?: string): Promise<string> {\n await this.getPage(contextId).goBack();\n return this.captureSnapshot(contextId);\n }\n\n private async snapshot(_opts?: any, contextId?: string): Promise<string> {\n return this.captureSnapshot(contextId);\n }\n\n private async captureSnapshot(contextId?: string): Promise<string> {\n const page = this.getPage(contextId) as PageWithAISnapshot;\n this.lastSnapshotYaml = await page._snapshotForAI({ mode: \"full\" });\n return this.lastSnapshotYaml;\n }\n\n private async takeScreenshot(opts?: any, contextId?: string): Promise<string | null> {\n const page = this.getPage(contextId);\n const buffer = await page.screenshot({\n type: opts?.type ?? \"jpeg\",\n fullPage: opts?.fullPage ?? false,\n });\n const mime = opts?.type === \"png\" ? \"image/png\" : \"image/jpeg\";\n return `data:${mime};base64,${buffer.toString(\"base64\")}`;\n }\n\n private async evaluate<T>(fn: string, _opts?: any, contextId?: string): Promise<T> {\n const page = this.getPage(contextId);\n return page.evaluate(new Function(`return (${fn})()`) as () => T);\n }\n\n private async runCode(code: string, _opts?: any, contextId?: string): Promise<unknown> {\n const page = this.getPage(contextId);\n const fn = new Function(\"page\", `return (async () => { ${code} })()`) as (\n p: Page\n ) => Promise<unknown>;\n return fn(page);\n }\n\n private async consoleMessages(_opts?: any): Promise<string> {\n return \"Console message capture not implemented in CLI host\";\n }\n\n private async networkRequests(_opts?: any): Promise<string> {\n return \"Network request capture not implemented in CLI host\";\n }\n\n private async click(ref: string, _elementDesc?: string, opts?: any, contextId?: string): Promise<void> {\n const locator = this.resolveRef(ref, contextId);\n await locator.scrollIntoViewIfNeeded({ timeout: 5000 }).catch(() => {});\n\n const box = await locator.boundingBox();\n if (box) {\n const centerX = box.x + box.width / 2;\n const centerY = box.y + box.height / 2;\n const page = this.getPage(contextId);\n\n if (opts?.modifiers?.length) {\n for (const mod of opts.modifiers) {\n await page.keyboard.down(mod);\n }\n }\n\n if (opts?.doubleClick) {\n await page.mouse.dblclick(centerX, centerY);\n } else {\n await page.mouse.click(centerX, centerY);\n }\n\n if (opts?.modifiers?.length) {\n for (const mod of opts.modifiers) {\n await page.keyboard.up(mod);\n }\n }\n } else {\n if (opts?.doubleClick) {\n await locator.dblclick({ timeout: opts?.timeoutMs ?? 30000 });\n } else {\n await locator.click({ timeout: opts?.timeoutMs ?? 30000 });\n }\n }\n }\n\n private async clickAtCoordinates(\n x: number,\n y: number,\n _elementDesc: string,\n opts?: any,\n contextId?: string\n ): Promise<void> {\n const page = this.getPage(contextId);\n if (opts?.doubleClick) {\n await page.mouse.dblclick(x, y);\n } else {\n await page.mouse.click(x, y);\n }\n }\n\n private async moveToCoordinates(\n x: number,\n y: number,\n _elementDesc: string,\n _opts?: any,\n contextId?: string\n ): Promise<void> {\n await this.getPage(contextId).mouse.move(x, y);\n }\n\n private async dragCoordinates(\n startX: number,\n startY: number,\n endX: number,\n endY: number,\n _elementDesc: string,\n _opts?: any,\n contextId?: string\n ): Promise<void> {\n const page = this.getPage(contextId);\n await page.mouse.move(startX, startY);\n await page.mouse.down();\n await page.mouse.move(endX, endY);\n await page.mouse.up();\n }\n\n private async hover(ref: string, _elementDesc?: string, opts?: any, contextId?: string): Promise<void> {\n await this.resolveRef(ref, contextId).hover({ timeout: opts?.timeoutMs ?? 30000 });\n }\n\n private async drag(\n startRef: string,\n _startElement: string,\n endRef: string,\n _endElement: string,\n opts?: any,\n contextId?: string\n ): Promise<void> {\n const startLocator = this.resolveRef(startRef, contextId);\n const endLocator = this.resolveRef(endRef, contextId);\n await startLocator.dragTo(endLocator, { timeout: opts?.timeoutMs ?? 60000 });\n }\n\n private async type(\n ref: string,\n text: string,\n _elementDesc?: string,\n submit?: boolean,\n opts?: any,\n contextId?: string\n ): Promise<void> {\n const locator = this.resolveRef(ref, contextId);\n await locator.clear();\n await locator.pressSequentially(text, {\n delay: opts?.delay ?? 0,\n timeout: opts?.timeoutMs ?? 30000,\n });\n if (submit) {\n await locator.press(\"Enter\");\n }\n }\n\n private async pressKey(key: string, _opts?: any, contextId?: string): Promise<void> {\n await this.getPage(contextId).keyboard.press(key);\n }\n\n private async fillForm(fields: any[], opts?: any, contextId?: string): Promise<void> {\n for (const field of fields) {\n const locator = this.resolveRef(field.ref, contextId);\n const fieldType = field.type ?? \"textbox\";\n\n switch (fieldType) {\n case \"checkbox\": {\n const isChecked = await locator.isChecked();\n const shouldBeChecked = field.value === \"true\";\n if (shouldBeChecked !== isChecked) {\n await locator.click({ timeout: opts?.timeoutMs ?? 30000 });\n }\n break;\n }\n case \"radio\":\n await locator.check({ timeout: opts?.timeoutMs ?? 30000 });\n break;\n case \"combobox\":\n await locator.selectOption(field.value, { timeout: opts?.timeoutMs ?? 30000 });\n break;\n default:\n await locator.fill(field.value, { timeout: opts?.timeoutMs ?? 30000 });\n }\n }\n }\n\n private async selectOption(\n ref: string,\n value: string,\n _elementDesc?: string,\n opts?: any,\n contextId?: string\n ): Promise<void> {\n await this.resolveRef(ref, contextId).selectOption(value, { timeout: opts?.timeoutMs ?? 30000 });\n }\n\n private async fileUpload(paths: string[], opts?: any, contextId?: string): Promise<void> {\n const fileChooser = await this.getPage(contextId).waitForEvent(\"filechooser\", {\n timeout: opts?.timeoutMs ?? 30000,\n });\n await fileChooser.setFiles(paths);\n }\n\n private async handleDialog(\n action: \"accept\" | \"dismiss\",\n promptText?: string,\n _opts?: any,\n contextId?: string\n ): Promise<void> {\n const slot = this.getSlot(contextId);\n const dialog = slot.pendingDialogs.shift();\n if (dialog) {\n if (action === \"accept\") {\n await dialog.accept(promptText);\n } else {\n await dialog.dismiss();\n }\n }\n }\n\n private async waitFor(opts?: any, contextId?: string): Promise<void> {\n const page = this.getPage(contextId);\n const timeout = opts?.timeout ?? opts?.timeoutMs ?? 30000;\n\n if (opts?.timeSec) {\n await page.waitForTimeout(opts.timeSec * 1000);\n return;\n }\n if (opts?.text) {\n await page.getByText(opts.text).first().waitFor({ state: \"visible\", timeout });\n return;\n }\n if (opts?.textGone) {\n await page.getByText(opts.textGone).first().waitFor({ state: \"hidden\", timeout });\n return;\n }\n if (opts?.selector) {\n await page.locator(opts.selector).waitFor({\n state: opts.state ?? \"visible\",\n timeout,\n });\n }\n }\n\n private async closePage(_opts?: any, contextId?: string): Promise<void> {\n const slot = this.getSlot(contextId);\n await slot.page.close();\n // If context still has pages, use the first one\n const remaining = slot.context.pages();\n if (remaining.length > 0) {\n slot.page = remaining[0];\n }\n }\n\n private async resize(width: number, height: number, _opts?: any, contextId?: string): Promise<void> {\n await this.getPage(contextId).setViewportSize({ width, height });\n }\n\n private async tabs(action: string, index?: number, _opts?: any, contextId?: string): Promise<unknown> {\n const slot = this.getSlot(contextId);\n const pages = slot.context.pages();\n\n switch (action) {\n case \"list\":\n return Promise.all(\n pages.map(async (p, i) => ({\n index: i,\n url: p.url(),\n title: await p.title().catch(() => \"\"),\n }))\n );\n case \"new\": {\n const newPage = await slot.context.newPage();\n slot.page = newPage;\n newPage.on(\"dialog\", (dialog) => slot.pendingDialogs.push(dialog));\n return { index: pages.length };\n }\n case \"close\":\n if (index !== undefined && pages[index]) {\n await pages[index].close();\n } else {\n await slot.page.close();\n }\n slot.page = slot.context.pages()[0] ?? slot.page;\n break;\n case \"select\":\n if (index !== undefined && pages[index]) {\n slot.page = pages[index];\n }\n break;\n }\n return null;\n }\n\n private async getStorageState(_opts?: any, contextId?: string): Promise<unknown> {\n const slot = this.getSlot(contextId);\n return slot.context.storageState();\n }\n\n private async getCurrentUrl(_opts?: any, contextId?: string): Promise<string> {\n return this.getPage(contextId).url();\n }\n\n private async getTitle(_opts?: any, contextId?: string): Promise<string> {\n return this.getPage(contextId).title();\n }\n\n private async getLinks(_opts?: any, contextId?: string): Promise<string[]> {\n const page = this.getPage(contextId);\n return page.$$eval(\"a[href]\", (links) =>\n links\n .map((a) => (a as HTMLAnchorElement).href)\n .filter((h): h is string => !!h && (h.startsWith(\"http://\") || h.startsWith(\"https://\")))\n );\n }\n\n private async getElementBoundingBox(\n ref: string,\n _opts?: any,\n contextId?: string\n ): Promise<{ x: number; y: number; width: number; height: number } | null> {\n const locator = this.resolveRef(ref, contextId);\n const box = await locator.boundingBox();\n if (!box) return null;\n return { x: box.x, y: box.y, width: box.width, height: box.height };\n }\n\n private async startTracing(_opts?: any, contextId?: string): Promise<void> {\n const slot = this.getSlot(contextId);\n await slot.context.tracing.start({ screenshots: true, snapshots: true });\n }\n\n private async stopTracing(_opts?: any, contextId?: string): Promise<{\n trace: string;\n network: string;\n resources: string;\n directory: string | null;\n legend: string | null;\n }> {\n const slot = this.getSlot(contextId);\n const tracePath = `/tmp/trace-${Date.now()}.zip`;\n await slot.context.tracing.stop({ path: tracePath });\n return {\n trace: tracePath,\n network: \"\",\n resources: \"\",\n directory: null,\n legend: `Trace saved to ${tracePath}`,\n };\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  readStoredToken
3
- } from "./chunk-SGNA6N2N.js";
3
+ } from "./chunk-2T64Z2NI.js";
4
4
 
5
5
  // src/local-run.ts
6
6
  import process from "process";
@@ -331,4 +331,4 @@ export {
331
331
  createTunnel,
332
332
  connectTunnel
333
333
  };
334
- //# sourceMappingURL=chunk-NRMZHITS.js.map
334
+ //# sourceMappingURL=chunk-V7U52ISX.js.map
@@ -1,11 +1,17 @@
1
1
  import {
2
2
  readStoredApiUrl,
3
3
  readStoredToken
4
- } from "./chunk-SGNA6N2N.js";
4
+ } from "./chunk-2T64Z2NI.js";
5
5
  import "./chunk-DGUM43GV.js";
6
6
 
7
7
  // src/feature-flag.ts
8
8
  import process from "process";
9
+ var ENV_URLS = {
10
+ prod: "https://api.trycanary.ai",
11
+ production: "https://api.trycanary.ai",
12
+ dev: "https://api.dev.trycanary.ai",
13
+ local: "http://localhost:3000"
14
+ };
9
15
  function getArgValue(argv, key) {
10
16
  const index = argv.indexOf(key);
11
17
  if (index === -1 || index >= argv.length - 1) return void 0;
@@ -16,7 +22,13 @@ function hasFlag(argv, ...flags) {
16
22
  }
17
23
  async function resolveConfig(argv) {
18
24
  const storedApiUrl = await readStoredApiUrl();
19
- const apiUrl = getArgValue(argv, "--api-url") ?? process.env.CANARY_API_URL ?? storedApiUrl ?? "https://api.trycanary.ai";
25
+ const env = getArgValue(argv, "--env");
26
+ if (env && !ENV_URLS[env]) {
27
+ console.error(`Unknown environment: ${env}`);
28
+ console.error("Valid environments: prod, dev, local");
29
+ process.exit(1);
30
+ }
31
+ const apiUrl = getArgValue(argv, "--api-url") ?? (env ? ENV_URLS[env] : void 0) ?? process.env.CANARY_API_URL ?? storedApiUrl ?? "https://api.trycanary.ai";
20
32
  const token = getArgValue(argv, "--token") ?? process.env.CANARY_API_TOKEN ?? await readStoredToken();
21
33
  if (!token) {
22
34
  console.error("Error: No API token found.");
@@ -172,8 +184,9 @@ function printFeatureFlagHelp() {
172
184
  " disable <name> --org <orgId> Disable a flag for an organization",
173
185
  "",
174
186
  "Options:",
187
+ " --env <env> Target environment (prod, dev, local)",
175
188
  " --json Output as JSON (list only)",
176
- " --api-url <url> API URL override",
189
+ " --api-url <url> API URL override (takes precedence over --env)",
177
190
  " --token <key> API token override"
178
191
  ].join("\n")
179
192
  );
@@ -210,4 +223,4 @@ async function runFeatureFlag(argv) {
210
223
  export {
211
224
  runFeatureFlag
212
225
  };
213
- //# sourceMappingURL=feature-flag-43WAHIUZ.js.map
226
+ //# sourceMappingURL=feature-flag-MFTRYRYX.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 FeatureFlag = {\n id: string;\n name: string;\n description: string | null;\n createdAt: string;\n};\n\ntype FeatureFlagListResponse = {\n ok: boolean;\n flags?: Array<FeatureFlag & { gates?: Array<{ gateType: string; gateValue: string }> }>;\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\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 handleList(argv: string[], apiUrl: string, token: string): Promise<void> {\n const jsonOutput = hasFlag(argv, \"--json\");\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 const flags = json.flags ?? [];\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 gates = flag.gates ?? [];\n const gateStr =\n gates.length > 0\n ? gates.map((g) => `${g.gateType}:${g.gateValue}`).join(\", \")\n : \"(no gates)\";\n console.log(` ${flag.name} ${flag.description ?? \"\"} [${gateStr}]`);\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\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 \"\",\n \"Options:\",\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 default:\n console.error(`Unknown sub-command: ${subCommand}`);\n printFeatureFlagHelp();\n process.exit(1);\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -4,13 +4,15 @@ import {
4
4
  createTunnel,
5
5
  runLocalTest,
6
6
  runTunnel
7
- } from "./chunk-NRMZHITS.js";
7
+ } from "./chunk-V7U52ISX.js";
8
8
  import {
9
+ ENV_URLS,
10
+ readAllStoredTokens,
9
11
  readStoredApiUrl,
10
12
  readStoredAuth,
11
13
  readStoredToken,
12
14
  saveAuth
13
- } from "./chunk-SGNA6N2N.js";
15
+ } from "./chunk-2T64Z2NI.js";
14
16
  import {
15
17
  __require
16
18
  } from "./chunk-DGUM43GV.js";
@@ -331,24 +333,12 @@ function countHealed(eventLogPath) {
331
333
  import process2 from "process";
332
334
  import readline from "readline";
333
335
  import { spawn as spawn2 } from "child_process";
334
- var ENV_URLS = {
335
- prod: {
336
- api: "https://api.trycanary.ai",
337
- app: "https://app.trycanary.ai"
338
- },
339
- production: {
340
- api: "https://api.trycanary.ai",
341
- app: "https://app.trycanary.ai"
342
- },
343
- dev: {
344
- api: "https://api.dev.trycanary.ai",
345
- app: "https://app.dev.trycanary.ai"
346
- },
347
- local: {
348
- api: "http://localhost:3000",
349
- app: "http://localhost:5173"
350
- }
351
- };
336
+ function envToProfile(env) {
337
+ if (env === "prod" || env === "production") return "production";
338
+ if (env === "dev") return "dev";
339
+ if (env === "local") return "local";
340
+ return env;
341
+ }
352
342
  function getArgValue(argv, key) {
353
343
  const index = argv.indexOf(key);
354
344
  if (index === -1) return void 0;
@@ -522,9 +512,11 @@ Choice [1-${orgs.length}]: `);
522
512
  finalOrgName = selectedOrg.name;
523
513
  }
524
514
  }
525
- const filePath = await saveAuth({ token: finalToken, apiUrl, orgId: finalOrgId, orgName: finalOrgName });
515
+ const profileName = env ? envToProfile(env) : void 0;
516
+ const filePath = await saveAuth({ token: finalToken, apiUrl, orgId: finalOrgId, orgName: finalOrgName }, profileName);
526
517
  const displayName = finalOrgName ? ` to ${finalOrgName}` : "";
527
- console.log(`Login successful${displayName}. Token saved to ${filePath}`);
518
+ const profileLabel = profileName ? ` (profile: ${profileName})` : "";
519
+ console.log(`Login successful${displayName}${profileLabel}. Token saved to ${filePath}`);
528
520
  console.log("Set CANARY_API_TOKEN to use the CLI without re-login.");
529
521
  }
530
522
 
@@ -965,8 +957,8 @@ function isSuperadminToken(token) {
965
957
  // src/index.ts
966
958
  var require2 = createRequire2(import.meta.url);
967
959
  var pkg = require2("../package.json");
968
- var loadMcp = () => import("./mcp-5N5Z343W.js").then((m) => m.runMcp);
969
- var loadLocalBrowser = () => import("./local-browser-REU2RIYX.js").then((m) => m.runLocalBrowser);
960
+ var loadMcp = () => import("./mcp-TMD2R5Z6.js").then((m) => m.runMcp);
961
+ var loadLocalBrowser = () => import("./local-browser-SYPTG6IQ.js").then((m) => m.runLocalBrowser);
970
962
  var canary = { run };
971
963
  var baseDir = typeof __dirname !== "undefined" ? __dirname : path4.dirname(fileURLToPath2(import.meta.url));
972
964
  var preloadPath = path4.join(baseDir, "runner", "preload.js");
@@ -1015,7 +1007,8 @@ function printHelp({ isSuperadmin }) {
1015
1007
  " canary debug-session [--env dev|local] [--json] Create browser debug session",
1016
1008
  " canary psql <query> [--json] Execute read-only SQL",
1017
1009
  " canary redis <command> [--json] Execute read-only Redis commands",
1018
- " canary feature-flag <sub-command> Manage feature flags"
1010
+ " canary feature-flag <sub-command> Manage feature flags",
1011
+ " canary knobs <sub-command> Manage knobs (global config)"
1019
1012
  );
1020
1013
  }
1021
1014
  lines.push(
@@ -1062,7 +1055,14 @@ function printHelp({ isSuperadmin }) {
1062
1055
  " create <name> [--description <text>] Create a flag",
1063
1056
  " delete <name> Delete a flag and its gates",
1064
1057
  " enable <name> --org <orgId> Enable for an org",
1065
- " disable <name> --org <orgId> Disable for an org"
1058
+ " disable <name> --org <orgId> Disable for an org",
1059
+ "",
1060
+ "Knobs sub-commands:",
1061
+ " list List all knobs",
1062
+ " get <key> Get a knob value",
1063
+ " set <key> <value> --type <type> Set a knob value",
1064
+ " delete <key> Delete a knob",
1065
+ " toggle <key> Toggle a boolean knob"
1066
1066
  );
1067
1067
  }
1068
1068
  lines.push(
@@ -1108,8 +1108,11 @@ function printTestHelp() {
1108
1108
  );
1109
1109
  }
1110
1110
  var COMMANDS_WITH_HELP = /* @__PURE__ */ new Set(["test"]);
1111
- async function resolveToken() {
1112
- return process7.env.CANARY_API_TOKEN ?? await readStoredToken();
1111
+ async function resolveIsSuperadmin() {
1112
+ const envToken = process7.env.CANARY_API_TOKEN;
1113
+ if (envToken) return isSuperadminToken(envToken);
1114
+ const tokens = await readAllStoredTokens();
1115
+ return tokens.some((t) => isSuperadminToken(t));
1113
1116
  }
1114
1117
  async function main(argv) {
1115
1118
  if (argv.includes("--version") || argv.includes("-V")) {
@@ -1119,13 +1122,11 @@ async function main(argv) {
1119
1122
  const [command, ...rest] = argv;
1120
1123
  const hasHelpFlag = argv.includes("--help") || argv.includes("-h");
1121
1124
  if (hasHelpFlag && (!command || !COMMANDS_WITH_HELP.has(command))) {
1122
- const token2 = await resolveToken();
1123
- printHelp({ isSuperadmin: isSuperadminToken(token2) });
1125
+ printHelp({ isSuperadmin: await resolveIsSuperadmin() });
1124
1126
  return;
1125
1127
  }
1126
1128
  if (!command || command === "help") {
1127
- const token2 = await resolveToken();
1128
- printHelp({ isSuperadmin: isSuperadminToken(token2) });
1129
+ printHelp({ isSuperadmin: await resolveIsSuperadmin() });
1129
1130
  return;
1130
1131
  }
1131
1132
  if (command === "version") {
@@ -1180,23 +1181,27 @@ async function main(argv) {
1180
1181
  return;
1181
1182
  }
1182
1183
  if (command === "psql") {
1183
- const { runPsql } = await import("./psql-7AEFGJWI.js");
1184
+ const { runPsql } = await import("./psql-6IFVXM3A.js");
1184
1185
  await runPsql(rest);
1185
1186
  return;
1186
1187
  }
1187
1188
  if (command === "redis") {
1188
- const { runRedis } = await import("./redis-BXYEPX4T.js");
1189
+ const { runRedis } = await import("./redis-HZC32IEO.js");
1189
1190
  await runRedis(rest);
1190
1191
  return;
1191
1192
  }
1192
1193
  if (command === "feature-flag") {
1193
- const { runFeatureFlag } = await import("./feature-flag-43WAHIUZ.js");
1194
+ const { runFeatureFlag } = await import("./feature-flag-MFTRYRYX.js");
1194
1195
  await runFeatureFlag(rest);
1195
1196
  return;
1196
1197
  }
1198
+ if (command === "knobs") {
1199
+ const { runKnobs } = await import("./knobs-T3O4Z3ZB.js");
1200
+ await runKnobs(rest);
1201
+ return;
1202
+ }
1197
1203
  console.log(`Unknown command "${command}".`);
1198
- const token = await resolveToken();
1199
- printHelp({ isSuperadmin: isSuperadminToken(token) });
1204
+ printHelp({ isSuperadmin: await resolveIsSuperadmin() });
1200
1205
  process7.exit(1);
1201
1206
  }
1202
1207
  if (import.meta.url === pathToFileURL2(process7.argv[1]).href) {