@canaryai/cli 0.2.9 → 0.2.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.
Files changed (58) hide show
  1. package/dist/{chunk-C2PGZRYK.js → chunk-CEW4BDXD.js} +26 -7
  2. package/dist/chunk-CEW4BDXD.js.map +1 -0
  3. package/dist/chunk-ERSNYLMZ.js +229 -0
  4. package/dist/chunk-ERSNYLMZ.js.map +1 -0
  5. package/dist/{chunk-XGO62PO2.js → chunk-MSMC6UXW.js} +198 -11
  6. package/dist/{chunk-XGO62PO2.js.map → chunk-MSMC6UXW.js.map} +1 -1
  7. package/dist/{chunk-LC7ZVXPH.js → chunk-Q7WFBG5C.js} +2 -2
  8. package/dist/{debug-workflow-I3F36JBL.js → debug-workflow-53ULOFJC.js} +4 -4
  9. package/dist/{docs-REHST3YB.js → docs-BEE3LOCO.js} +2 -2
  10. package/dist/{feature-flag-3HB5NTMY.js → feature-flag-CYTDV4ZB.js} +2 -2
  11. package/dist/index.js +61 -139
  12. package/dist/index.js.map +1 -1
  13. package/dist/init-M6I3MG3D.js +146 -0
  14. package/dist/init-M6I3MG3D.js.map +1 -0
  15. package/dist/{issues-YU57CHXS.js → issues-NLM72HLU.js} +2 -2
  16. package/dist/{knobs-QJ4IBLCT.js → knobs-O35GAU5M.js} +2 -2
  17. package/dist/list-4K4EIGAT.js +57 -0
  18. package/dist/list-4K4EIGAT.js.map +1 -0
  19. package/dist/local-NHXXPHZ3.js +63 -0
  20. package/dist/local-NHXXPHZ3.js.map +1 -0
  21. package/dist/{local-browser-MKTJ36KY.js → local-browser-VAZORCO3.js} +3 -3
  22. package/dist/login-ZLP64YQP.js +130 -0
  23. package/dist/login-ZLP64YQP.js.map +1 -0
  24. package/dist/{mcp-ZOKM2AUE.js → mcp-ZF5G5DCB.js} +4 -126
  25. package/dist/mcp-ZF5G5DCB.js.map +1 -0
  26. package/dist/{record-TNDBT3NY.js → record-V6QKFFH3.js} +6 -47
  27. package/dist/record-V6QKFFH3.js.map +1 -0
  28. package/dist/{release-L4IXOHDF.js → release-7TI7EIGD.js} +8 -4
  29. package/dist/release-7TI7EIGD.js.map +1 -0
  30. package/dist/{session-RNLKFS2Z.js → session-UGNJXRUW.js} +138 -70
  31. package/dist/session-UGNJXRUW.js.map +1 -0
  32. package/dist/skill-ORWAPBDW.js +424 -0
  33. package/dist/skill-ORWAPBDW.js.map +1 -0
  34. package/dist/{src-2WSMYBMJ.js → src-4VIDSK4A.js} +2 -2
  35. package/dist/start-E532F3BU.js +112 -0
  36. package/dist/start-E532F3BU.js.map +1 -0
  37. package/dist/workflow-HXIUXRFI.js +613 -0
  38. package/dist/workflow-HXIUXRFI.js.map +1 -0
  39. package/package.json +1 -1
  40. package/dist/chunk-C2PGZRYK.js.map +0 -1
  41. package/dist/chunk-DXIAHB72.js +0 -340
  42. package/dist/chunk-DXIAHB72.js.map +0 -1
  43. package/dist/chunk-QLFSJG5O.js +0 -93
  44. package/dist/chunk-QLFSJG5O.js.map +0 -1
  45. package/dist/mcp-ZOKM2AUE.js.map +0 -1
  46. package/dist/record-TNDBT3NY.js.map +0 -1
  47. package/dist/release-L4IXOHDF.js.map +0 -1
  48. package/dist/session-RNLKFS2Z.js.map +0 -1
  49. package/dist/skill-CZ7SHI3P.js +0 -156
  50. package/dist/skill-CZ7SHI3P.js.map +0 -1
  51. /package/dist/{chunk-LC7ZVXPH.js.map → chunk-Q7WFBG5C.js.map} +0 -0
  52. /package/dist/{debug-workflow-I3F36JBL.js.map → debug-workflow-53ULOFJC.js.map} +0 -0
  53. /package/dist/{docs-REHST3YB.js.map → docs-BEE3LOCO.js.map} +0 -0
  54. /package/dist/{feature-flag-3HB5NTMY.js.map → feature-flag-CYTDV4ZB.js.map} +0 -0
  55. /package/dist/{issues-YU57CHXS.js.map → issues-NLM72HLU.js.map} +0 -0
  56. /package/dist/{knobs-QJ4IBLCT.js.map → knobs-O35GAU5M.js.map} +0 -0
  57. /package/dist/{local-browser-MKTJ36KY.js.map → local-browser-VAZORCO3.js.map} +0 -0
  58. /package/dist/{src-2WSMYBMJ.js.map → src-4VIDSK4A.js.map} +0 -0
@@ -3,6 +3,7 @@ import { createRequire as __cr } from "module"; const require = __cr(import.meta
3
3
  // src/session/daemon-client.ts
4
4
  import { spawn } from "child_process";
5
5
  import fs from "fs/promises";
6
+ import { existsSync } from "fs";
6
7
  import path from "path";
7
8
  import os from "os";
8
9
  var PIDFILE_DIR = path.join(os.homedir(), ".config", "canary-cli");
@@ -45,12 +46,20 @@ async function healthCheck(port) {
45
46
  }
46
47
  }
