@canaryai/cli 0.2.14 → 0.2.15

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 (48) hide show
  1. package/dist/{chunk-4A4G5KTC.js → chunk-N7L7AH6L.js} +48 -23
  2. package/dist/chunk-N7L7AH6L.js.map +1 -0
  3. package/dist/{chunk-ZQF72UTG.js → chunk-W3OTKNJB.js} +2 -2
  4. package/dist/{chunk-BOS2YLKH.js → chunk-XSOKWWRV.js} +2 -2
  5. package/dist/chunk-XSOKWWRV.js.map +1 -0
  6. package/dist/{chunk-6IAPGYZQ.js → chunk-ZYUOJQQX.js} +298 -2
  7. package/dist/chunk-ZYUOJQQX.js.map +1 -0
  8. package/dist/{debug-workflow-DIQZDFMN.js → debug-workflow-JZKUDSVY.js} +4 -4
  9. package/dist/{docs-CSVSGIGW.js → docs-FORYHZVU.js} +13 -7
  10. package/dist/docs-FORYHZVU.js.map +1 -0
  11. package/dist/{feature-flag-BIPFVVNC.js → feature-flag-7BD3NT6G.js} +2 -2
  12. package/dist/index.js +11 -11
  13. package/dist/{init-BTDX5N6P.js → init-JHZ3OWKQ.js} +7 -6
  14. package/dist/init-JHZ3OWKQ.js.map +1 -0
  15. package/dist/{issues-EWVB52CA.js → issues-37LDBJU7.js} +2 -2
  16. package/dist/{knobs-VYABZESR.js → knobs-QUJQ4NWV.js} +2 -2
  17. package/dist/{list-RCPYLS36.js → list-2L2NGCSF.js} +2 -2
  18. package/dist/{local-ZPVM4BXX.js → local-45POWSFC.js} +5 -5
  19. package/dist/{local-browser-WV4IH2DU.js → local-browser-LHJHPWXU.js} +3 -3
  20. package/dist/{login-W4GXV3VA.js → login-ZKF6GONZ.js} +3 -3
  21. package/dist/{mcp-YER5GQG7.js → mcp-ETZ3OTXY.js} +4 -4
  22. package/dist/{record-KRS2PHMW.js → record-Y4T5FPVF.js} +3 -3
  23. package/dist/{session-CLWAVJ2K.js → session-UPH7SPEU.js} +33 -7
  24. package/dist/session-UPH7SPEU.js.map +1 -0
  25. package/dist/{src-WLOHOI6P.js → src-3VT7JMAG.js} +2 -2
  26. package/dist/{start-CNNQUP5I.js → start-QMTRNSYD.js} +3 -3
  27. package/dist/{workflow-XXL4H5R4.js → workflow-U2NK4YED.js} +2 -2
  28. package/package.json +1 -1
  29. package/dist/chunk-4A4G5KTC.js.map +0 -1
  30. package/dist/chunk-6IAPGYZQ.js.map +0 -1
  31. package/dist/chunk-BOS2YLKH.js.map +0 -1
  32. package/dist/docs-CSVSGIGW.js.map +0 -1
  33. package/dist/init-BTDX5N6P.js.map +0 -1
  34. package/dist/session-CLWAVJ2K.js.map +0 -1
  35. /package/dist/{chunk-ZQF72UTG.js.map → chunk-W3OTKNJB.js.map} +0 -0
  36. /package/dist/{debug-workflow-DIQZDFMN.js.map → debug-workflow-JZKUDSVY.js.map} +0 -0
  37. /package/dist/{feature-flag-BIPFVVNC.js.map → feature-flag-7BD3NT6G.js.map} +0 -0
  38. /package/dist/{issues-EWVB52CA.js.map → issues-37LDBJU7.js.map} +0 -0
  39. /package/dist/{knobs-VYABZESR.js.map → knobs-QUJQ4NWV.js.map} +0 -0
  40. /package/dist/{list-RCPYLS36.js.map → list-2L2NGCSF.js.map} +0 -0
  41. /package/dist/{local-ZPVM4BXX.js.map → local-45POWSFC.js.map} +0 -0
  42. /package/dist/{local-browser-WV4IH2DU.js.map → local-browser-LHJHPWXU.js.map} +0 -0
  43. /package/dist/{login-W4GXV3VA.js.map → login-ZKF6GONZ.js.map} +0 -0
  44. /package/dist/{mcp-YER5GQG7.js.map → mcp-ETZ3OTXY.js.map} +0 -0
  45. /package/dist/{record-KRS2PHMW.js.map → record-Y4T5FPVF.js.map} +0 -0
  46. /package/dist/{src-WLOHOI6P.js.map → src-3VT7JMAG.js.map} +0 -0
  47. /package/dist/{start-CNNQUP5I.js.map → start-QMTRNSYD.js.map} +0 -0
  48. /package/dist/{workflow-XXL4H5R4.js.map → workflow-U2NK4YED.js.map} +0 -0