47
48
  async function spawnDaemon() {
48
- const cliEntry = path.resolve(
49
- path.dirname(new URL(import.meta.url).pathname),
50
- "..",
51
- "index.ts"
52
- );
53
- const child = spawn(process.execPath, ["--bun", cliEntry, "session", "daemon"], {
49
+ const dir = path.dirname(new URL(import.meta.url).pathname);
50
+ const candidates = [
51
+ path.resolve(dir, "..", "index.ts"),
52
+ // source layout
53
+ path.resolve(dir, "index.js"),
54
+ // dist layout (flat)
55
+ path.resolve(dir, "..", "index.js")
56
+ // fallback
57
+ ];
58
+ const cliEntry = candidates.find((p) => existsSync(p));
59
+ if (!cliEntry) {
60
+ throw new Error(`Cannot find CLI entry point. Searched: ${candidates.join(", ")}`);
61
+ }
62
+ const child = spawn(process.execPath, [cliEntry, "session", "daemon"], {
54
63
  detached: true,
55
64
  stdio: ["ignore", "pipe", "ignore"],
56
65
  env: { ...process.env }
@@ -122,6 +131,14 @@ async function deleteAllSessions() {
122
131
  const port = await ensureDaemon();
123
132
  return daemonFetch(port, "DELETE", "/sessions");
124
133
  }
134
+ async function swapSessionContext(sessionId, params) {
135
+ const port = await ensureDaemon();
136
+ return daemonFetch(port, "POST", `/sessions/${sessionId}/swap-context`, params);
137
+ }
138
+ async function getSessionStorageState(sessionId) {
139
+ const port = await ensureDaemon();
140
+ return daemonFetch(port, "GET", `/sessions/${sessionId}/storage-state`);
141
+ }
125
142
  async function callTool(sessionId, toolName, args) {
126
143
  const port = await ensureDaemon();
127
144
  return daemonFetch(port, "POST", `/sessions/${sessionId}/tools/${toolName}`, args);
@@ -161,7 +178,9 @@ export {
161
178
  getSession,
162
179
  deleteSession,
163
180
  deleteAllSessions,
181
+ swapSessionContext,
182
+ getSessionStorageState,
164
183
  callTool,
165
184
  resolveTargetSession
166
185
  };
167
- //# sourceMappingURL=chunk-C2PGZRYK.js.map
186
+ //# sourceMappingURL=chunk-CEW4BDXD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/session/daemon-client.ts"],"sourcesContent":["/**\n * Daemon client — HTTP client for the session daemon.\n *\n * Handles pidfile read/write, stale PID detection, auto-start,\n * and provides typed HTTP helpers for daemon communication.\n *\n * @module\n */\n\nimport { spawn } from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport { existsSync } from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport type {\n DaemonState,\n DaemonResponse,\n SessionInfo,\n CreateSessionRequest,\n SwapContextRequest,\n ToolResponse,\n StorageStateResponse,\n} from './types.js';\n\nconst PIDFILE_DIR = path.join(os.homedir(), '.config', 'canary-cli');\nconst PIDFILE_PATH = path.join(PIDFILE_DIR, 'daemon.json');\nconst HEALTH_POLL_INTERVAL_MS = 100;\nconst HEALTH_POLL_TIMEOUT_MS = 15_000;\n\n/* ── Pidfile helpers ─────────────────────────────────────────────────── */\n\nasync function readPidfile(): Promise<DaemonState | null> {\n try {\n const content = await fs.readFile(PIDFILE_PATH, 'utf-8');\n return JSON.parse(content) as DaemonState;\n } catch {\n return null;\n }\n}\n\nfunction isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/* ── HTTP helpers ────────────────────────────────────────────────────── */\n\nasync function daemonFetch(\n port: number,\n method: string,\n path: string,\n body?: unknown\n): Promise<unknown> {\n const url = `http://127.0.0.1:${port}${path}`;\n const res = await fetch(url, {\n method,\n headers: body ? { 'Content-Type': 'application/json' } : undefined,\n body: body ? JSON.stringify(body) : undefined,\n });\n return res.json();\n}\n\nasync function healthCheck(port: number): Promise<boolean> {\n try {\n const res = await fetch(`http://127.0.0.1:${port}/health`, {\n signal: AbortSignal.timeout(2000),\n });\n return res.ok;\n } catch {\n return false;\n }\n}\n\n/* ── Auto-start ──────────────────────────────────────────────────────── */\n\nasync function spawnDaemon(): Promise<number> {\n // Resolve the canary CLI entry point\n // In source: this file is src/session/daemon-client.ts → ../index.ts\n // In dist: this file is dist/chunk-*.js → ./index.js (flat)\n const dir = path.dirname(new URL(import.meta.url).pathname);\n const candidates = [\n path.resolve(dir, '..', 'index.ts'), // source layout\n path.resolve(dir, 'index.js'), // dist layout (flat)\n path.resolve(dir, '..', 'index.js'), // fallback\n ];\n const cliEntry = candidates.find((p) => existsSync(p));\n if (!cliEntry) {\n throw new Error(`Cannot find CLI entry point. Searched: ${candidates.join(', ')}`);\n }\n\n const child = spawn(process.execPath, [cliEntry, 'session', 'daemon'], {\n detached: true,\n stdio: ['ignore', 'pipe', 'ignore'],\n env: { ...process.env },\n });\n\n child.unref();\n\n // Wait for \"DAEMON_READY:<port>\" on stdout\n return new Promise<number>((resolve, reject) => {\n let output = '';\n const timeout = setTimeout(() => {\n reject(new Error('Daemon startup timed out'));\n }, HEALTH_POLL_TIMEOUT_MS);\n\n child.stdout!.on('data', (data: Buffer) => {\n output += data.toString();\n const match = output.match(/DAEMON_READY:(\\d+)/);\n if (match) {\n clearTimeout(timeout);\n resolve(parseInt(match[1], 10));\n }\n });\n\n child.on('error', (err) => {\n clearTimeout(timeout);\n reject(err);\n });\n\n child.on('exit', (code) => {\n clearTimeout(timeout);\n if (!output.includes('DAEMON_READY')) {\n reject(new Error(`Daemon exited with code ${code} before becoming ready`));\n }\n });\n });\n}\n\n/* ── Public API ──────────────────────────────────────────────────────── */\n\n/**\n * Ensure the daemon is running and return its port.\n * Starts the daemon if needed, cleans up stale pidfiles.\n */\nexport async function ensureDaemon(): Promise<number> {\n const state = await readPidfile();\n\n if (state) {\n if (isProcessAlive(state.pid)) {\n // Verify it actually responds\n if (await healthCheck(state.port)) {\n return state.port;\n }\n }\n // Stale pidfile — clean up\n try {\n await fs.unlink(PIDFILE_PATH);\n } catch {\n // ignore\n }\n }\n\n // Spawn new daemon\n const port = await spawnDaemon();\n\n // Poll until healthy\n const deadline = Date.now() + HEALTH_POLL_TIMEOUT_MS;\n while (Date.now() < deadline) {\n if (await healthCheck(port)) return port;\n await new Promise((r) => setTimeout(r, HEALTH_POLL_INTERVAL_MS));\n }\n\n throw new Error('Daemon failed to become healthy after spawn');\n}\n\n/**\n * Get daemon port if running, null otherwise.\n */\nexport async function getDaemonPort(): Promise<number | null> {\n const state = await readPidfile();\n if (!state) return null;\n if (!isProcessAlive(state.pid)) return null;\n if (!(await healthCheck(state.port))) return null;\n return state.port;\n}\n\n/* ── Session operations ──────────────────────────────────────────────── */\n\nexport async function createSession(params: CreateSessionRequest): Promise<DaemonResponse<SessionInfo>> {\n const port = await ensureDaemon();\n return daemonFetch(port, 'POST', '/sessions', params) as Promise<DaemonResponse<SessionInfo>>;\n}\n\nexport async function listSessions(): Promise<DaemonResponse<SessionInfo[]>> {\n const port = await ensureDaemon();\n return daemonFetch(port, 'GET', '/sessions') as Promise<DaemonResponse<SessionInfo[]>>;\n}\n\nexport async function getSession(sessionId: string): Promise<DaemonResponse<SessionInfo>> {\n const port = await ensureDaemon();\n return daemonFetch(port, 'GET', `/sessions/${sessionId}`) as Promise<DaemonResponse<SessionInfo>>;\n}\n\nexport async function deleteSession(sessionId: string): Promise<DaemonResponse> {\n const port = await ensureDaemon();\n return daemonFetch(port, 'DELETE', `/sessions/${sessionId}`) as Promise<DaemonResponse>;\n}\n\nexport async function deleteAllSessions(): Promise<DaemonResponse> {\n const port = await ensureDaemon();\n return daemonFetch(port, 'DELETE', '/sessions') as Promise<DaemonResponse>;\n}\n\nexport async function swapSessionContext(\n sessionId: string,\n params: SwapContextRequest\n): Promise<DaemonResponse<SessionInfo>> {\n const port = await ensureDaemon();\n return daemonFetch(port, 'POST', `/sessions/${sessionId}/swap-context`, params) as Promise<\n DaemonResponse<SessionInfo>\n >;\n}\n\nexport async function getSessionStorageState(sessionId: string): Promise<StorageStateResponse> {\n const port = await ensureDaemon();\n return daemonFetch(port, 'GET', `/sessions/${sessionId}/storage-state`) as Promise<StorageStateResponse>;\n}\n\nexport async function callTool(\n sessionId: string,\n toolName: string,\n args: Record<string, unknown>\n): Promise<ToolResponse> {\n const port = await ensureDaemon();\n return daemonFetch(port, 'POST', `/sessions/${sessionId}/tools/${toolName}`, args) as Promise<ToolResponse>;\n}\n\n/**\n * Resolve the target session for a command.\n * If there's exactly one session, auto-targets it.\n * If a sessionId or name is provided, looks it up.\n */\nexport async function resolveTargetSession(sessionIdOrName?: string): Promise<SessionInfo> {\n const result = await listSessions();\n if (!result.ok || !result.data) {\n throw new Error('Failed to list sessions');\n }\n const sessions = result.data;\n\n if (sessions.length === 0) {\n throw new Error('No active sessions. Start one with: canary session start');\n }\n\n if (sessionIdOrName) {\n const match = sessions.find(\n (s) => s.id === sessionIdOrName || s.name === sessionIdOrName\n );\n if (!match) {\n throw new Error(\n `Session \"${sessionIdOrName}\" not found. Active sessions: ${sessions.map((s) => s.id).join(', ')}`\n );\n }\n return match;\n }\n\n if (sessions.length === 1) {\n return sessions[0];\n }\n\n throw new Error(\n `Multiple sessions active. Specify one with --session:\\n${sessions.map((s) => ` ${s.id} (${s.name})`).join('\\n')}`\n );\n}\n"],"mappings":"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","names":["path"]}
@@ -0,0 +1,229 @@
1
+ import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
2
+ import {
3
+ getArgValue
4
+ } from "./chunk-PWWQGYFG.js";
5
+ import {
6
+ getCanaryTmpDir
7
+ } from "./chunk-XAA5VQ5N.js";
8
+
9
+ // src/cli-helpers.ts
10
+ import fs from "fs/promises";
11
+ import path from "path";
12
+ import process from "process";
13
+ function toLifecycleLabel(stage) {
14
+ switch (stage) {
15
+ case "deprecated":
16
+ return "deprecated";
17
+ case "ready_for_cleanup":
18
+ return "ready_for_cleanup";
19
+ default:
20
+ return "active";
21
+ }
22
+ }
23
+ function parseLifecycleStage(argv) {
24
+ const stage = getArgValue(argv, "--stage");
25
+ if (!stage || !["active", "deprecated", "ready_for_cleanup"].includes(stage)) {
26
+ console.error("Error: --stage is required and must be one of: active, deprecated, ready_for_cleanup");
27
+ process.exit(1);
28
+ }
29
+ return stage;
30
+ }
31
+ async function apiRequest(apiUrl, token, method, path2, body) {
32
+ const res = await fetch(`${apiUrl}${path2}`, {
33
+ method,
34
+ headers: {
35
+ Authorization: `Bearer ${token}`,
36
+ "Content-Type": "application/json"
37
+ },
38
+ ...body ? { body: JSON.stringify(body) } : {}
39
+ });
40
+ if (res.status === 401) {
41
+ console.error("Error: Unauthorized. Your session may have expired.");
42
+ console.error("Run: canary login");
43
+ process.exit(1);
44
+ }
45
+ return await res.json();
46
+ }
47
+ async function downloadStorageState(opts) {
48
+ const tmpFile = path.join(
49
+ getCanaryTmpDir(),
50
+ `${opts.prefix ?? "canary-ss"}-${Date.now()}.json`
51
+ );
52
+ try {
53
+ const res = await fetch(
54
+ `${opts.apiUrl}/org/properties/${opts.propertyId}/credentials/${opts.credentialId}/storage-state/download`,
55
+ {
56
+ headers: { Authorization: `Bearer ${opts.token}` },
57
+ redirect: "follow"
58
+ }
59
+ );
60
+ if (res.ok) {
61
+ const body = await res.text();
62
+ await fs.writeFile(tmpFile, body, "utf-8");
63
+ return tmpFile;
64
+ }
65
+ } catch {
66
+ }
67
+ return void 0;
68
+ }
69
+ async function selectCredential(apiUrl, token, credentialArg) {
70
+ const credentials = await fetchList(apiUrl, token, "/org/credentials", "items");
71
+ if (credentials.length === 0) {
72
+ console.error("No credentials found. Create one first in the web app.");
73
+ return null;
74
+ }
75
+ if (credentialArg) {
76
+ const match = credentials.find(
77
+ (c) => c.id === credentialArg || c.name.toLowerCase() === credentialArg.toLowerCase()
78
+ );
79
+ if (!match) {
80
+ console.error(`Credential "${credentialArg}" not found.`);
81
+ console.error("Available credentials:");
82
+ for (const c of credentials) {
83
+ const baseUrl = c.propertyBaseUrl ? ` \u2014 ${c.propertyBaseUrl}` : "";
84
+ console.error(` - ${c.name} [${c.propertyName ?? c.propertyId}${baseUrl}]`);
85
+ }
86
+ return null;
87
+ }
88
+ return match;
89
+ }
90
+ console.log("\nSelect a credential:\n");
91
+ const labels = credentials.map((c) => {
92
+ const property = c.propertyName ?? c.propertyId;
93
+ const baseUrl = c.propertyBaseUrl ? ` \u2014 ${c.propertyBaseUrl}` : "";
94
+ return `${c.name} [${property}${baseUrl}]`;
95
+ });
96
+ const readline = await import("readline");
97
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
98
+ const answer = await new Promise((resolve) => {
99
+ for (let i = 0; i < labels.length; i++) {
100
+ console.log(` ${i + 1}. ${labels[i]}`);
101
+ }
102
+ rl.question("\nEnter number: ", (ans) => {
103
+ rl.close();
104
+ resolve(ans.trim());
105
+ });
106
+ });
107
+ const idx = parseInt(answer, 10) - 1;
108
+ if (isNaN(idx) || idx < 0 || idx >= credentials.length) {
109
+ console.error("Invalid selection.");
110
+ return null;
111
+ }
112
+ return credentials[idx];
113
+ }
114
+ async function fetchProperties(apiUrl, token) {
115
+ return fetchList(apiUrl, token, "/org/properties", "data");
116
+ }
117
+ async function selectProperty(apiUrl, token, hint) {
118
+ const properties = await fetchProperties(apiUrl, token);
119
+ if (properties.length === 0) {
120
+ console.error("No properties found. Create one first in the web app.");
121
+ return null;
122
+ }
123
+ if (hint) {
124
+ const match = properties.find(
125
+ (p) => p.id === hint || p.name.toLowerCase() === hint.toLowerCase()
126
+ );
127
+ if (!match) {
128
+ console.error(`Property "${hint}" not found.`);
129
+ console.error("Available properties:");
130
+ for (const p of properties) {
131
+ console.error(` - ${p.name} (${p.baseUrl})`);
132
+ }
133
+ return null;
134
+ }
135
+ return match;
136
+ }
137
+ if (properties.length === 1) {
138
+ console.log(`Auto-selected property: ${properties[0].name}`);
139
+ return properties[0];
140
+ }
141
+ return promptChoice(
142
+ "Select a property:",
143
+ properties.map((p) => ({ label: `${p.name} \u2014 ${p.baseUrl}`, value: p }))
144
+ );
145
+ }
146
+ async function promptInput(question, defaultValue) {
147
+ const readline = await import("readline");
148
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
149
+ const prompt = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
150
+ const answer = await new Promise((resolve) => {
151
+ rl.question(prompt, (ans) => {
152
+ rl.close();
153
+ resolve(ans.trim());
154
+ });
155
+ });
156
+ return answer || defaultValue || "";
157
+ }
158
+ async function promptChoice(question, options) {
159
+ console.log(`
160
+ ${question}
161
+ `);
162
+ for (let i = 0; i < options.length; i++) {
163
+ console.log(` ${i + 1}. ${options[i].label}`);
164
+ }
165
+ const readline = await import("readline");
166
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
167
+ const answer = await new Promise((resolve) => {
168
+ rl.question("\nEnter number: ", (ans) => {
169
+ rl.close();
170
+ resolve(ans.trim());
171
+ });
172
+ });
173
+ const idx = parseInt(answer, 10) - 1;
174
+ if (isNaN(idx) || idx < 0 || idx >= options.length) {
175
+ console.error("Invalid selection.");
176
+ return null;
177
+ }
178
+ return options[idx].value;
179
+ }
180
+ async function uploadStorageState(opts) {
181
+ const res = await fetch(
182
+ `${opts.apiUrl}/org/properties/${opts.propertyId}/credentials/${opts.credentialId}/storage-state/upload`,
183
+ {
184
+ method: "PUT",
185
+ headers: {
186
+ Authorization: `Bearer ${opts.token}`,
187
+ "Content-Type": "application/json"
188
+ },
189
+ body: JSON.stringify({ storageState: opts.storageState })
190
+ }
191
+ );
192
+ if (res.status === 401) {
193
+ console.error("Error: Unauthorized. Your session may have expired.");
194
+ console.error("Run: canary login");
195
+ process.exit(1);
196
+ }
197
+ return await res.json();
198
+ }
199
+ async function fetchList(apiUrl, token, path2, listKey) {
200
+ const res = await fetch(`${apiUrl}${path2}`, {
201
+ headers: { Authorization: `Bearer ${token}` }
202
+ });
203
+ if (res.status === 401) {
204
+ console.error("Error: Unauthorized. Your session may have expired.");
205
+ console.error("Run: canary login");
206
+ process.exit(1);
207
+ }
208
+ const json = await res.json();
209
+ if (!json.ok) {
210
+ console.error(`Error: ${json.error}`);
211
+ process.exit(1);
212
+ }
213
+ return json[listKey] ?? [];
214
+ }
215
+
216
+ export {
217
+ toLifecycleLabel,
218
+ parseLifecycleStage,
219
+ apiRequest,
220
+ downloadStorageState,
221
+ selectCredential,
222
+ fetchProperties,
223
+ selectProperty,
224
+ promptInput,
225
+ promptChoice,
226
+ uploadStorageState,
227
+ fetchList
228
+ };
229
+ //# sourceMappingURL=chunk-ERSNYLMZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli-helpers.ts"],"sourcesContent":["/**\n * Shared CLI helpers for superadmin management commands (knobs, feature-flags).\n */\n\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport { getCanaryTmpDir } from \"@chatsdet/tmp\";\nimport { getArgValue } from \"./auth.js\";\n\nexport type LifecycleStage = \"active\" | \"deprecated\" | \"ready_for_cleanup\";\n\nexport function 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\nexport function 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\nexport async function apiRequest<T extends { ok: boolean; error?: string }>(\n apiUrl: string,\n token: string,\n method: string,\n path: string,\n body?: Record<string, unknown>\n): Promise<T> {\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 return (await res.json()) as T;\n}\n\nexport async function downloadStorageState(opts: {\n apiUrl: string;\n token: string;\n propertyId: string;\n credentialId: string;\n prefix?: string;\n}): Promise<string | undefined> {\n const tmpFile = path.join(\n getCanaryTmpDir(),\n `${opts.prefix ?? \"canary-ss\"}-${Date.now()}.json`\n );\n try {\n const res = await fetch(\n `${opts.apiUrl}/org/properties/${opts.propertyId}/credentials/${opts.credentialId}/storage-state/download`,\n {\n headers: { Authorization: `Bearer ${opts.token}` },\n redirect: \"follow\",\n }\n );\n if (res.ok) {\n const body = await res.text();\n await fs.writeFile(tmpFile, body, \"utf-8\");\n return tmpFile;\n }\n } catch {\n // Caller handles missing storage state\n }\n return undefined;\n}\n\n/* ── Credential selection ─────────────────────────────────────────────── */\n\nexport interface CredentialListItem {\n id: string;\n name: string;\n propertyId: string;\n propertyName?: string;\n propertyBaseUrl?: string;\n loginUrl?: string;\n storageStateS3Key?: string | null;\n}\n\n/**\n * Fetch org credentials and let the user pick one interactively.\n *\n * @param credentialArg - Credential name or ID to auto-select, or undefined for interactive\n * @returns The selected credential, or null if none found / invalid selection\n */\nexport async function selectCredential(\n apiUrl: string,\n token: string,\n credentialArg?: string\n): Promise<CredentialListItem | null> {\n const credentials = await fetchList<CredentialListItem>(apiUrl, token, \"/org/credentials\", \"items\");\n\n if (credentials.length === 0) {\n console.error(\"No credentials found. Create one first in the web app.\");\n return null;\n }\n\n if (credentialArg) {\n const match = credentials.find(\n (c) =>\n c.id === credentialArg ||\n c.name.toLowerCase() === credentialArg.toLowerCase()\n );\n if (!match) {\n console.error(`Credential \"${credentialArg}\" not found.`);\n console.error(\"Available credentials:\");\n for (const c of credentials) {\n const baseUrl = c.propertyBaseUrl ? ` — ${c.propertyBaseUrl}` : '';\n console.error(` - ${c.name} [${c.propertyName ?? c.propertyId}${baseUrl}]`);\n }\n return null;\n }\n return match;\n }\n\n // Interactive selection\n console.log(\"\\nSelect a credential:\\n\");\n const labels = credentials.map((c) => {\n const property = c.propertyName ?? c.propertyId;\n const baseUrl = c.propertyBaseUrl ? ` — ${c.propertyBaseUrl}` : '';\n return `${c.name} [${property}${baseUrl}]`;\n });\n\n const readline = await import(\"node:readline\");\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout });\n const answer = await new Promise<string>((resolve) => {\n for (let i = 0; i < labels.length; i++) {\n console.log(` ${i + 1}. ${labels[i]}`);\n }\n rl.question(\"\\nEnter number: \", (ans) => {\n rl.close();\n resolve(ans.trim());\n });\n });\n\n const idx = parseInt(answer, 10) - 1;\n if (isNaN(idx) || idx < 0 || idx >= credentials.length) {\n console.error(\"Invalid selection.\");\n return null;\n }\n\n return credentials[idx];\n}\n\n/* ── Property selection ──────────────────────────────────────────────── */\n\nexport interface PropertyListItem {\n id: string;\n name: string;\n baseUrl: string;\n}\n\nexport async function fetchProperties(\n apiUrl: string,\n token: string\n): Promise<PropertyListItem[]> {\n return fetchList<PropertyListItem>(apiUrl, token, \"/org/properties\", \"data\");\n}\n\nexport async function selectProperty(\n apiUrl: string,\n token: string,\n hint?: string\n): Promise<PropertyListItem | null> {\n const properties = await fetchProperties(apiUrl, token);\n\n if (properties.length === 0) {\n console.error(\"No properties found. Create one first in the web app.\");\n return null;\n }\n\n if (hint) {\n const match = properties.find(\n (p) =>\n p.id === hint ||\n p.name.toLowerCase() === hint.toLowerCase()\n );\n if (!match) {\n console.error(`Property \"${hint}\" not found.`);\n console.error(\"Available properties:\");\n for (const p of properties) {\n console.error(` - ${p.name} (${p.baseUrl})`);\n }\n return null;\n }\n return match;\n }\n\n if (properties.length === 1) {\n console.log(`Auto-selected property: ${properties[0].name}`);\n return properties[0];\n }\n\n return promptChoice(\n \"Select a property:\",\n properties.map((p) => ({ label: `${p.name} — ${p.baseUrl}`, value: p }))\n );\n}\n\n/* ── Interactive prompt helpers ──────────────────────────────────────── */\n\nexport async function promptInput(question: string, defaultValue?: string): Promise<string> {\n const readline = await import(\"node:readline\");\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout });\n const prompt = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;\n const answer = await new Promise<string>((resolve) => {\n rl.question(prompt, (ans) => {\n rl.close();\n resolve(ans.trim());\n });\n });\n return answer || defaultValue || \"\";\n}\n\nexport async function promptChoice<T>(\n question: string,\n options: Array<{ label: string; value: T }>\n): Promise<T | null> {\n console.log(`\\n${question}\\n`);\n for (let i = 0; i < options.length; i++) {\n console.log(` ${i + 1}. ${options[i].label}`);\n }\n const readline = await import(\"node:readline\");\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout });\n const answer = await new Promise<string>((resolve) => {\n rl.question(\"\\nEnter number: \", (ans) => {\n rl.close();\n resolve(ans.trim());\n });\n });\n\n const idx = parseInt(answer, 10) - 1;\n if (isNaN(idx) || idx < 0 || idx >= options.length) {\n console.error(\"Invalid selection.\");\n return null;\n }\n return options[idx].value;\n}\n\nexport async function promptConfirm(question: string): Promise<boolean> {\n const answer = await promptInput(`${question} (y/n)`, \"n\");\n return answer.toLowerCase() === \"y\" || answer.toLowerCase() === \"yes\";\n}\n\nexport async function uploadStorageState(opts: {\n apiUrl: string;\n token: string;\n propertyId: string;\n credentialId: string;\n storageState: unknown;\n}): Promise<{ ok: boolean; s3Key?: string; error?: string }> {\n const res = await fetch(\n `${opts.apiUrl}/org/properties/${opts.propertyId}/credentials/${opts.credentialId}/storage-state/upload`,\n {\n method: \"PUT\",\n headers: {\n Authorization: `Bearer ${opts.token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ storageState: opts.storageState }),\n }\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 return (await res.json()) as { ok: boolean; s3Key?: string; error?: string };\n}\n\nexport async function fetchList<T>(\n apiUrl: string,\n token: string,\n path: string,\n listKey: string\n): Promise<T[]> {\n const res = await fetch(`${apiUrl}${path}`, {\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 Record<string, unknown>;\n\n if (!json.ok) {\n console.error(`Error: ${json.error}`);\n process.exit(1);\n }\n\n return (json[listKey] as T[]) ?? [];\n}\n"],"mappings":"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","names":["path","path"]}
@@ -38537,6 +38537,18 @@ function normalizeIconLabelText(role, text) {
38537
38537
  }
38538
38538
  return ICON_ONLY_ROLES.has(role) ? "(icon)" : "";
38539
38539
  }
38540
+ var STYLED_WRAPPER_RE = /^Styled\(\w+\)$/;
38541
+ var MUI_CLASS_RE = /^Mui\w+-[\w-]+$/;
38542
+ var HAS_ASCII_ALNUM_RE = /[A-Za-z0-9]/;
38543
+ function isNoisyLabel(text) {
38544
+ const trimmed = text.trim();
38545
+ if (!trimmed) return false;
38546
+ if (STYLED_WRAPPER_RE.test(trimmed)) return true;
38547
+ if (MUI_CLASS_RE.test(trimmed)) return true;
38548
+ if (HAS_PRIVATE_USE_GLYPH_RE.test(trimmed)) return false;
38549
+ if (!HAS_ASCII_ALNUM_RE.test(trimmed)) return true;
38550
+ return false;
38551
+ }
38540
38552
 
38541
38553
  // ../browser-core/src/snapshot-formatter-shared.ts
38542
38554
  var MAX_SEARCH_MATCHES = 10;
@@ -38651,9 +38663,34 @@ function composeLabel(element) {
38651
38663
  }
38652
38664
  return textParts.join(" | ") || element.role;
38653
38665
  }
38666
+ function composeChildLabel(element) {
38667
+ const textParts = [];
38668
+ function collectText(el, depth) {
38669
+ if (depth > 3) return;
38670
+ const text = getElementText(el);
38671
+ if (text && TEXT_CARRYING_ROLES.has(el.role)) {
38672
+ textParts.push(text);
38673
+ }
38674
+ for (const child of el.children) {
38675
+ if (!INTERACTIVE_LEAF_ROLES.has(child.role)) {
38676
+ collectText(child, depth + 1);
38677
+ }
38678
+ }
38679
+ }
38680
+ for (const child of element.children) {
38681
+ if (!INTERACTIVE_LEAF_ROLES.has(child.role)) {
38682
+ collectText(child, 0);
38683
+ }
38684
+ }
38685
+ return textParts.length > 0 ? textParts.join(" | ") : null;
38686
+ }
38654
38687
  function getDisplayText(element, options) {
38655
38688
  const isClickable = isActionableClickableGeneric(element);
38656
- const rawTextSource = options?.overrideLabel ?? (isClickable ? composeLabel(element) : element.text ?? "");
38689
+ let rawTextSource = options?.overrideLabel ?? (isClickable ? composeLabel(element) : element.text ?? "");
38690
+ if (rawTextSource && isNoisyLabel(rawTextSource) && INTERACTIVE_LEAF_ROLES.has(element.role)) {
38691
+ const childText = composeChildLabel(element);
38692
+ rawTextSource = childText ?? "";
38693
+ }
38657
38694
  return normalizeIconLabelText(element.role, rawTextSource);
38658
38695
  }
38659
38696
  function hasInteractiveDescendants(element) {
@@ -38852,7 +38889,7 @@ function extractGridCell(cellEl) {
38852
38889
  findInteractive(cellEl);
38853
38890
  if (interactiveElements.length === 0) {
38854
38891
  const textValue = extractCellText(cellEl);
38855
- if (textValue) {
38892
+ if (textValue && !isNoisyLabel(textValue)) {
38856
38893
  return {
38857
38894
  ref: cellEl.ref,
38858
38895
  column: cellEl.text || "",
@@ -38866,7 +38903,8 @@ function extractGridCell(cellEl) {
38866
38903
  (best, current) => gridCellPriority(current) > gridCellPriority(best) ? current : best
38867
38904
  );
38868
38905
  const column = stripBlankPatterns(found.column);
38869
- const value = isBlankValue(found.value) ? "" : stripBlankPatterns(found.value).trim();
38906
+ const rawValue = isBlankValue(found.value) ? "" : stripBlankPatterns(found.value).trim();
38907
+ const value = isNoisyLabel(rawValue) ? "" : rawValue;
38870
38908
  const gridcellLabel = cellEl.text ? stripBlankPatterns(cellEl.text).trim() : void 0;
38871
38909
  return {
38872
38910
  ref: found.ref,
@@ -39821,6 +39859,24 @@ function markBackgroundSections(sections) {
39821
39859
  markBackgroundSections(section.subsections);
39822
39860
  }
39823
39861
  }
39862
+ function mergeFilterDropdownPairs(elements) {
39863
+ for (let i = elements.length - 2; i >= 0; i--) {
39864
+ const current = elements[i];
39865
+ const next = elements[i + 1];
39866
+ if (current.role === "generic" && current.isClickable && next.role === "img" && next.isClickable && (!next.label || next.label === "img")) {
39867
+ if (!current.fieldLabel && next.fieldLabel) {
39868
+ current.fieldLabel = next.fieldLabel;
39869
+ }
39870
+ elements.splice(i + 1, 1);
39871
+ }
39872
+ }
39873
+ }
39874
+ function applyFilterMerge(sections) {
39875
+ for (const section of sections) {
39876
+ mergeFilterDropdownPairs(section.elements);
39877
+ applyFilterMerge(section.subsections);
39878
+ }
39879
+ }
39824
39880
  function buildSectionTree(elements) {
39825
39881
  const sections = [];
39826
39882
  for (const element of elements) {
@@ -39830,6 +39886,7 @@ function buildSectionTree(elements) {
39830
39886
  }
39831
39887
  }
39832
39888
  markBackgroundSections(sections);
39889
+ applyFilterMerge(sections);
39833
39890
  return sections;
39834
39891
  }
39835
39892
  function markMatchingSections(sections, matchedRefs) {
@@ -40146,6 +40203,60 @@ ACTIONS:
40146
40203
  - Use bash or python_execute on the FULL TREE FILE for detailed element analysis
40147
40204
  ---`;
40148
40205
  }
40206
+ function disambiguateDuplicateLabels(sections) {
40207
+ const elementEntries = [];
40208
+ function collectElements(section) {
40209
+ for (const el of section.elements) {
40210
+ elementEntries.push({ el, sectionHeading: section.heading });
40211
+ }
40212
+ for (const sub of section.subsections) {
40213
+ collectElements(sub);
40214
+ }
40215
+ }
40216
+ for (const section of sections) {
40217
+ collectElements(section);
40218
+ }
40219
+ const SKIP_LABELS = /* @__PURE__ */ new Set(["(icon)", "button", "link", "generic"]);
40220
+ const byLabel = /* @__PURE__ */ new Map();
40221
+ for (const entry of elementEntries) {
40222
+ const label = entry.el.label || entry.el.name;
40223
+ if (!label || label.length === 0) continue;
40224
+ if (SKIP_LABELS.has(label)) continue;
40225
+ if (!entry.el.ref) continue;
40226
+ const existing = byLabel.get(label);
40227
+ if (existing) {
40228
+ existing.push(entry);
40229
+ } else {
40230
+ byLabel.set(label, [entry]);
40231
+ }
40232
+ }
40233
+ for (const [, entries] of byLabel) {
40234
+ if (entries.length < 2) continue;
40235
+ const distinctSections = new Set(entries.map((e) => e.sectionHeading));
40236
+ if (distinctSections.size > 1) {
40237
+ for (const entry of entries) {
40238
+ const currentLabel = entry.el.label || entry.el.name || "";
40239
+ if (entry.el.label) {
40240
+ entry.el.label = `${currentLabel} (in ${entry.sectionHeading})`;
40241
+ } else if (entry.el.name) {
40242
+ entry.el.name = `${currentLabel} (in ${entry.sectionHeading})`;
40243
+ }
40244
+ }
40245
+ } else {
40246
+ const ordinals = ["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th"];
40247
+ for (let i = 0; i < entries.length; i++) {
40248
+ const entry = entries[i];
40249
+ const ordinal = i < ordinals.length ? ordinals[i] : `${i + 1}th`;
40250
+ const currentLabel = entry.el.label || entry.el.name || "";
40251
+ if (entry.el.label) {
40252
+ entry.el.label = `${currentLabel} (${ordinal})`;
40253
+ } else if (entry.el.name) {
40254
+ entry.el.name = `${currentLabel} (${ordinal})`;
40255
+ }
40256
+ }
40257
+ }
40258
+ }
40259
+ }
40149
40260
  function formatSemanticSnapshot(yaml, options) {
40150
40261
  if (!yaml || yaml.trim() === "") {
40151
40262
  return "---\nSEMANTIC SNAPSHOT - 0 elements\nPage appears empty or not loaded.\n---";
@@ -40179,6 +40290,7 @@ function formatSemanticSnapshot(yaml, options) {
40179
40290
  if (options?.probeResult?.ref) {
40180
40291
  markMatchingSections(sortedSections, /* @__PURE__ */ new Set([options.probeResult.ref]));
40181
40292
  }
40293
+ disambiguateDuplicateLabels(sortedSections);
40182
40294
  const elementCount = countRenderedElements(sortedSections);
40183
40295
  const sectionCount = countTotalSections(sortedSections);
40184
40296
  const lines = [];
@@ -40204,7 +40316,11 @@ function formatSemanticSnapshot(yaml, options) {
40204
40316
  lines.push("");
40205
40317
  }
40206
40318
  }
40207
- return lines.join("\n").trim();
40319
+ let result = lines.join("\n").trim();
40320
+ if (options?.offScreenRefs && options.offScreenRefs.size > 0) {
40321
+ result = markOffScreenElements(result, options.offScreenRefs);
40322
+ }
40323
+ return result;
40208
40324
  }
40209
40325
  function expandSection(yaml, sectionRef) {
40210
40326
  if (!yaml || yaml.trim() === "") {
@@ -40278,6 +40394,15 @@ Section with ref="${sectionRef}" not found in snapshot.
40278
40394
  targetSection.collapsed = false;
40279
40395
  return formatExpandedSectionOutput(targetSection);
40280
40396
  }
40397
+ function markOffScreenElements(text, offScreenRefs) {
40398
+ if (offScreenRefs.size === 0) return text;
40399
+ return text.replace(/\[(e\d+)\](?! \[off-screen\])/g, (match, ref) => {
40400
+ if (offScreenRefs.has(ref)) {
40401
+ return `[${ref}] [off-screen]`;
40402
+ }
40403
+ return match;
40404
+ });
40405
+ }
40281
40406
 
40282
40407
  // ../browser-core/src/table-extractor.ts
40283
40408
  function extractTablesFromSnapshot(yamlOrResponse, options) {
@@ -41458,6 +41583,42 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41458
41583
  await fs2.writeFile(snapshotFilePath, yaml, "utf-8");
41459
41584
  } catch {
41460
41585
  }
41586
+ let offScreenRefs;
41587
+ try {
41588
+ const viewportSize = this.client.getViewportSize();
41589
+ const offScreen = await this.client.evaluate(
41590
+ `() => {
41591
+ const vw = ${viewportSize.width};
41592
+ const vh = ${viewportSize.height};
41593
+ const offscreen = [];
41594
+ const els = document.querySelectorAll('[aria-ref]');
41595
+ for (const el of els) {
41596
+ const ref = el.getAttribute('aria-ref');
41597
+ if (!ref) continue;
41598
+ const r = el.getBoundingClientRect();
41599
+ if (r.width === 0 && r.height === 0) continue;
41600
+ if (r.right < 0 || r.left > vw || r.bottom < 0 || r.top > vh) {
41601
+ offscreen.push(ref);
41602
+ }
41603
+ }
41604
+ return offscreen;
41605
+ }`,
41606
+ { signal: this.signal }
41607
+ );
41608
+ if (typeof offScreen === "string") {
41609
+ try {
41610
+ const parsed = JSON.parse(offScreen);
41611
+ if (Array.isArray(parsed) && parsed.length > 0) {
41612
+ offScreenRefs = new Set(parsed);
41613
+ }
41614
+ } catch {
41615
+ }
41616
+ }
41617
+ } catch (e) {
41618
+ this.logger?.debug?.("[browser_snapshot] Off-screen detection failed", {
41619
+ error: e instanceof Error ? e.message : String(e)
41620
+ });
41621
+ }
41461
41622
  const now = (/* @__PURE__ */ new Date()).toISOString();
41462
41623
  const semanticText = formatSemanticSnapshot(yaml, {
41463
41624
  searchMatches: searchMatches.length > 0 ? searchMatches : void 0,
@@ -41465,7 +41626,8 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41465
41626
  snapshotFilePath,
41466
41627
  probeResult,
41467
41628
  currentTime: now,
41468
- capturedToasts: this.client.getCapturedToasts?.() ?? []
41629
+ capturedToasts: this.client.getCapturedToasts?.() ?? [],
41630
+ offScreenRefs
41469
41631
  });
41470
41632
  if (hasImages) {
41471
41633
  const resolutionNote = buildResolutionNote(
@@ -42179,7 +42341,7 @@ function getBrowserToolDefinitions() {
42179
42341
  },
42180
42342
  toRef: {
42181
42343
  type: "string",
42182
- description: "Scroll the page so this element is centered vertically in the viewport."
42344
+ description: "Scroll the page so this element is centered in the viewport. Handles both vertical and horizontal scrolling automatically \u2014 works for grid columns that are horizontally off-screen."
42183
42345
  },
42184
42346
  x: {
42185
42347
  type: "number",
@@ -46149,17 +46311,42 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
46149
46311
  const locator = await this.resolveRef(toRef);
46150
46312
  const box = await locator.boundingBox();
46151
46313
  if (box) {
46314
+ const viewportSize = this.getViewportSize();
46152
46315
  const elementCenterY = box.y + box.height / 2;
46153
- const viewportHeight = 1080;
46154
- const viewportCenterY = viewportHeight / 2;
46316
+ const viewportCenterY = viewportSize.height / 2;
46155
46317
  const scrollY = elementCenterY - viewportCenterY;
46318
+ const elementCenterX = box.x + box.width / 2;
46319
+ const viewportCenterX = viewportSize.width / 2;
46320
+ const boxRight = box.x + box.width;
46321
+ const needsHorizontalScroll = boxRight < 0 || box.x > viewportSize.width || elementCenterX < 0 || elementCenterX > viewportSize.width;
46322
+ const scrollX = needsHorizontalScroll ? elementCenterX - viewportCenterX : 0;
46156
46323
  this.logger.debug("[DirectPlaywright] Scrolling to center element", {
46157
46324
  toRef,
46158
46325
  elementCenterY,
46159
46326
  viewportCenterY,
46160
- scrollY
46327
+ scrollY,
46328
+ elementCenterX,
46329
+ scrollX,
46330
+ needsHorizontalScroll
46161
46331
  });
46162
- await page.evaluate(`window.scrollBy(0, ${scrollY})`);
46332
+ await page.evaluate(`window.scrollBy(${scrollX}, ${scrollY})`);
46333
+ if (needsHorizontalScroll) {
46334
+ await page.evaluate((ref) => {
46335
+ const el = document.querySelector(`[aria-ref="${ref}"]`);
46336
+ if (!el) return;
46337
+ let parent = el.parentElement;
46338
+ for (let i = 0; i < 10 && parent; i++) {
46339
+ if (parent.scrollWidth > parent.clientWidth) {
46340
+ const elRect = el.getBoundingClientRect();
46341
+ const parentRect = parent.getBoundingClientRect();
46342
+ const targetScrollLeft = parent.scrollLeft + (elRect.left - parentRect.left) - parentRect.width / 2 + elRect.width / 2;
46343
+ parent.scrollLeft = Math.max(0, targetScrollLeft);
46344
+ break;
46345
+ }
46346
+ parent = parent.parentElement;
46347
+ }
46348
+ }, toRef);
46349
+ }
46163
46350
  } else {
46164
46351
  this.logger.warn("[DirectPlaywright] No bounding box, using scrollIntoViewIfNeeded", {
46165
46352
  toRef
@@ -48285,4 +48472,4 @@ playwright-extra/dist/index.esm.js:
48285
48472
  * @license MIT
48286
48473
  *)
48287
48474
  */
48288
- //# sourceMappingURL=chunk-XGO62PO2.js.map
48475
+ //# sourceMappingURL=chunk-MSMC6UXW.js.map