@@ -26,19 +26,54 @@ function isProcessAlive(pid) {
26
26
  return false;
27
27
  }
28
28
  }
29
- async function daemonFetch(port, method, path2, body) {
29
+ var DAEMON_REQUEST_TIMEOUT_MS = 3e4;
30
+ var DAEMON_FETCH_MAX_RETRIES = 2;
31
+ function isConnectionError(err) {
32
+ if (err instanceof TypeError && err.message === "fetch failed") return true;
33
+ const cause = err?.cause;
34
+ if (cause?.code === "UND_ERR_SOCKET") return true;
35
+ if (cause?.code === "ECONNREFUSED") return true;
36
+ if (cause?.code === "ECONNRESET") return true;
37
+ return false;
38
+ }
39
+ async function daemonFetchOnce(port, method, path2, body) {
30
40
  const url = `http://127.0.0.1:${port}${path2}`;
31
41
  const res = await fetch(url, {
32
42
  method,
33
- headers: body ? { "Content-Type": "application/json" } : void 0,
34
- body: body ? JSON.stringify(body) : void 0
43
+ headers: {
44
+ ...body ? { "Content-Type": "application/json" } : void 0,
45
+ Connection: "close"
46
+ },
47
+ body: body ? JSON.stringify(body) : void 0,
48
+ signal: AbortSignal.timeout(DAEMON_REQUEST_TIMEOUT_MS)
35
49
  });
36
50
  return res.json();
37
51
  }
52
+ async function daemonFetchWithRetry(method, path2, body) {
53
+ let port = await ensureDaemon();
54
+ let lastError;
55
+ for (let attempt = 0; attempt <= DAEMON_FETCH_MAX_RETRIES; attempt++) {
56
+ try {
57
+ return await daemonFetchOnce(port, method, path2, body);
58
+ } catch (err) {
59
+ lastError = err;
60
+ if (!isConnectionError(err) || attempt === DAEMON_FETCH_MAX_RETRIES) {
61
+ throw err;
62
+ }
63
+ try {
64
+ await fs.unlink(PIDFILE_PATH);
65
+ } catch {
66
+ }
67
+ port = await ensureDaemon();
68
+ }
69
+ }
70
+ throw lastError;
71
+ }
38
72
  async function healthCheck(port) {
39
73
  try {
40
74
  const res = await fetch(`http://127.0.0.1:${port}/health`, {
41
- signal: AbortSignal.timeout(2e3)
75
+ signal: AbortSignal.timeout(2e3),
76
+ headers: { Connection: "close" }
42
77
  });
43
78
  return res.ok;
44
79
  } catch {
@@ -113,41 +148,31 @@ async function ensureDaemon() {
113
148
  throw new Error("Daemon failed to become healthy after spawn");
114
149
  }
115
150
  async function createSession(params) {
116
- const port = await ensureDaemon();
117
- return daemonFetch(port, "POST", "/sessions", params);
151
+ return daemonFetchWithRetry("POST", "/sessions", params);
118
152
  }
119
153
  async function listSessions() {
120
- const port = await ensureDaemon();
121
- return daemonFetch(port, "GET", "/sessions");
154
+ return daemonFetchWithRetry("GET", "/sessions");
122
155
  }
123
156
  async function getSession(sessionId) {
124
- const port = await ensureDaemon();
125
- return daemonFetch(port, "GET", `/sessions/${sessionId}`);
157
+ return daemonFetchWithRetry("GET", `/sessions/${sessionId}`);
126
158
  }
127
159
  async function deleteSession(sessionId) {
128
- const port = await ensureDaemon();
129
- return daemonFetch(port, "DELETE", `/sessions/${sessionId}`);
160
+ return daemonFetchWithRetry("DELETE", `/sessions/${sessionId}`);
130
161
  }
131
162
  async function deleteAllSessions() {
132
- const port = await ensureDaemon();
133
- return daemonFetch(port, "DELETE", "/sessions");
163
+ return daemonFetchWithRetry("DELETE", "/sessions");
134
164
  }
135
165
  async function swapSessionContext(sessionId, params) {
136
- const port = await ensureDaemon();
137
- return daemonFetch(port, "POST", `/sessions/${sessionId}/swap-context`, params);
166
+ return daemonFetchWithRetry("POST", `/sessions/${sessionId}/swap-context`, params);
138
167
  }
139
168
  async function getSessionStorageState(sessionId) {
140
- const port = await ensureDaemon();
141
- return daemonFetch(
142
- port,
169
+ return daemonFetchWithRetry(
143
170
  "GET",
144
171
  `/sessions/${sessionId}/storage-state`
145
172
  );
146
173
  }
147
174
  async function callTool(sessionId, toolName, args) {
148
- const port = await ensureDaemon();
149
- return daemonFetch(
150
- port,
175
+ return daemonFetchWithRetry(
151
176
  "POST",
152
177
  `/sessions/${sessionId}/tools/${toolName}`,
153
178
  args
@@ -191,4 +216,4 @@ export {
191
216
  callTool,
192
217
  resolveTargetSession
193
218
  };
194
- //# sourceMappingURL=chunk-4A4G5KTC.js.map
219
+ //# sourceMappingURL=chunk-N7L7AH6L.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\nconst DAEMON_REQUEST_TIMEOUT_MS = 30_000;\nconst DAEMON_FETCH_MAX_RETRIES = 2;\n\n/* ── HTTP helpers ────────────────────────────────────────────────────── */\n\nfunction isConnectionError(err: unknown): boolean {\n if (err instanceof TypeError && err.message === 'fetch failed') return true;\n const cause = (err as { cause?: { code?: string } })?.cause;\n if (cause?.code === 'UND_ERR_SOCKET') return true;\n if (cause?.code === 'ECONNREFUSED') return true;\n if (cause?.code === 'ECONNRESET') return true;\n return false;\n}\n\nasync function daemonFetchOnce(\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: {\n ...(body ? { 'Content-Type': 'application/json' } : undefined),\n Connection: 'close',\n },\n body: body ? JSON.stringify(body) : undefined,\n signal: AbortSignal.timeout(DAEMON_REQUEST_TIMEOUT_MS),\n });\n return res.json();\n}\n\n/**\n * Fetch from daemon with retry. On connection errors (daemon died or\n * idle-exited between ensureDaemon and the request), re-ensure the\n * daemon and retry up to DAEMON_FETCH_MAX_RETRIES times.\n */\nasync function daemonFetchWithRetry(\n method: string,\n path: string,\n body?: unknown\n): Promise<unknown> {\n let port = await ensureDaemon();\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= DAEMON_FETCH_MAX_RETRIES; attempt++) {\n try {\n return await daemonFetchOnce(port, method, path, body);\n } catch (err) {\n lastError = err;\n if (!isConnectionError(err) || attempt === DAEMON_FETCH_MAX_RETRIES) {\n throw err;\n }\n // Daemon likely died — clean up stale pidfile and re-ensure\n try {\n await fs.unlink(PIDFILE_PATH);\n } catch {\n // ignore\n }\n port = await ensureDaemon();\n }\n }\n\n throw lastError;\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 headers: { Connection: 'close' },\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 child.stdout!.destroy(); // Release the pipe so the CLI process can exit\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 */\nasync 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/* ── Session operations ──────────────────────────────────────────────── */\n\nexport async function createSession(\n params: CreateSessionRequest\n): Promise<DaemonResponse<SessionInfo>> {\n return daemonFetchWithRetry('POST', '/sessions', params) as Promise<\n DaemonResponse<SessionInfo>\n >;\n}\n\nexport async function listSessions(): Promise<DaemonResponse<SessionInfo[]>> {\n return daemonFetchWithRetry('GET', '/sessions') as Promise<DaemonResponse<SessionInfo[]>>;\n}\n\nexport async function getSession(sessionId: string): Promise<DaemonResponse<SessionInfo>> {\n return daemonFetchWithRetry('GET', `/sessions/${sessionId}`) as Promise<\n DaemonResponse<SessionInfo>\n >;\n}\n\nexport async function deleteSession(sessionId: string): Promise<DaemonResponse> {\n return daemonFetchWithRetry('DELETE', `/sessions/${sessionId}`) as Promise<DaemonResponse>;\n}\n\nexport async function deleteAllSessions(): Promise<DaemonResponse> {\n return daemonFetchWithRetry('DELETE', '/sessions') as Promise<DaemonResponse>;\n}\n\nexport async function swapSessionContext(\n sessionId: string,\n params: SwapContextRequest\n): Promise<DaemonResponse<SessionInfo>> {\n return daemonFetchWithRetry('POST', `/sessions/${sessionId}/swap-context`, params) as Promise<\n DaemonResponse<SessionInfo>\n >;\n}\n\nexport async function getSessionStorageState(sessionId: string): Promise<StorageStateResponse> {\n return daemonFetchWithRetry(\n 'GET',\n `/sessions/${sessionId}/storage-state`\n ) as Promise<StorageStateResponse>;\n}\n\nexport async function callTool(\n sessionId: string,\n toolName: string,\n args: Record<string, unknown>\n): Promise<ToolResponse> {\n return daemonFetchWithRetry(\n 'POST',\n `/sessions/${sessionId}/tools/${toolName}`,\n args\n ) 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((s) => s.id === sessionIdOrName || s.name === sessionIdOrName);\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"]}
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
2
2
  import {
3
3
  PlaywrightClient
4
- } from "./chunk-6IAPGYZQ.js";
4
+ } from "./chunk-ZYUOJQQX.js";
5
5
 
6
6
  // src/local-browser/host.ts
7
7
  var HEARTBEAT_INTERVAL_MS = 3e4;
@@ -384,4 +384,4 @@ var LocalBrowserHost = class {
384
384
  export {
385
385
  LocalBrowserHost
386
386
  };
387
- //# sourceMappingURL=chunk-ZQF72UTG.js.map
387
+ //# sourceMappingURL=chunk-W3OTKNJB.js.map
@@ -116,7 +116,7 @@ async function selectCredential(apiUrl, token, credentialArg) {
116
116
  return credentials[idx];
117
117
  }
118
118
  async function fetchProperties(apiUrl, token) {
119
- return fetchList(apiUrl, token, "/org/properties", "data");
119
+ return fetchList(apiUrl, token, "/org/properties", "items");
120
120
  }
121
121
  async function selectProperty(apiUrl, token, hint) {
122
122
  const properties = await fetchProperties(apiUrl, token);
@@ -230,4 +230,4 @@ export {
230
230
  uploadStorageState,
231
231
  fetchList
232
232
  };
233
- //# sourceMappingURL=chunk-BOS2YLKH.js.map
233
+ //# sourceMappingURL=chunk-XSOKWWRV.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(\n 'Error: --stage is required and must be one of: active, deprecated, ready_for_cleanup'\n );\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(getCanaryTmpDir(), `${opts.prefix ?? 'canary-ss'}-${Date.now()}.json`);\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>(\n apiUrl,\n token,\n '/org/credentials',\n 'items'\n );\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) => c.id === credentialArg || 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\ninterface PropertyListItem {\n id: string;\n name: string;\n baseUrl: string;\n}\n\nexport async function fetchProperties(apiUrl: string, token: string): Promise<PropertyListItem[]> {\n return fetchList<PropertyListItem>(apiUrl, token, '/org/properties', 'items');\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) => p.id === hint || 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 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"]}
@@ -39388,6 +39388,189 @@ function sortMatchesByContext(matches) {
39388
39388
  });
39389
39389
  }
39390
39390
 
39391
+ // ../browser-core/src/snapshot-formatter-content.ts
39392
+ var MAX_CONTEXT_ANNOTATIONS = 5;
39393
+ var MIN_CONTENT_CARD_RUN = 1;
39394
+ var MAX_CONTENT_CARD_CHILDREN = 6;
39395
+ var MIN_CONTENT_CARD_CHILDREN = 2;
39396
+ function isContextAnnotation(element) {
39397
+ if (element.role !== "paragraph" && element.role !== "generic") return false;
39398
+ if (isClickableByAttribute(element)) return false;
39399
+ if (INTERACTIVE_ROLES.has(element.role)) return false;
39400
+ if (hasInteractiveDescendants(element)) return false;
39401
+ const text = getAnnotationText(element);
39402
+ if (!text || text.length < 3 || text.length > 200) return false;
39403
+ return true;
39404
+ }
39405
+ function getAnnotationText(element) {
39406
+ const direct = getElementText(element);
39407
+ if (direct && direct.trim().length >= 3) return direct.trim();
39408
+ const parts = [];
39409
+ for (const child of element.children) {
39410
+ if (TEXT_CARRYING_ROLES.has(child.role)) {
39411
+ const childText = getElementText(child);
39412
+ if (childText?.trim()) {
39413
+ parts.push(childText.trim());
39414
+ }
39415
+ }
39416
+ }
39417
+ const composed = parts.join(" | ");
39418
+ return composed.length >= 3 ? composed : void 0;
39419
+ }
39420
+ function collectContextAnnotations(siblings) {
39421
+ const annotations = [];
39422
+ for (const sib of siblings) {
39423
+ if (annotations.length >= MAX_CONTEXT_ANNOTATIONS) break;
39424
+ if (extractCardTexts(sib) !== null) continue;
39425
+ if (isContextAnnotation(sib)) {
39426
+ const text = getAnnotationText(sib);
39427
+ if (text) {
39428
+ annotations.push(text);
39429
+ }
39430
+ }
39431
+ }
39432
+ return annotations;
39433
+ }
39434
+ function extractCardTexts(element) {
39435
+ if (isClickableByAttribute(element)) return null;
39436
+ if (INTERACTIVE_ROLES.has(element.role)) return null;
39437
+ if (hasInteractiveDescendants(element)) return null;
39438
+ const texts = [];
39439
+ collectCardTexts(element, texts, 0);
39440
+ if (texts.length < MIN_CONTENT_CARD_CHILDREN || texts.length > MAX_CONTENT_CARD_CHILDREN) {
39441
+ return null;
39442
+ }
39443
+ return texts;
39444
+ }
39445
+ function collectCardTexts(element, texts, depth) {
39446
+ if (depth > 2) return;
39447
+ for (const child of element.children) {
39448
+ if (TEXT_CARRYING_ROLES.has(child.role) || child.role === "listitem") {
39449
+ const text = getElementText(child);
39450
+ if (text && text.trim().length >= 2) {
39451
+ texts.push(text.trim());
39452
+ } else {
39453
+ collectCardTexts(child, texts, depth + 1);
39454
+ }
39455
+ }
39456
+ }
39457
+ }
39458
+ function detectContentCardRun(siblings, startIndex) {
39459
+ const cards = [];
39460
+ let endIndex = startIndex;
39461
+ for (let i = startIndex; i < siblings.length; i++) {
39462
+ const sib = siblings[i];
39463
+ const texts = extractCardTexts(sib);
39464
+ if (!texts) break;
39465
+ cards.push({ ref: sib.ref || "", texts });
39466
+ endIndex = i + 1;
39467
+ }
39468
+ if (cards.length < MIN_CONTENT_CARD_RUN) return null;
39469
+ return { cards, endIndex };
39470
+ }
39471
+ var DAY_ABBREVIATIONS = /* @__PURE__ */ new Set([
39472
+ "S",
39473
+ "M",
39474
+ "T",
39475
+ "W",
39476
+ "F",
39477
+ "Su",
39478
+ "Mo",
39479
+ "Tu",
39480
+ "We",
39481
+ "Th",
39482
+ "Fr",
39483
+ "Sa",
39484
+ "Sun",
39485
+ "Mon",
39486
+ "Tue",
39487
+ "Wed",
39488
+ "Thu",
39489
+ "Fri",
39490
+ "Sat"
39491
+ ]);
39492
+ function detectDatepicker(element) {
39493
+ const kids = element.children;
39494
+ if (kids.length < 35) return null;
39495
+ let headerStart = -1;
39496
+ for (let i = 0; i <= kids.length - 7; i++) {
39497
+ if (isDayHeaderRun(kids, i)) {
39498
+ headerStart = i;
39499
+ break;
39500
+ }
39501
+ }
39502
+ if (headerStart === -1) return null;
39503
+ const dayHeaders = kids.slice(headerStart, headerStart + 7).map((k) => {
39504
+ const text = getElementText(k);
39505
+ return text?.trim() ?? "";
39506
+ });
39507
+ const dayCells = [];
39508
+ for (let i = headerStart + 7; i < kids.length; i++) {
39509
+ const child = kids[i];
39510
+ const text = getElementText(child)?.trim();
39511
+ if (!text) continue;
39512
+ const num = parseInt(text, 10);
39513
+ if (isNaN(num) || num < 1 || num > 31 || String(num) !== text) break;
39514
+ dayCells.push({
39515
+ ref: child.ref || "",
39516
+ dayNumber: num,
39517
+ isSelected: isActiveElement(child) || child.rawLine.includes("[aria-selected=true]")
39518
+ });
39519
+ }
39520
+ if (dayCells.length < 28 || dayCells.length > 42) return null;
39521
+ const weeks = [];
39522
+ for (let i = 0; i < dayCells.length; i += 7) {
39523
+ const weekDays = dayCells.slice(i, i + 7).map((cell) => ({
39524
+ ref: cell.ref,
39525
+ dayNumber: cell.dayNumber,
39526
+ isSelected: cell.isSelected
39527
+ }));
39528
+ weeks.push({ days: weekDays });
39529
+ }
39530
+ return {
39531
+ ref: element.ref || "",
39532
+ dayHeaders,
39533
+ weeks
39534
+ };
39535
+ }
39536
+ function isDayHeaderRun(kids, startIndex) {
39537
+ for (let i = startIndex; i < startIndex + 7; i++) {
39538
+ const child = kids[i];
39539
+ if (isClickableByAttribute(child)) return false;
39540
+ const text = getElementText(child)?.trim();
39541
+ if (!text || !DAY_ABBREVIATIONS.has(text)) return false;
39542
+ }
39543
+ return true;
39544
+ }
39545
+ function formatContextAnnotations(annotations, indent) {
39546
+ const indentStr = " ".repeat(indent + 1);
39547
+ return annotations.map((text) => `${indentStr}context: "${text}"`);
39548
+ }
39549
+ function formatContentCards(cards, indent) {
39550
+ const indentStr = " ".repeat(indent + 1);
39551
+ const lines = [];
39552
+ lines.push(`${indentStr}Content:`);
39553
+ for (const card of cards) {
39554
+ const refPart = card.ref ? `[${card.ref}] ` : "";
39555
+ lines.push(`${indentStr} ${refPart}${card.texts.join(" \xB7 ")}`);
39556
+ }
39557
+ return lines;
39558
+ }
39559
+ function formatDatepicker(info, indent) {
39560
+ const indentStr = " ".repeat(indent + 1);
39561
+ const lines = [];
39562
+ lines.push(`${indentStr}Calendar (${info.dayHeaders.join(" ")}):`);
39563
+ for (let wi = 0; wi < info.weeks.length; wi++) {
39564
+ const week = info.weeks[wi];
39565
+ const dayCells = week.days.map((d) => {
39566
+ const selected = d.isSelected ? "*" : "";
39567
+ return `[${d.ref}]${d.dayNumber}${selected}`;
39568
+ });
39569
+ lines.push(`${indentStr} ${dayCells.join(" ")}`);
39570
+ }
39571
+ return lines;
39572
+ }
39573
+
39391
39574
  // ../browser-core/src/snapshot-formatter-sections.ts
39392
39575
  var FIELD_LABEL_TEXT_ROLES = /* @__PURE__ */ new Set([
39393
39576
  "generic",
@@ -39627,15 +39810,23 @@ function formatElement(element, options) {
39627
39810
  attributes: normalizedAttributes
39628
39811
  };
39629
39812
  }
39813
+ function containsNavigation(section) {
39814
+ for (const sub of section.subsections) {
39815
+ if (sub.role === "navigation") return true;
39816
+ if (containsNavigation(sub)) return true;
39817
+ }
39818
+ return false;
39819
+ }
39630
39820
  function shouldCollapse(section) {
39631
39821
  if (section.containsActive) return false;
39632
39822
  if (section.hasSearchMatch) return false;
39633
- if (section.isBackground) return true;
39634
39823
  if (section.role === "dialog" || section.role === "alertdialog") return false;
39635
39824
  if (section.role === "navigation" || section.role === "menubar" || section.role === "menu")
39636
39825
  return false;
39637
39826
  if (section.role === "banner") return false;
39638
39827
  if (section.role === "form") return false;
39828
+ if (containsNavigation(section)) return false;
39829
+ if (section.isBackground) return true;
39639
39830
  if (section.elementCount >= MIN_ELEMENTS_TO_COLLAPSE) return true;
39640
39831
  return false;
39641
39832
  }
@@ -39713,6 +39904,50 @@ function collectInteractiveElements(element, elements, subsections, depth, inher
39713
39904
  return;
39714
39905
  }
39715
39906
  const kids = element.children;
39907
+ const datepickerResult = detectDatepicker(element);
39908
+ if (datepickerResult) {
39909
+ const annotations = collectContextAnnotations(kids);
39910
+ subsections.push({
39911
+ heading: "Calendar",
39912
+ ref: datepickerResult.ref,
39913
+ role: "generic",
39914
+ level: 4,
39915
+ elements: [],
39916
+ subsections: [],
39917
+ collapsed: false,
39918
+ containsActive: false,
39919
+ isBackground: false,
39920
+ isFocused: false,
39921
+ elementCount: datepickerResult.weeks.reduce((sum, w) => sum + w.days.length, 0),
39922
+ datepickerInfo: datepickerResult,
39923
+ contextAnnotations: annotations.length > 0 ? annotations : void 0
39924
+ });
39925
+ return;
39926
+ }
39927
+ const cardRun = detectContentCardRun(kids, 0);
39928
+ if (cardRun) {
39929
+ const annotations = collectContextAnnotations(kids);
39930
+ subsections.push({
39931
+ heading: "Content",
39932
+ ref: element.ref || "",
39933
+ role: "generic",
39934
+ level: 4,
39935
+ elements: [],
39936
+ subsections: [],
39937
+ collapsed: false,
39938
+ containsActive: false,
39939
+ isBackground: false,
39940
+ isFocused: false,
39941
+ elementCount: cardRun.cards.length,
39942
+ contentCards: cardRun.cards,
39943
+ contextAnnotations: annotations.length > 0 ? annotations : void 0
39944
+ });
39945
+ for (let ki = cardRun.endIndex; ki < kids.length; ki++) {
39946
+ const child = kids[ki];
39947
+ collectInteractiveElements(child, elements, subsections, depth + 1, inheritedFieldLabel);
39948
+ }
39949
+ return;
39950
+ }
39716
39951
  for (let ki = 0; ki < kids.length; ki++) {
39717
39952
  const child = kids[ki];
39718
39953
  if (isVisualHeading(child, kids, ki)) {
@@ -39771,6 +40006,44 @@ function buildSection(element, depth) {
39771
40006
  if (gridInfo) {
39772
40007
  return buildGridSection(element);
39773
40008
  }
40009
+ const datepickerResult = detectDatepicker(element);
40010
+ if (datepickerResult) {
40011
+ const annotations = collectContextAnnotations(element.children);
40012
+ return {
40013
+ ref: element.ref || "",
40014
+ role: element.role,
40015
+ heading: element.text || "Calendar",
40016
+ level: 0,
40017
+ elements: [],
40018
+ subsections: [],
40019
+ containsActive: false,
40020
+ collapsed: false,
40021
+ isBackground: false,
40022
+ isFocused: false,
40023
+ elementCount: datepickerResult.weeks.reduce((sum, w) => sum + w.days.length, 0),
40024
+ datepickerInfo: datepickerResult,
40025
+ contextAnnotations: annotations.length > 0 ? annotations : void 0
40026
+ };
40027
+ }
40028
+ const cardRun = detectContentCardRun(element.children, 0);
40029
+ if (cardRun) {
40030
+ const annotations = collectContextAnnotations(element.children);
40031
+ return {
40032
+ ref: element.ref || "",
40033
+ role: element.role,
40034
+ heading: element.text || "Content",
40035
+ level: 0,
40036
+ elements: [],
40037
+ subsections: [],
40038
+ containsActive: false,
40039
+ collapsed: false,
40040
+ isBackground: false,
40041
+ isFocused: false,
40042
+ elementCount: cardRun.cards.length,
40043
+ contentCards: cardRun.cards,
40044
+ contextAnnotations: annotations.length > 0 ? annotations : void 0
40045
+ };
40046
+ }
39774
40047
  const isHeading = element.role === "heading";
39775
40048
  const isStructural = STRUCTURAL_ROLES.has(element.role);
39776
40049
  if (isHeading && !element.text?.trim() && element.children.length === 0) {
@@ -40098,6 +40371,26 @@ function formatSectionOutput(section, indent, showCollapsed) {
40098
40371
  }
40099
40372
  const indentStr = " ".repeat(indent);
40100
40373
  const lines = [];
40374
+ if (section.datepickerInfo) {
40375
+ const headingPrefix2 = section.level > 0 ? "#".repeat(Math.min(section.level + 2, 6)) + " " : "";
40376
+ const refPart2 = section.ref ? ` [${section.ref}]` : "";
40377
+ lines.push(`${indentStr}${headingPrefix2}${section.heading}${refPart2}`);
40378
+ if (section.contextAnnotations) {
40379
+ lines.push(...formatContextAnnotations(section.contextAnnotations, indent));
40380
+ }
40381
+ lines.push(...formatDatepicker(section.datepickerInfo, indent));
40382
+ return lines.join("\n");
40383
+ }
40384
+ if (section.contentCards && section.contentCards.length > 0) {
40385
+ const headingPrefix2 = section.level > 0 ? "#".repeat(Math.min(section.level + 2, 6)) + " " : "";
40386
+ const refPart2 = section.ref ? ` [${section.ref}]` : "";
40387
+ lines.push(`${indentStr}${headingPrefix2}${section.heading}${refPart2}`);
40388
+ if (section.contextAnnotations) {
40389
+ lines.push(...formatContextAnnotations(section.contextAnnotations, indent));
40390
+ }
40391
+ lines.push(...formatContentCards(section.contentCards, indent));
40392
+ return lines.join("\n");
40393
+ }
40101
40394
  const headingPrefix = section.level > 0 ? "#".repeat(Math.min(section.level + 2, 6)) + " " : "";
40102
40395
  const refPart = section.ref ? ` [${section.ref}]` : "";
40103
40396
  const focusedPart = section.isFocused ? " [FOCUSED]" : "";
@@ -40106,6 +40399,9 @@ function formatSectionOutput(section, indent, showCollapsed) {
40106
40399
  lines.push(
40107
40400
  `${indentStr}${headingPrefix}${section.heading}${refPart}${focusedPart}${backgroundPart}${activePart}`
40108
40401
  );
40402
+ if (section.contextAnnotations) {
40403
+ lines.push(...formatContextAnnotations(section.contextAnnotations, indent));
40404
+ }
40109
40405
  if (section.collapsed && !showCollapsed) {
40110
40406
  if (section.isBackground) {
40111
40407
  if (section.ref) {
@@ -49243,4 +49539,4 @@ playwright-extra/dist/index.esm.js:
49243
49539
  * @license MIT
49244
49540
  *)
49245
49541
  */
49246
- //# sourceMappingURL=chunk-6IAPGYZQ.js.map
49542
+ //# sourceMappingURL=chunk-ZYUOJQQX.js.map