@canaryai/cli 0.2.1 → 0.2.3

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 (40) hide show
  1. package/dist/{chunk-TO66FC4R.js → chunk-JMI7WWPF.js} +2 -2
  2. package/dist/chunk-OE6O7H45.js +92 -0
  3. package/dist/chunk-OE6O7H45.js.map +1 -0
  4. package/dist/chunk-PLDDJCW6.js +49 -0
  5. package/dist/chunk-ZTWHPIXU.js +42798 -0
  6. package/dist/chunk-ZTWHPIXU.js.map +1 -0
  7. package/dist/debug-workflow-PMLMWKWI.js +241 -0
  8. package/dist/debug-workflow-PMLMWKWI.js.map +1 -0
  9. package/dist/dist-6NXLJYRZ.js +335 -0
  10. package/dist/dist-6NXLJYRZ.js.map +1 -0
  11. package/dist/{feature-flag-ZDLDYRSF.js → feature-flag-D3QTHGL6.js} +2 -2
  12. package/dist/index.js +41 -9
  13. package/dist/index.js.map +1 -1
  14. package/dist/issues-6MHRFKTU.js +362 -0
  15. package/dist/issues-6MHRFKTU.js.map +1 -0
  16. package/dist/{knobs-3MKMOXIV.js → knobs-ED6LXBVM.js} +2 -2
  17. package/dist/{local-browser-GG5GUXDS.js → local-browser-YSE3XCUW.js} +4 -3
  18. package/dist/{local-browser-GG5GUXDS.js.map → local-browser-YSE3XCUW.js.map} +1 -1
  19. package/dist/{mcp-AD67OLQM.js → mcp-LKHFYMA6.js} +4 -3
  20. package/dist/{mcp-AD67OLQM.js.map → mcp-LKHFYMA6.js.map} +1 -1
  21. package/dist/pdf-extract-YIDRKYUD.js +12 -0
  22. package/dist/pdf-extract-YIDRKYUD.js.map +1 -0
  23. package/dist/pdfjs-44AOKLEM.js +35242 -0
  24. package/dist/pdfjs-44AOKLEM.js.map +1 -0
  25. package/dist/{psql-IVAPNYZV.js → psql-U5LF6ELS.js} +2 -2
  26. package/dist/{redis-LWY7L6AS.js → redis-PBQZGU6T.js} +2 -2
  27. package/dist/{release-KQFCTAXA.js → release-QBSP474D.js} +2 -2
  28. package/dist/runner/preload.js +2 -2
  29. package/dist/test.js +2 -2
  30. package/package.json +2 -2
  31. package/dist/chunk-DGUM43GV.js +0 -11
  32. package/dist/chunk-UEOXNF5X.js +0 -371
  33. package/dist/chunk-UEOXNF5X.js.map +0 -1
  34. /package/dist/{chunk-TO66FC4R.js.map → chunk-JMI7WWPF.js.map} +0 -0
  35. /package/dist/{chunk-DGUM43GV.js.map → chunk-PLDDJCW6.js.map} +0 -0
  36. /package/dist/{feature-flag-ZDLDYRSF.js.map → feature-flag-D3QTHGL6.js.map} +0 -0
  37. /package/dist/{knobs-3MKMOXIV.js.map → knobs-ED6LXBVM.js.map} +0 -0
  38. /package/dist/{psql-IVAPNYZV.js.map → psql-U5LF6ELS.js.map} +0 -0
  39. /package/dist/{redis-LWY7L6AS.js.map → redis-PBQZGU6T.js.map} +0 -0
  40. /package/dist/{release-KQFCTAXA.js.map → release-QBSP474D.js.map} +0 -0
@@ -0,0 +1,362 @@
1
+ import {
2
+ apiRequest
3
+ } from "./chunk-DXJNFJ3A.js";
4
+ import {
5
+ getArgValue,
6
+ hasFlag,
7
+ resolveConfig
8
+ } from "./chunk-7R4YFGP6.js";
9
+ import "./chunk-PLDDJCW6.js";
10
+
11
+ // src/issues.ts
12
+ import process from "process";
13
+ var SEVERITY_ICONS = {
14
+ high: "!!!",
15
+ medium: "!!",
16
+ low: "!",
17
+ unknown: "?"
18
+ };
19
+ var DIAGNOSTIC_CATEGORY_LABELS = {
20
+ software_bug: "Software Bug",
21
+ dependency_issue: "Dependency Issue",
22
+ agent_confusion: "Agent Confusion"
23
+ };
24
+ function formatStatusLabel(status) {
25
+ switch (status) {
26
+ case "open":
27
+ return "Open";
28
+ case "closed":
29
+ return "Closed";
30
+ case "not_a_bug":
31
+ return "Not a Bug";
32
+ default:
33
+ return status;
34
+ }
35
+ }
36
+ function formatSeverity(severity) {
37
+ return severity.charAt(0).toUpperCase() + severity.slice(1);
38
+ }
39
+ function formatRelative(value) {
40
+ if (!value) return "\u2014";
41
+ try {
42
+ const date = new Date(value);
43
+ const now = /* @__PURE__ */ new Date();
44
+ const diffMs = now.getTime() - date.getTime();
45
+ const diffMins = Math.floor(diffMs / 6e4);
46
+ if (diffMins < 1) return "just now";
47
+ if (diffMins < 60) return `${diffMins}m ago`;
48
+ const diffHours = Math.floor(diffMins / 60);
49
+ if (diffHours < 24) return `${diffHours}h ago`;
50
+ const diffDays = Math.floor(diffHours / 24);
51
+ if (diffDays < 30) return `${diffDays}d ago`;
52
+ return date.toISOString().slice(0, 10);
53
+ } catch {
54
+ return value;
55
+ }
56
+ }
57
+ function truncate(text, max) {
58
+ if (text.length <= max) return text;
59
+ return text.slice(0, max - 1) + "\u2026";
60
+ }
61
+ function formatIssueDetailMarkdown(issue, consoleErrors, networkErrors) {
62
+ const sections = [];
63
+ const occ = issue.latestOccurrence;
64
+ sections.push(`# ${issue.title}`);
65
+ const meta = [];
66
+ meta.push(`**Status:** ${formatStatusLabel(issue.status)}`);
67
+ meta.push(`**Severity:** ${formatSeverity(issue.severity)}`);
68
+ meta.push(`**Category:** ${issue.category.label}`);
69
+ sections.push(meta.join(" | "));
70
+ if (occ?.primaryUrl) {
71
+ sections.push(`**URL:** ${occ.primaryUrl}`);
72
+ }
73
+ const timeParts = [];
74
+ timeParts.push(`**First seen:** ${formatRelative(issue.firstSeenAt)}`);
75
+ timeParts.push(`**Last seen:** ${formatRelative(issue.lastSeenAt)}`);
76
+ timeParts.push(`**Occurrences:** ${issue.occurrenceCount}`);
77
+ sections.push(timeParts.join(" | "));
78
+ const summary = occ?.ticketUserFacingSummary?.trim();
79
+ if (summary) {
80
+ sections.push(`## Summary
81
+ ${summary}`);
82
+ }
83
+ const reproSteps = occ?.ticketReproSteps?.trim();
84
+ if (reproSteps) {
85
+ sections.push(`## Reproduction Steps
86
+ ${reproSteps}`);
87
+ }
88
+ const diagnostic = occ?.diagnosticJson;
89
+ if (diagnostic) {
90
+ const diagParts = [];
91
+ const categoryLabel = DIAGNOSTIC_CATEGORY_LABELS[diagnostic.category] ?? diagnostic.category;
92
+ diagParts.push("## Diagnostic Analysis");
93
+ diagParts.push(`**Category:** ${categoryLabel} (${Math.round(diagnostic.confidence * 100)}% confidence)`);
94
+ diagParts.push(`**${diagnostic.title}**`);
95
+ diagParts.push(diagnostic.analysis);
96
+ if (diagnostic.smokingGun) {
97
+ const gun = diagnostic.smokingGun;
98
+ const gunLines = [`> **Key Evidence:** ${gun.description}`];
99
+ if (gun.detail) {
100
+ gunLines.push(`> \`${gun.detail}\``);
101
+ }
102
+ diagParts.push(gunLines.join("\n"));
103
+ }
104
+ diagParts.push(`**Recommendation:** ${diagnostic.recommendation}`);
105
+ sections.push(diagParts.join("\n\n"));
106
+ }
107
+ if (consoleErrors.length > 0) {
108
+ const logLines = consoleErrors.map((entry) => {
109
+ const loc = entry.location ? ` (${entry.location})` : "";
110
+ return `[${entry.type}] ${entry.text}${loc}`;
111
+ });
112
+ sections.push(`## Console Errors
113
+ \`\`\`
114
+ ${logLines.join("\n")}
115
+ \`\`\``);
116
+ }
117
+ if (networkErrors.length > 0) {
118
+ const rows = networkErrors.map((entry) => {
119
+ const status = entry.status === null || entry.status === 0 ? "0 (failed)" : String(entry.status);
120
+ const duration = entry.durationMs != null ? `${entry.durationMs}ms` : "\u2014";
121
+ return `| ${entry.method} | ${entry.url} | ${status} | ${duration} |`;
122
+ });
123
+ sections.push(
124
+ `## Network Errors
125
+ | Method | URL | Status | Duration |
126
+ |--------|-----|--------|----------|
127
+ ${rows.join("\n")}`
128
+ );
129
+ }
130
+ return sections.join("\n\n");
131
+ }
132
+ function formatIssueListMarkdown(data, pagination) {
133
+ const sections = [];
134
+ sections.push(`# Issues (page ${pagination.page} of ${pagination.totalPages}, ${pagination.totalItems} total)`);
135
+ if (data.length === 0) {
136
+ sections.push("No issues found.");
137
+ return sections.join("\n\n");
138
+ }
139
+ const header = "| Severity | Title | Status | Last Seen | Occurrences |";
140
+ const divider = "|----------|-------|--------|-----------|-------------|";
141
+ const rows = data.map(
142
+ (issue) => `| ${issue.severity} | ${truncate(issue.title, 60)} | ${issue.status} | ${formatRelative(issue.lastSeenAt)} | ${issue.occurrenceCount} |`
143
+ );
144
+ sections.push([header, divider, ...rows].join("\n"));
145
+ return sections.join("\n\n");
146
+ }
147
+ async function fetchDiagnostics(apiUrl, token, issue) {
148
+ const occ = issue.latestOccurrence;
149
+ if (!occ) return { consoleErrors: [], networkErrors: [] };
150
+ let consoleErrors = [];
151
+ let networkErrors = [];
152
+ try {
153
+ const [consoleRes, networkRes] = await Promise.all([
154
+ apiRequest(
155
+ apiUrl,
156
+ token,
157
+ "GET",
158
+ `/v2/issues/${issue.id}/occurrences/${occ.id}/console-logs`
159
+ ),
160
+ apiRequest(
161
+ apiUrl,
162
+ token,
163
+ "GET",
164
+ `/v2/issues/${issue.id}/occurrences/${occ.id}/network-requests`
165
+ )
166
+ ]);
167
+ if (consoleRes.ok && Array.isArray(consoleRes.data)) {
168
+ consoleErrors = consoleRes.data.filter((e) => e.type === "error");
169
+ }
170
+ if (networkRes.ok && Array.isArray(networkRes.data)) {
171
+ networkErrors = networkRes.data.filter(
172
+ (e) => e.status === null || e.status === 0 || e.status >= 400
173
+ );
174
+ }
175
+ } catch {
176
+ }
177
+ if (consoleErrors.length === 0 && occ.consoleLogExcerpt?.trim()) {
178
+ consoleErrors = [{ type: "error", text: occ.consoleLogExcerpt.trim(), timestamp: null, location: null }];
179
+ }
180
+ if (networkErrors.length === 0 && occ.networkFailureUrl) {
181
+ networkErrors = [{
182
+ url: occ.networkFailureUrl,
183
+ method: "GET",
184
+ status: occ.networkFailureStatus,
185
+ durationMs: null
186
+ }];
187
+ }
188
+ return { consoleErrors, networkErrors };
189
+ }
190
+ async function handleList(argv, apiUrl, token) {
191
+ const jsonOutput = hasFlag(argv, "--json");
192
+ const markdownOutput = getArgValue(argv, "--format") === "markdown";
193
+ const params = new URLSearchParams();
194
+ const search = getArgValue(argv, "--search");
195
+ const severity = getArgValue(argv, "--severity");
196
+ const status = getArgValue(argv, "--status");
197
+ const propertyId = getArgValue(argv, "--property-id");
198
+ const page = getArgValue(argv, "--page");
199
+ const pageSize = getArgValue(argv, "--page-size");
200
+ if (search) params.set("search", search);
201
+ if (severity) params.set("severity", severity);
202
+ if (status) params.set("statuses", status);
203
+ if (propertyId) params.set("propertyId", propertyId);
204
+ if (page) params.set("page", page);
205
+ if (pageSize) params.set("pageSize", pageSize);
206
+ const qs = params.toString();
207
+ const path = `/v2/issues${qs ? `?${qs}` : ""}`;
208
+ const result = await apiRequest(apiUrl, token, "GET", path);
209
+ if (!result.ok) {
210
+ console.error(`Error: ${result.error}`);
211
+ process.exit(1);
212
+ }
213
+ if (jsonOutput) {
214
+ console.log(JSON.stringify({ data: result.data, pagination: result.pagination }, null, 2));
215
+ return;
216
+ }
217
+ if (markdownOutput) {
218
+ console.log(formatIssueListMarkdown(result.data, result.pagination));
219
+ return;
220
+ }
221
+ const { data, pagination } = result;
222
+ console.log(`Issues: ${pagination.totalItems} total (page ${pagination.page}/${pagination.totalPages})
223
+ `);
224
+ if (data.length === 0) {
225
+ console.log("No issues found.");
226
+ return;
227
+ }
228
+ for (const issue of data) {
229
+ const icon = SEVERITY_ICONS[issue.severity] ?? "?";
230
+ const statusLabel = formatStatusLabel(issue.status);
231
+ const lastSeen = formatRelative(issue.lastSeenAt);
232
+ console.log(` [${icon}] ${truncate(issue.title, 60)} ${statusLabel} ${lastSeen} (${issue.occurrenceCount}x) ${issue.id}`);
233
+ }
234
+ }
235
+ async function handleGet(argv, apiUrl, token) {
236
+ const issueId = argv[0];
237
+ if (!issueId || issueId.startsWith("--")) {
238
+ console.error("Error: Missing issue ID.");
239
+ console.error("Usage: canary issues get <issueId>");
240
+ process.exit(1);
241
+ }
242
+ const jsonOutput = hasFlag(argv, "--json");
243
+ const markdownOutput = getArgValue(argv, "--format") === "markdown";
244
+ const result = await apiRequest(apiUrl, token, "GET", `/v2/issues/${issueId}`);
245
+ if (!result.ok) {
246
+ console.error(`Error: ${result.error}`);
247
+ process.exit(1);
248
+ }
249
+ const { issue } = result.data;
250
+ const { consoleErrors, networkErrors } = await fetchDiagnostics(apiUrl, token, issue);
251
+ if (jsonOutput) {
252
+ console.log(JSON.stringify({ ...result.data, consoleErrors, networkErrors }, null, 2));
253
+ return;
254
+ }
255
+ if (markdownOutput) {
256
+ console.log(formatIssueDetailMarkdown(issue, consoleErrors, networkErrors));
257
+ return;
258
+ }
259
+ const occ = issue.latestOccurrence;
260
+ console.log(` Title: ${issue.title}`);
261
+ console.log(` ID: ${issue.id}`);
262
+ console.log(` Status: ${formatStatusLabel(issue.status)}`);
263
+ console.log(` Severity: ${formatSeverity(issue.severity)}`);
264
+ console.log(` Category: ${issue.category.label}`);
265
+ console.log(` Occurrences: ${issue.occurrenceCount}`);
266
+ console.log(` First seen: ${formatRelative(issue.firstSeenAt)}`);
267
+ console.log(` Last seen: ${formatRelative(issue.lastSeenAt)}`);
268
+ if (occ?.primaryUrl) {
269
+ console.log(` URL: ${occ.primaryUrl}`);
270
+ }
271
+ if (issue.assignedTo) {
272
+ console.log(` Assigned to: ${issue.assignedTo.displayName ?? issue.assignedTo.primaryEmail ?? issue.assignedTo.id}`);
273
+ }
274
+ const summary = occ?.ticketUserFacingSummary?.trim();
275
+ if (summary) {
276
+ console.log(`
277
+ Summary:
278
+ ${summary.split("\n").join("\n ")}`);
279
+ }
280
+ const diagnostic = occ?.diagnosticJson;
281
+ if (diagnostic) {
282
+ const categoryLabel = DIAGNOSTIC_CATEGORY_LABELS[diagnostic.category] ?? diagnostic.category;
283
+ console.log(`
284
+ Diagnostic: ${categoryLabel} (${Math.round(diagnostic.confidence * 100)}%)`);
285
+ console.log(` ${diagnostic.title}`);
286
+ console.log(` Recommendation: ${diagnostic.recommendation}`);
287
+ }
288
+ if (consoleErrors.length > 0) {
289
+ console.log(`
290
+ Console Errors (${consoleErrors.length}):`);
291
+ for (const entry of consoleErrors.slice(0, 5)) {
292
+ const loc = entry.location ? ` (${entry.location})` : "";
293
+ console.log(` [${entry.type}] ${truncate(entry.text, 100)}${loc}`);
294
+ }
295
+ if (consoleErrors.length > 5) {
296
+ console.log(` ... and ${consoleErrors.length - 5} more`);
297
+ }
298
+ }
299
+ if (networkErrors.length > 0) {
300
+ console.log(`
301
+ Network Errors (${networkErrors.length}):`);
302
+ for (const entry of networkErrors.slice(0, 5)) {
303
+ const status = entry.status === null || entry.status === 0 ? "failed" : String(entry.status);
304
+ console.log(` ${entry.method} ${truncate(entry.url, 80)} -> ${status}`);
305
+ }
306
+ if (networkErrors.length > 5) {
307
+ console.log(` ... and ${networkErrors.length - 5} more`);
308
+ }
309
+ }
310
+ }
311
+ function printIssuesHelp() {
312
+ console.log(
313
+ [
314
+ "Usage: canary issues <sub-command> [options]",
315
+ "",
316
+ "Sub-commands:",
317
+ " list [options] List and search issues",
318
+ " get <issueId> [options] Get issue detail with diagnostics",
319
+ "",
320
+ "List options:",
321
+ " --search <query> Full-text search",
322
+ " --severity <level> Filter: low, medium, high, unknown",
323
+ " --status <statuses> Filter: open, closed, not_a_bug (comma-separated)",
324
+ " --property-id <uuid> Filter by property",
325
+ " --page <n> Page number (default: 1)",
326
+ " --page-size <n> Page size (default: 25)",
327
+ "",
328
+ "Output options:",
329
+ " --json Output raw JSON",
330
+ " --format markdown Output as markdown",
331
+ "",
332
+ "Common options:",
333
+ " --env <env> Target environment (prod, dev, local)",
334
+ " --api-url <url> API URL override (takes precedence over --env)",
335
+ " --token <key> API token override"
336
+ ].join("\n")
337
+ );
338
+ }
339
+ async function runIssues(argv) {
340
+ const [subCommand, ...rest] = argv;
341
+ if (!subCommand || subCommand === "help" || hasFlag(argv, "--help", "-h")) {
342
+ printIssuesHelp();
343
+ return;
344
+ }
345
+ const { apiUrl, token } = await resolveConfig(argv);
346
+ switch (subCommand) {
347
+ case "list":
348
+ await handleList(rest, apiUrl, token);
349
+ break;
350
+ case "get":
351
+ await handleGet(rest, apiUrl, token);
352
+ break;
353
+ default:
354
+ console.error(`Unknown sub-command: ${subCommand}`);
355
+ printIssuesHelp();
356
+ process.exit(1);
357
+ }
358
+ }
359
+ export {
360
+ runIssues
361
+ };
362
+ //# sourceMappingURL=issues-6MHRFKTU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/issues.ts"],"sourcesContent":["/**\n * CLI Issues Management\n *\n * Search, list, and view issues with full diagnostics from the terminal.\n */\n\nimport process from \"node:process\";\nimport { resolveConfig, getArgValue, hasFlag } from \"./auth.js\";\nimport { apiRequest } from \"./cli-helpers.js\";\n\n/* ── Types ────────────────────────────────────────────────────────────── */\n\ntype IssueCategorySummary = {\n id: string;\n key: string;\n label: string;\n description: string | null;\n instructions: string | null;\n};\n\ntype IssueAssignee = {\n id: string;\n displayName: string | null;\n primaryEmail: string | null;\n};\n\ntype FailureDiagnostic = {\n category: string;\n confidence: number;\n title: string;\n analysis: string;\n recommendation: string;\n smokingGun?: { description: string; detail?: string } | null;\n};\n\ntype IssueOccurrence = {\n id: string;\n primaryUrl: string;\n ticketUserFacingSummary: string | null;\n ticketReproSteps: string | null;\n diagnosticJson: FailureDiagnostic | null;\n consoleLogExcerpt: string | null;\n networkFailureUrl: string | null;\n networkFailureStatus: number | null;\n};\n\ntype IssueSummary = {\n id: string;\n title: string;\n severity: \"low\" | \"medium\" | \"high\" | \"unknown\";\n status: \"open\" | \"closed\" | \"not_a_bug\";\n category: IssueCategorySummary;\n firstSeenAt: string;\n lastSeenAt: string;\n occurrenceCount: number;\n assignedTo: IssueAssignee | null;\n latestOccurrence: IssueOccurrence | null;\n};\n\ntype ConsoleLogEntry = {\n type: string;\n text: string;\n timestamp: number | null;\n location: string | null;\n};\n\ntype NetworkRequestEntry = {\n url: string;\n method: string;\n status: number | null;\n durationMs: number | null;\n};\n\ntype Pagination = {\n page: number;\n pageSize: number;\n totalItems: number;\n totalPages: number;\n};\n\ntype IssueListResponse = {\n ok: boolean;\n error?: string;\n data: IssueSummary[];\n pagination: Pagination;\n};\n\ntype IssueDetailResponse = {\n ok: boolean;\n error?: string;\n data: {\n issue: IssueSummary;\n occurrences: IssueOccurrence[];\n commentCount: number;\n };\n};\n\ntype DiagnosticsDataResponse = {\n ok: boolean;\n data: ConsoleLogEntry[] | NetworkRequestEntry[];\n};\n\n/* ── Formatting Helpers ───────────────────────────────────────────────── */\n\nconst SEVERITY_ICONS: Record<string, string> = {\n high: \"!!!\",\n medium: \"!!\",\n low: \"!\",\n unknown: \"?\",\n};\n\nconst DIAGNOSTIC_CATEGORY_LABELS: Record<string, string> = {\n software_bug: \"Software Bug\",\n dependency_issue: \"Dependency Issue\",\n agent_confusion: \"Agent Confusion\",\n};\n\nfunction formatStatusLabel(status: string): string {\n switch (status) {\n case \"open\": return \"Open\";\n case \"closed\": return \"Closed\";\n case \"not_a_bug\": return \"Not a Bug\";\n default: return status;\n }\n}\n\nfunction formatSeverity(severity: string): string {\n return severity.charAt(0).toUpperCase() + severity.slice(1);\n}\n\nfunction formatRelative(value: string | null): string {\n if (!value) return \"—\";\n try {\n const date = new Date(value);\n const now = new Date();\n const diffMs = now.getTime() - date.getTime();\n const diffMins = Math.floor(diffMs / 60000);\n if (diffMins < 1) return \"just now\";\n if (diffMins < 60) return `${diffMins}m ago`;\n const diffHours = Math.floor(diffMins / 60);\n if (diffHours < 24) return `${diffHours}h ago`;\n const diffDays = Math.floor(diffHours / 24);\n if (diffDays < 30) return `${diffDays}d ago`;\n return date.toISOString().slice(0, 10);\n } catch {\n return value;\n }\n}\n\nfunction truncate(text: string, max: number): string {\n if (text.length <= max) return text;\n return text.slice(0, max - 1) + \"…\";\n}\n\n/* ── Markdown Formatters ──────────────────────────────────────────────── */\n\nfunction formatIssueDetailMarkdown(\n issue: IssueSummary,\n consoleErrors: ConsoleLogEntry[],\n networkErrors: NetworkRequestEntry[]\n): string {\n const sections: string[] = [];\n const occ = issue.latestOccurrence;\n\n sections.push(`# ${issue.title}`);\n\n const meta: string[] = [];\n meta.push(`**Status:** ${formatStatusLabel(issue.status)}`);\n meta.push(`**Severity:** ${formatSeverity(issue.severity)}`);\n meta.push(`**Category:** ${issue.category.label}`);\n sections.push(meta.join(\" | \"));\n\n if (occ?.primaryUrl) {\n sections.push(`**URL:** ${occ.primaryUrl}`);\n }\n\n const timeParts: string[] = [];\n timeParts.push(`**First seen:** ${formatRelative(issue.firstSeenAt)}`);\n timeParts.push(`**Last seen:** ${formatRelative(issue.lastSeenAt)}`);\n timeParts.push(`**Occurrences:** ${issue.occurrenceCount}`);\n sections.push(timeParts.join(\" | \"));\n\n const summary = occ?.ticketUserFacingSummary?.trim();\n if (summary) {\n sections.push(`## Summary\\n${summary}`);\n }\n\n const reproSteps = occ?.ticketReproSteps?.trim();\n if (reproSteps) {\n sections.push(`## Reproduction Steps\\n${reproSteps}`);\n }\n\n const diagnostic = occ?.diagnosticJson;\n if (diagnostic) {\n const diagParts: string[] = [];\n const categoryLabel = DIAGNOSTIC_CATEGORY_LABELS[diagnostic.category] ?? diagnostic.category;\n diagParts.push(\"## Diagnostic Analysis\");\n diagParts.push(`**Category:** ${categoryLabel} (${Math.round(diagnostic.confidence * 100)}% confidence)`);\n diagParts.push(`**${diagnostic.title}**`);\n diagParts.push(diagnostic.analysis);\n\n if (diagnostic.smokingGun) {\n const gun = diagnostic.smokingGun;\n const gunLines = [`> **Key Evidence:** ${gun.description}`];\n if (gun.detail) {\n gunLines.push(`> \\`${gun.detail}\\``);\n }\n diagParts.push(gunLines.join(\"\\n\"));\n }\n\n diagParts.push(`**Recommendation:** ${diagnostic.recommendation}`);\n sections.push(diagParts.join(\"\\n\\n\"));\n }\n\n if (consoleErrors.length > 0) {\n const logLines = consoleErrors.map((entry) => {\n const loc = entry.location ? ` (${entry.location})` : \"\";\n return `[${entry.type}] ${entry.text}${loc}`;\n });\n sections.push(`## Console Errors\\n\\`\\`\\`\\n${logLines.join(\"\\n\")}\\n\\`\\`\\``);\n }\n\n if (networkErrors.length > 0) {\n const rows = networkErrors.map((entry) => {\n const status =\n entry.status === null || entry.status === 0\n ? \"0 (failed)\"\n : String(entry.status);\n const duration = entry.durationMs != null ? `${entry.durationMs}ms` : \"—\";\n return `| ${entry.method} | ${entry.url} | ${status} | ${duration} |`;\n });\n sections.push(\n `## Network Errors\\n| Method | URL | Status | Duration |\\n|--------|-----|--------|----------|\\n${rows.join(\"\\n\")}`\n );\n }\n\n return sections.join(\"\\n\\n\");\n}\n\nfunction formatIssueListMarkdown(data: IssueSummary[], pagination: Pagination): string {\n const sections: string[] = [];\n sections.push(`# Issues (page ${pagination.page} of ${pagination.totalPages}, ${pagination.totalItems} total)`);\n\n if (data.length === 0) {\n sections.push(\"No issues found.\");\n return sections.join(\"\\n\\n\");\n }\n\n const header = \"| Severity | Title | Status | Last Seen | Occurrences |\";\n const divider = \"|----------|-------|--------|-----------|-------------|\";\n const rows = data.map((issue) =>\n `| ${issue.severity} | ${truncate(issue.title, 60)} | ${issue.status} | ${formatRelative(issue.lastSeenAt)} | ${issue.occurrenceCount} |`\n );\n sections.push([header, divider, ...rows].join(\"\\n\"));\n\n return sections.join(\"\\n\\n\");\n}\n\n/* ── Diagnostics Fetching ─────────────────────────────────────────────── */\n\nasync function fetchDiagnostics(\n apiUrl: string,\n token: string,\n issue: IssueSummary\n): Promise<{ consoleErrors: ConsoleLogEntry[]; networkErrors: NetworkRequestEntry[] }> {\n const occ = issue.latestOccurrence;\n if (!occ) return { consoleErrors: [], networkErrors: [] };\n\n let consoleErrors: ConsoleLogEntry[] = [];\n let networkErrors: NetworkRequestEntry[] = [];\n\n try {\n const [consoleRes, networkRes] = await Promise.all([\n apiRequest<DiagnosticsDataResponse>(\n apiUrl, token, \"GET\",\n `/v2/issues/${issue.id}/occurrences/${occ.id}/console-logs`\n ),\n apiRequest<DiagnosticsDataResponse>(\n apiUrl, token, \"GET\",\n `/v2/issues/${issue.id}/occurrences/${occ.id}/network-requests`\n ),\n ]);\n\n if (consoleRes.ok && Array.isArray(consoleRes.data)) {\n consoleErrors = (consoleRes.data as ConsoleLogEntry[]).filter((e) => e.type === \"error\");\n }\n\n if (networkRes.ok && Array.isArray(networkRes.data)) {\n networkErrors = (networkRes.data as NetworkRequestEntry[]).filter(\n (e) => e.status === null || e.status === 0 || e.status >= 400\n );\n }\n } catch {\n // Diagnostics are best-effort; continue without them\n }\n\n // Fallback to legacy fields\n if (consoleErrors.length === 0 && occ.consoleLogExcerpt?.trim()) {\n consoleErrors = [{ type: \"error\", text: occ.consoleLogExcerpt.trim(), timestamp: null, location: null }];\n }\n\n if (networkErrors.length === 0 && occ.networkFailureUrl) {\n networkErrors = [{\n url: occ.networkFailureUrl,\n method: \"GET\",\n status: occ.networkFailureStatus,\n durationMs: null,\n }];\n }\n\n return { consoleErrors, networkErrors };\n}\n\n/* ── Sub-command Handlers ─────────────────────────────────────────────── */\n\nasync function handleList(argv: string[], apiUrl: string, token: string): Promise<void> {\n const jsonOutput = hasFlag(argv, \"--json\");\n const markdownOutput = getArgValue(argv, \"--format\") === \"markdown\";\n\n const params = new URLSearchParams();\n const search = getArgValue(argv, \"--search\");\n const severity = getArgValue(argv, \"--severity\");\n const status = getArgValue(argv, \"--status\");\n const propertyId = getArgValue(argv, \"--property-id\");\n const page = getArgValue(argv, \"--page\");\n const pageSize = getArgValue(argv, \"--page-size\");\n\n if (search) params.set(\"search\", search);\n if (severity) params.set(\"severity\", severity);\n if (status) params.set(\"statuses\", status);\n if (propertyId) params.set(\"propertyId\", propertyId);\n if (page) params.set(\"page\", page);\n if (pageSize) params.set(\"pageSize\", pageSize);\n\n const qs = params.toString();\n const path = `/v2/issues${qs ? `?${qs}` : \"\"}`;\n\n const result = await apiRequest<IssueListResponse>(apiUrl, token, \"GET\", path);\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n\n if (jsonOutput) {\n console.log(JSON.stringify({ data: result.data, pagination: result.pagination }, null, 2));\n return;\n }\n\n if (markdownOutput) {\n console.log(formatIssueListMarkdown(result.data, result.pagination));\n return;\n }\n\n // Default compact output\n const { data, pagination } = result;\n console.log(`Issues: ${pagination.totalItems} total (page ${pagination.page}/${pagination.totalPages})\\n`);\n\n if (data.length === 0) {\n console.log(\"No issues found.\");\n return;\n }\n\n for (const issue of data) {\n const icon = SEVERITY_ICONS[issue.severity] ?? \"?\";\n const statusLabel = formatStatusLabel(issue.status);\n const lastSeen = formatRelative(issue.lastSeenAt);\n console.log(` [${icon}] ${truncate(issue.title, 60)} ${statusLabel} ${lastSeen} (${issue.occurrenceCount}x) ${issue.id}`);\n }\n}\n\nasync function handleGet(argv: string[], apiUrl: string, token: string): Promise<void> {\n const issueId = argv[0];\n if (!issueId || issueId.startsWith(\"--\")) {\n console.error(\"Error: Missing issue ID.\");\n console.error(\"Usage: canary issues get <issueId>\");\n process.exit(1);\n }\n\n const jsonOutput = hasFlag(argv, \"--json\");\n const markdownOutput = getArgValue(argv, \"--format\") === \"markdown\";\n\n const result = await apiRequest<IssueDetailResponse>(apiUrl, token, \"GET\", `/v2/issues/${issueId}`);\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n\n const { issue } = result.data;\n const { consoleErrors, networkErrors } = await fetchDiagnostics(apiUrl, token, issue);\n\n if (jsonOutput) {\n console.log(JSON.stringify({ ...result.data, consoleErrors, networkErrors }, null, 2));\n return;\n }\n\n if (markdownOutput) {\n console.log(formatIssueDetailMarkdown(issue, consoleErrors, networkErrors));\n return;\n }\n\n // Default human-readable output\n const occ = issue.latestOccurrence;\n console.log(` Title: ${issue.title}`);\n console.log(` ID: ${issue.id}`);\n console.log(` Status: ${formatStatusLabel(issue.status)}`);\n console.log(` Severity: ${formatSeverity(issue.severity)}`);\n console.log(` Category: ${issue.category.label}`);\n console.log(` Occurrences: ${issue.occurrenceCount}`);\n console.log(` First seen: ${formatRelative(issue.firstSeenAt)}`);\n console.log(` Last seen: ${formatRelative(issue.lastSeenAt)}`);\n\n if (occ?.primaryUrl) {\n console.log(` URL: ${occ.primaryUrl}`);\n }\n\n if (issue.assignedTo) {\n console.log(` Assigned to: ${issue.assignedTo.displayName ?? issue.assignedTo.primaryEmail ?? issue.assignedTo.id}`);\n }\n\n const summary = occ?.ticketUserFacingSummary?.trim();\n if (summary) {\n console.log(`\\n Summary:\\n ${summary.split(\"\\n\").join(\"\\n \")}`);\n }\n\n const diagnostic = occ?.diagnosticJson;\n if (diagnostic) {\n const categoryLabel = DIAGNOSTIC_CATEGORY_LABELS[diagnostic.category] ?? diagnostic.category;\n console.log(`\\n Diagnostic: ${categoryLabel} (${Math.round(diagnostic.confidence * 100)}%)`);\n console.log(` ${diagnostic.title}`);\n console.log(` Recommendation: ${diagnostic.recommendation}`);\n }\n\n if (consoleErrors.length > 0) {\n console.log(`\\n Console Errors (${consoleErrors.length}):`);\n for (const entry of consoleErrors.slice(0, 5)) {\n const loc = entry.location ? ` (${entry.location})` : \"\";\n console.log(` [${entry.type}] ${truncate(entry.text, 100)}${loc}`);\n }\n if (consoleErrors.length > 5) {\n console.log(` ... and ${consoleErrors.length - 5} more`);\n }\n }\n\n if (networkErrors.length > 0) {\n console.log(`\\n Network Errors (${networkErrors.length}):`);\n for (const entry of networkErrors.slice(0, 5)) {\n const status = entry.status === null || entry.status === 0 ? \"failed\" : String(entry.status);\n console.log(` ${entry.method} ${truncate(entry.url, 80)} -> ${status}`);\n }\n if (networkErrors.length > 5) {\n console.log(` ... and ${networkErrors.length - 5} more`);\n }\n }\n}\n\n/* ── Help & Entry Point ───────────────────────────────────────────────── */\n\nfunction printIssuesHelp(): void {\n console.log(\n [\n \"Usage: canary issues <sub-command> [options]\",\n \"\",\n \"Sub-commands:\",\n \" list [options] List and search issues\",\n \" get <issueId> [options] Get issue detail with diagnostics\",\n \"\",\n \"List options:\",\n \" --search <query> Full-text search\",\n \" --severity <level> Filter: low, medium, high, unknown\",\n \" --status <statuses> Filter: open, closed, not_a_bug (comma-separated)\",\n \" --property-id <uuid> Filter by property\",\n \" --page <n> Page number (default: 1)\",\n \" --page-size <n> Page size (default: 25)\",\n \"\",\n \"Output options:\",\n \" --json Output raw JSON\",\n \" --format markdown Output as markdown\",\n \"\",\n \"Common options:\",\n \" --env <env> Target environment (prod, dev, local)\",\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 runIssues(argv: string[]): Promise<void> {\n const [subCommand, ...rest] = argv;\n\n if (!subCommand || subCommand === \"help\" || hasFlag(argv, \"--help\", \"-h\")) {\n printIssuesHelp();\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 \"get\":\n await handleGet(rest, apiUrl, token);\n break;\n default:\n console.error(`Unknown sub-command: ${subCommand}`);\n printIssuesHelp();\n process.exit(1);\n }\n}\n"],"mappings":"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","names":[]}
@@ -9,7 +9,7 @@ import {
9
9
  hasFlag,
10
10
  resolveConfig
11
11
  } from "./chunk-7R4YFGP6.js";
12
- import "./chunk-DGUM43GV.js";
12
+ import "./chunk-PLDDJCW6.js";
13
13
 
14
14
  // src/knobs.ts
15
15
  import process from "process";
@@ -284,4 +284,4 @@ async function runKnobs(argv) {
284
284
  export {
285
285
  runKnobs
286
286
  };
287
- //# sourceMappingURL=knobs-3MKMOXIV.js.map
287
+ //# sourceMappingURL=knobs-ED6LXBVM.js.map
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  LocalBrowserHost
3
- } from "./chunk-UEOXNF5X.js";
3
+ } from "./chunk-ZTWHPIXU.js";
4
4
  import {
5
5
  readStoredToken
6
6
  } from "./chunk-7R4YFGP6.js";
7
- import "./chunk-DGUM43GV.js";
7
+ import "./chunk-OE6O7H45.js";
8
+ import "./chunk-PLDDJCW6.js";
8
9
 
9
10
  // src/local-browser/index.ts
10
11
  import process from "process";
@@ -137,4 +138,4 @@ async function runLocalBrowser(args) {
137
138
  export {
138
139
  runLocalBrowser
139
140
  };
140
- //# sourceMappingURL=local-browser-GG5GUXDS.js.map
141
+ //# sourceMappingURL=local-browser-YSE3XCUW.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/local-browser/index.ts"],"sourcesContent":["/**\n * Local Browser Command\n *\n * Starts a local browser that can be controlled by the cloud agent.\n *\n * Usage:\n * canary browser [options]\n *\n * Options:\n * --mode <playwright|cdp> Browser mode (default: playwright)\n * --cdp-url <url> CDP endpoint for existing Chrome\n * --headless Run headless (default: true for playwright)\n * --storage-state <path> Path to storage state JSON\n * --api-url <url> API URL (default: https://api.trycanary.ai)\n * --instructions <text> Instructions for the cloud agent\n */\n\nimport process from \"node:process\";\nimport { readStoredToken } from \"../auth\";\nimport { LocalBrowserHost } from \"./host\";\nimport type { LocalBrowserMode } from \"./protocol\";\n\nconst DEFAULT_API_URL = \"https://api.trycanary.ai\";\n\ninterface LocalBrowserOptions {\n mode: LocalBrowserMode;\n cdpUrl?: string;\n headless: boolean;\n storageStatePath?: string;\n apiUrl: string;\n instructions?: string;\n}\n\nfunction parseArgs(args: string[]): LocalBrowserOptions {\n const options: LocalBrowserOptions = {\n mode: \"playwright\",\n headless: true,\n apiUrl: process.env.CANARY_API_URL ?? DEFAULT_API_URL,\n };\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n const nextArg = args[i + 1];\n\n switch (arg) {\n case \"--mode\":\n if (nextArg === \"playwright\" || nextArg === \"cdp\") {\n options.mode = nextArg;\n i++;\n }\n break;\n case \"--cdp-url\":\n options.cdpUrl = nextArg;\n options.mode = \"cdp\";\n i++;\n break;\n case \"--headless\":\n options.headless = true;\n break;\n case \"--no-headless\":\n options.headless = false;\n break;\n case \"--storage-state\":\n options.storageStatePath = nextArg;\n i++;\n break;\n case \"--api-url\":\n options.apiUrl = nextArg;\n i++;\n break;\n case \"--instructions\":\n options.instructions = nextArg;\n i++;\n break;\n }\n }\n\n return options;\n}\n\nasync function resolveToken(): Promise<string> {\n const token = process.env.CANARY_API_TOKEN ?? (await readStoredToken());\n if (!token) {\n throw new Error(\"Missing token. Run `canary login` first or set CANARY_API_TOKEN.\");\n }\n return token;\n}\n\ninterface CreateSessionResponse {\n ok: boolean;\n sessionId: string;\n wsToken: string;\n wsUrl: string;\n expiresAt: string;\n error?: string;\n}\n\nasync function createSession(\n apiUrl: string,\n token: string,\n options: LocalBrowserOptions\n): Promise<CreateSessionResponse> {\n const response = await fetch(`${apiUrl}/local-browser/sessions`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({\n browserMode: options.mode,\n instructions: options.instructions ?? null,\n }),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`Failed to create session: ${response.status} ${text}`);\n }\n\n return response.json();\n}\n\nexport async function runLocalBrowser(args: string[]): Promise<void> {\n const options = parseArgs(args);\n\n console.log(\"Starting local browser...\");\n console.log(` Mode: ${options.mode}`);\n if (options.cdpUrl) {\n console.log(` CDP URL: ${options.cdpUrl}`);\n }\n console.log(` Headless: ${options.headless}`);\n console.log(` API URL: ${options.apiUrl}`);\n console.log();\n\n // Get auth token\n const token = await resolveToken();\n\n // Create session with cloud API\n console.log(\"Creating session with cloud API...\");\n const session = await createSession(options.apiUrl, token, options);\n\n if (!session.ok) {\n throw new Error(`Failed to create session: ${session.error}`);\n }\n\n console.log(`Session created: ${session.sessionId}`);\n console.log(`Expires at: ${session.expiresAt}`);\n console.log();\n\n // Start the local browser host\n const host = new LocalBrowserHost({\n apiUrl: options.apiUrl,\n wsToken: session.wsToken,\n sessionId: session.sessionId,\n browserMode: options.mode,\n cdpUrl: options.cdpUrl,\n headless: options.headless,\n storageStatePath: options.storageStatePath,\n onLog: (level, message, data) => {\n const prefix = `[${level.toUpperCase()}]`;\n if (data) {\n console.log(prefix, message, data);\n } else {\n console.log(prefix, message);\n }\n },\n });\n\n // Handle shutdown\n const shutdown = async () => {\n console.log(\"\\nShutting down...\");\n await host.stop();\n process.exit(0);\n };\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n try {\n await host.start();\n console.log();\n console.log(\"Local browser is ready and connected to cloud.\");\n console.log(\"Press Ctrl+C to stop.\");\n console.log();\n\n // Keep the process running\n await new Promise(() => {});\n } catch (error) {\n console.error(\"Failed to start local browser:\", error);\n await host.stop();\n process.exit(1);\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/local-browser/index.ts"],"sourcesContent":["/**\n * Local Browser Command\n *\n * Starts a local browser that can be controlled by the cloud agent.\n *\n * Usage:\n * canary browser [options]\n *\n * Options:\n * --mode <playwright|cdp> Browser mode (default: playwright)\n * --cdp-url <url> CDP endpoint for existing Chrome\n * --headless Run headless (default: true for playwright)\n * --storage-state <path> Path to storage state JSON\n * --api-url <url> API URL (default: https://api.trycanary.ai)\n * --instructions <text> Instructions for the cloud agent\n */\n\nimport process from \"node:process\";\nimport { readStoredToken } from \"../auth\";\nimport { LocalBrowserHost } from \"./host\";\nimport type { LocalBrowserMode } from \"./protocol\";\n\nconst DEFAULT_API_URL = \"https://api.trycanary.ai\";\n\ninterface LocalBrowserOptions {\n mode: LocalBrowserMode;\n cdpUrl?: string;\n headless: boolean;\n storageStatePath?: string;\n apiUrl: string;\n instructions?: string;\n}\n\nfunction parseArgs(args: string[]): LocalBrowserOptions {\n const options: LocalBrowserOptions = {\n mode: \"playwright\",\n headless: true,\n apiUrl: process.env.CANARY_API_URL ?? DEFAULT_API_URL,\n };\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n const nextArg = args[i + 1];\n\n switch (arg) {\n case \"--mode\":\n if (nextArg === \"playwright\" || nextArg === \"cdp\") {\n options.mode = nextArg;\n i++;\n }\n break;\n case \"--cdp-url\":\n options.cdpUrl = nextArg;\n options.mode = \"cdp\";\n i++;\n break;\n case \"--headless\":\n options.headless = true;\n break;\n case \"--no-headless\":\n options.headless = false;\n break;\n case \"--storage-state\":\n options.storageStatePath = nextArg;\n i++;\n break;\n case \"--api-url\":\n options.apiUrl = nextArg;\n i++;\n break;\n case \"--instructions\":\n options.instructions = nextArg;\n i++;\n break;\n }\n }\n\n return options;\n}\n\nasync function resolveToken(): Promise<string> {\n const token = process.env.CANARY_API_TOKEN ?? (await readStoredToken());\n if (!token) {\n throw new Error(\"Missing token. Run `canary login` first or set CANARY_API_TOKEN.\");\n }\n return token;\n}\n\ninterface CreateSessionResponse {\n ok: boolean;\n sessionId: string;\n wsToken: string;\n wsUrl: string;\n expiresAt: string;\n error?: string;\n}\n\nasync function createSession(\n apiUrl: string,\n token: string,\n options: LocalBrowserOptions\n): Promise<CreateSessionResponse> {\n const response = await fetch(`${apiUrl}/local-browser/sessions`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({\n browserMode: options.mode,\n instructions: options.instructions ?? null,\n }),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`Failed to create session: ${response.status} ${text}`);\n }\n\n return response.json();\n}\n\nexport async function runLocalBrowser(args: string[]): Promise<void> {\n const options = parseArgs(args);\n\n console.log(\"Starting local browser...\");\n console.log(` Mode: ${options.mode}`);\n if (options.cdpUrl) {\n console.log(` CDP URL: ${options.cdpUrl}`);\n }\n console.log(` Headless: ${options.headless}`);\n console.log(` API URL: ${options.apiUrl}`);\n console.log();\n\n // Get auth token\n const token = await resolveToken();\n\n // Create session with cloud API\n console.log(\"Creating session with cloud API...\");\n const session = await createSession(options.apiUrl, token, options);\n\n if (!session.ok) {\n throw new Error(`Failed to create session: ${session.error}`);\n }\n\n console.log(`Session created: ${session.sessionId}`);\n console.log(`Expires at: ${session.expiresAt}`);\n console.log();\n\n // Start the local browser host\n const host = new LocalBrowserHost({\n apiUrl: options.apiUrl,\n wsToken: session.wsToken,\n sessionId: session.sessionId,\n browserMode: options.mode,\n cdpUrl: options.cdpUrl,\n headless: options.headless,\n storageStatePath: options.storageStatePath,\n onLog: (level, message, data) => {\n const prefix = `[${level.toUpperCase()}]`;\n if (data) {\n console.log(prefix, message, data);\n } else {\n console.log(prefix, message);\n }\n },\n });\n\n // Handle shutdown\n const shutdown = async () => {\n console.log(\"\\nShutting down...\");\n await host.stop();\n process.exit(0);\n };\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n try {\n await host.start();\n console.log();\n console.log(\"Local browser is ready and connected to cloud.\");\n console.log(\"Press Ctrl+C to stop.\");\n console.log();\n\n // Keep the process running\n await new Promise(() => {});\n } catch (error) {\n console.error(\"Failed to start local browser:\", error);\n await host.stop();\n process.exit(1);\n }\n}\n"],"mappings":"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","names":[]}
@@ -5,11 +5,12 @@ import {
5
5
  } from "./chunk-HOYYXZPV.js";
6
6
  import {
7
7
  LocalBrowserHost
8
- } from "./chunk-UEOXNF5X.js";
8
+ } from "./chunk-ZTWHPIXU.js";
9
9
  import {
10
10
  readStoredToken
11
11
  } from "./chunk-7R4YFGP6.js";
12
- import "./chunk-DGUM43GV.js";
12
+ import "./chunk-OE6O7H45.js";
13
+ import "./chunk-PLDDJCW6.js";
13
14
 
14
15
  // src/mcp.ts
15
16
  import process from "process";
@@ -381,4 +382,4 @@ function formatReport(input) {
381
382
  export {
382
383
  runMcp
383
384
  };
384
- //# sourceMappingURL=mcp-AD67OLQM.js.map
385
+ //# sourceMappingURL=mcp-LKHFYMA6.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/mcp.ts"],"sourcesContent":["import process from \"node:process\";\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { CallToolRequestSchema, ListToolsRequestSchema } from \"@modelcontextprotocol/sdk/types.js\";\nimport { createParser, type EventSourceMessage } from \"eventsource-parser\";\nimport { readStoredToken } from \"./auth\";\nimport { createLocalRun } from \"./local-run\";\nimport { connectTunnel, createTunnel } from \"./tunnel\";\nimport { LocalBrowserHost } from \"./local-browser/host\";\nimport type { LocalBrowserMode } from \"./local-browser/protocol\";\n\ntype RunLocalInput = {\n port: number;\n instructions: string;\n title?: string;\n};\n\ntype WaitForRunInput = {\n runId: string;\n};\n\ntype LocalBrowserStartInput = {\n mode?: LocalBrowserMode;\n cdpUrl?: string;\n headless?: boolean;\n storageStatePath?: string;\n instructions?: string;\n};\n\ntype LocalBrowserStopInput = {\n sessionId: string;\n};\n\ntype LocalBrowserStatusInput = {\n sessionId: string;\n};\n\ninterface BrowserSession {\n sessionId: string;\n host: LocalBrowserHost;\n startedAt: number;\n mode: LocalBrowserMode;\n}\n\n// Global browser sessions managed by MCP\nconst browserSessions = new Map<string, BrowserSession>();\n\nconst DEFAULT_API_URL = \"https://api.trycanary.ai\";\n\nfunction resolveApiUrl(input?: string): string {\n return input ?? process.env.CANARY_API_URL ?? DEFAULT_API_URL;\n}\n\nasync function resolveToken(): Promise<string> {\n const token = process.env.CANARY_API_TOKEN ?? (await readStoredToken());\n if (!token) {\n throw new Error(\"Missing token. Run `canary login` first or set CANARY_API_TOKEN.\");\n }\n return token;\n}\n\nfunction toolText(text: string) {\n return { content: [{ type: \"text\", text }] };\n}\n\nfunction toolJson(data: unknown) {\n return { content: [{ type: \"text\", text: JSON.stringify(data, null, 2) }] };\n}\n\nexport async function runMcp(argv: string[]) {\n const server = new Server(\n { name: \"canary-cli\", version: \"0.1.0\" },\n { capabilities: { tools: {} } }\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: [\n {\n name: \"local_run_tests\",\n description:\n \"Start an async local test run. A tunnel is opened automatically. Returns runId and watchUrl.\",\n inputSchema: {\n type: \"object\",\n properties: {\n port: { type: \"number\" },\n instructions: { type: \"string\" },\n title: { type: \"string\" },\n },\n required: [\"port\", \"instructions\"],\n },\n },\n {\n name: \"local_wait_for_results\",\n description:\n \"Wait for a local test run to complete. Streams until completion and returns a compact report.\",\n inputSchema: {\n type: \"object\",\n properties: {\n runId: { type: \"string\" },\n },\n required: [\"runId\"],\n },\n },\n {\n name: \"local_browser_start\",\n description:\n \"Start a local browser session that connects to the cloud agent. The cloud agent can then control this browser to test local applications. Returns sessionId for tracking.\",\n inputSchema: {\n type: \"object\",\n properties: {\n mode: {\n type: \"string\",\n enum: [\"playwright\", \"cdp\"],\n description: \"Browser mode: 'playwright' for fresh browser, 'cdp' to connect to existing Chrome\",\n },\n cdpUrl: {\n type: \"string\",\n description: \"CDP endpoint URL when mode is 'cdp' (e.g. http://localhost:9222)\",\n },\n headless: {\n type: \"boolean\",\n description: \"Run browser headless (default: true for playwright mode)\",\n },\n storageStatePath: {\n type: \"string\",\n description: \"Path to Playwright storage state JSON for pre-authenticated sessions\",\n },\n instructions: {\n type: \"string\",\n description: \"Instructions for the cloud agent on what to test\",\n },\n },\n },\n },\n {\n name: \"local_browser_status\",\n description: \"Check the status of a local browser session.\",\n inputSchema: {\n type: \"object\",\n properties: {\n sessionId: { type: \"string\" },\n },\n required: [\"sessionId\"],\n },\n },\n {\n name: \"local_browser_stop\",\n description: \"Stop a local browser session and close the browser.\",\n inputSchema: {\n type: \"object\",\n properties: {\n sessionId: { type: \"string\" },\n },\n required: [\"sessionId\"],\n },\n },\n {\n name: \"local_browser_list\",\n description: \"List all active local browser sessions.\",\n inputSchema: {\n type: \"object\",\n properties: {},\n },\n },\n {\n name: \"local_browser_run\",\n description:\n \"Start a test run on an active local browser session. The cloud agent will control the local browser according to the instructions.\",\n inputSchema: {\n type: \"object\",\n properties: {\n sessionId: {\n type: \"string\",\n description: \"The session ID from local_browser_start\",\n },\n instructions: {\n type: \"string\",\n description: \"Instructions for the cloud agent on what to test\",\n },\n startUrl: {\n type: \"string\",\n description: \"Optional URL to navigate to before starting\",\n },\n },\n required: [\"sessionId\", \"instructions\"],\n },\n },\n ],\n }));\n\n server.setRequestHandler(CallToolRequestSchema, async (req) => {\n const token = await resolveToken();\n const tool = req.params.name;\n\n if (tool === \"local_run_tests\") {\n const input = req.params.arguments as RunLocalInput;\n const apiUrl = resolveApiUrl();\n const tunnel = await createTunnel({ apiUrl, token, port: input.port });\n connectTunnel({\n apiUrl,\n tunnelId: tunnel.tunnelId,\n token: tunnel.token,\n port: input.port,\n });\n const tunnelUrl = tunnel.publicUrl;\n\n const run = await createLocalRun({\n apiUrl,\n token,\n title: input.title ?? \"Local MCP run\",\n featureSpec: input.instructions,\n startUrl: undefined,\n tunnelUrl,\n });\n\n return toolJson({\n runId: run.runId,\n watchUrl: run.watchUrl,\n tunnelUrl,\n note:\n \"Testing is asynchronous. Use local_wait_for_results with the runId to wait for completion.\",\n });\n }\n\n if (tool === \"local_wait_for_results\") {\n const input = req.params.arguments as WaitForRunInput;\n const apiUrl = resolveApiUrl();\n const report = await waitForResult({ apiUrl, token, runId: input.runId });\n return toolJson(report);\n }\n\n if (tool === \"local_browser_start\") {\n const input = req.params.arguments as LocalBrowserStartInput;\n const apiUrl = resolveApiUrl();\n const mode = input.mode ?? \"playwright\";\n\n // Create session with cloud API\n const sessionResponse = await fetch(`${apiUrl}/local-browser/sessions`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({\n browserMode: mode,\n instructions: input.instructions ?? null,\n }),\n });\n\n if (!sessionResponse.ok) {\n const text = await sessionResponse.text();\n return toolJson({ ok: false, error: `Failed to create session: ${text}` });\n }\n\n const session = (await sessionResponse.json()) as {\n ok: boolean;\n sessionId: string;\n wsToken: string;\n expiresAt: string;\n };\n\n // Start the local browser host\n const host = new LocalBrowserHost({\n apiUrl,\n wsToken: session.wsToken,\n sessionId: session.sessionId,\n browserMode: mode,\n cdpUrl: input.cdpUrl,\n headless: input.headless ?? true,\n storageStatePath: input.storageStatePath,\n onLog: (level, message) => {\n // Silent logging for MCP context\n if (level === \"error\") {\n console.error(`[LocalBrowser] ${message}`);\n }\n },\n });\n\n // Start in background\n host.start().catch((err) => {\n console.error(\"Failed to start local browser:\", err);\n browserSessions.delete(session.sessionId);\n });\n\n browserSessions.set(session.sessionId, {\n sessionId: session.sessionId,\n host,\n startedAt: Date.now(),\n mode,\n });\n\n return toolJson({\n ok: true,\n sessionId: session.sessionId,\n mode,\n expiresAt: session.expiresAt,\n note: \"Browser session started. The cloud agent can now control this browser. Use local_browser_stop to end the session.\",\n });\n }\n\n if (tool === \"local_browser_status\") {\n const input = req.params.arguments as LocalBrowserStatusInput;\n const session = browserSessions.get(input.sessionId);\n\n if (!session) {\n return toolJson({ ok: false, error: \"Session not found\", sessionId: input.sessionId });\n }\n\n return toolJson({\n ok: true,\n sessionId: session.sessionId,\n mode: session.mode,\n startedAt: new Date(session.startedAt).toISOString(),\n uptimeMs: Date.now() - session.startedAt,\n });\n }\n\n if (tool === \"local_browser_stop\") {\n const input = req.params.arguments as LocalBrowserStopInput;\n const session = browserSessions.get(input.sessionId);\n\n if (!session) {\n return toolJson({ ok: false, error: \"Session not found\", sessionId: input.sessionId });\n }\n\n await session.host.stop();\n browserSessions.delete(input.sessionId);\n\n return toolJson({\n ok: true,\n sessionId: input.sessionId,\n note: \"Browser session stopped and browser closed.\",\n });\n }\n\n if (tool === \"local_browser_list\") {\n const sessions = Array.from(browserSessions.values()).map((s) => ({\n sessionId: s.sessionId,\n mode: s.mode,\n startedAt: new Date(s.startedAt).toISOString(),\n uptimeMs: Date.now() - s.startedAt,\n }));\n\n return toolJson({\n ok: true,\n count: sessions.length,\n sessions,\n });\n }\n\n if (tool === \"local_browser_run\") {\n const input = req.params.arguments as {\n sessionId: string;\n instructions: string;\n startUrl?: string;\n };\n const apiUrl = resolveApiUrl();\n\n // Verify session is active locally\n const session = browserSessions.get(input.sessionId);\n if (!session) {\n return toolJson({\n ok: false,\n error: \"Session not found locally. Make sure you started it with local_browser_start.\",\n sessionId: input.sessionId,\n });\n }\n\n // Call the API to start the run\n const response = await fetch(`${apiUrl}/local-browser/sessions/${input.sessionId}/run`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({\n instructions: input.instructions,\n startUrl: input.startUrl ?? null,\n }),\n });\n\n if (!response.ok) {\n const text = await response.text();\n return toolJson({ ok: false, error: `Failed to start run: ${text}` });\n }\n\n const result = (await response.json()) as { ok: boolean; jobId: string; sessionId: string };\n\n return toolJson({\n ok: true,\n jobId: result.jobId,\n sessionId: result.sessionId,\n note: \"Test run started. The cloud agent is now controlling your local browser. You can watch the browser to see the test in action.\",\n });\n }\n\n return toolText(`Unknown tool: ${tool}`);\n });\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n return new Promise<void>(() => undefined);\n}\n\nasync function waitForResult(input: { apiUrl: string; token: string; runId: string }) {\n await streamUntilComplete(input);\n const response = await fetch(`${input.apiUrl}/local-tests/runs/${input.runId}`, {\n credentials: \"include\",\n headers: { authorization: `Bearer ${input.token}` },\n });\n const data = await response.json();\n const run = data?.data?.run ?? data?.run ?? data?.data;\n const summary = run?.summaryJson;\n return formatReport({ run, summary });\n}\n\nasync function streamUntilComplete(input: { apiUrl: string; token: string; runId: string }) {\n const response = await fetch(`${input.apiUrl}/local-tests/runs/${input.runId}/stream`, {\n headers: { authorization: `Bearer ${input.token}` },\n });\n\n if (!response.body) return;\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n const parser = createParser({\n onEvent: (event: EventSourceMessage) => {\n if (event.event === \"status\") {\n try {\n const payload = JSON.parse(event.data);\n if (payload?.status === \"completed\" || payload?.status === \"failed\") {\n reader.cancel().catch(() => undefined);\n }\n } catch {}\n }\n if (event.event === \"complete\" || event.event === \"error\") {\n reader.cancel().catch(() => undefined);\n }\n },\n });\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n parser.feed(decoder.decode(value, { stream: true }));\n }\n}\n\nfunction formatReport(input: { run: any; summary: any }) {\n if (!input.summary) {\n return {\n runId: input.run?.id,\n status: input.run?.status ?? \"unknown\",\n summary: \"No final report available.\",\n };\n }\n\n const tested = Array.isArray(input.summary.testedItems) ? input.summary.testedItems : [];\n const status = input.summary.status ?? input.run?.status ?? \"unknown\";\n const issues =\n status === \"issues_found\"\n ? (input.summary.notes ? [input.summary.notes] : [\"Issues reported.\"])\n : [];\n\n return {\n runId: input.run?.id,\n status,\n summary: input.summary.summary ?? \"Run completed.\",\n testedItems: tested,\n issues,\n notes: input.summary.notes ?? null,\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/mcp.ts"],"sourcesContent":["import process from \"node:process\";\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { CallToolRequestSchema, ListToolsRequestSchema } from \"@modelcontextprotocol/sdk/types.js\";\nimport { createParser, type EventSourceMessage } from \"eventsource-parser\";\nimport { readStoredToken } from \"./auth\";\nimport { createLocalRun } from \"./local-run\";\nimport { connectTunnel, createTunnel } from \"./tunnel\";\nimport { LocalBrowserHost } from \"./local-browser/host\";\nimport type { LocalBrowserMode } from \"./local-browser/protocol\";\n\ntype RunLocalInput = {\n port: number;\n instructions: string;\n title?: string;\n};\n\ntype WaitForRunInput = {\n runId: string;\n};\n\ntype LocalBrowserStartInput = {\n mode?: LocalBrowserMode;\n cdpUrl?: string;\n headless?: boolean;\n storageStatePath?: string;\n instructions?: string;\n};\n\ntype LocalBrowserStopInput = {\n sessionId: string;\n};\n\ntype LocalBrowserStatusInput = {\n sessionId: string;\n};\n\ninterface BrowserSession {\n sessionId: string;\n host: LocalBrowserHost;\n startedAt: number;\n mode: LocalBrowserMode;\n}\n\n// Global browser sessions managed by MCP\nconst browserSessions = new Map<string, BrowserSession>();\n\nconst DEFAULT_API_URL = \"https://api.trycanary.ai\";\n\nfunction resolveApiUrl(input?: string): string {\n return input ?? process.env.CANARY_API_URL ?? DEFAULT_API_URL;\n}\n\nasync function resolveToken(): Promise<string> {\n const token = process.env.CANARY_API_TOKEN ?? (await readStoredToken());\n if (!token) {\n throw new Error(\"Missing token. Run `canary login` first or set CANARY_API_TOKEN.\");\n }\n return token;\n}\n\nfunction toolText(text: string) {\n return { content: [{ type: \"text\", text }] };\n}\n\nfunction toolJson(data: unknown) {\n return { content: [{ type: \"text\", text: JSON.stringify(data, null, 2) }] };\n}\n\nexport async function runMcp(argv: string[]) {\n const server = new Server(\n { name: \"canary-cli\", version: \"0.1.0\" },\n { capabilities: { tools: {} } }\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: [\n {\n name: \"local_run_tests\",\n description:\n \"Start an async local test run. A tunnel is opened automatically. Returns runId and watchUrl.\",\n inputSchema: {\n type: \"object\",\n properties: {\n port: { type: \"number\" },\n instructions: { type: \"string\" },\n title: { type: \"string\" },\n },\n required: [\"port\", \"instructions\"],\n },\n },\n {\n name: \"local_wait_for_results\",\n description:\n \"Wait for a local test run to complete. Streams until completion and returns a compact report.\",\n inputSchema: {\n type: \"object\",\n properties: {\n runId: { type: \"string\" },\n },\n required: [\"runId\"],\n },\n },\n {\n name: \"local_browser_start\",\n description:\n \"Start a local browser session that connects to the cloud agent. The cloud agent can then control this browser to test local applications. Returns sessionId for tracking.\",\n inputSchema: {\n type: \"object\",\n properties: {\n mode: {\n type: \"string\",\n enum: [\"playwright\", \"cdp\"],\n description: \"Browser mode: 'playwright' for fresh browser, 'cdp' to connect to existing Chrome\",\n },\n cdpUrl: {\n type: \"string\",\n description: \"CDP endpoint URL when mode is 'cdp' (e.g. http://localhost:9222)\",\n },\n headless: {\n type: \"boolean\",\n description: \"Run browser headless (default: true for playwright mode)\",\n },\n storageStatePath: {\n type: \"string\",\n description: \"Path to Playwright storage state JSON for pre-authenticated sessions\",\n },\n instructions: {\n type: \"string\",\n description: \"Instructions for the cloud agent on what to test\",\n },\n },\n },\n },\n {\n name: \"local_browser_status\",\n description: \"Check the status of a local browser session.\",\n inputSchema: {\n type: \"object\",\n properties: {\n sessionId: { type: \"string\" },\n },\n required: [\"sessionId\"],\n },\n },\n {\n name: \"local_browser_stop\",\n description: \"Stop a local browser session and close the browser.\",\n inputSchema: {\n type: \"object\",\n properties: {\n sessionId: { type: \"string\" },\n },\n required: [\"sessionId\"],\n },\n },\n {\n name: \"local_browser_list\",\n description: \"List all active local browser sessions.\",\n inputSchema: {\n type: \"object\",\n properties: {},\n },\n },\n {\n name: \"local_browser_run\",\n description:\n \"Start a test run on an active local browser session. The cloud agent will control the local browser according to the instructions.\",\n inputSchema: {\n type: \"object\",\n properties: {\n sessionId: {\n type: \"string\",\n description: \"The session ID from local_browser_start\",\n },\n instructions: {\n type: \"string\",\n description: \"Instructions for the cloud agent on what to test\",\n },\n startUrl: {\n type: \"string\",\n description: \"Optional URL to navigate to before starting\",\n },\n },\n required: [\"sessionId\", \"instructions\"],\n },\n },\n ],\n }));\n\n server.setRequestHandler(CallToolRequestSchema, async (req) => {\n const token = await resolveToken();\n const tool = req.params.name;\n\n if (tool === \"local_run_tests\") {\n const input = req.params.arguments as RunLocalInput;\n const apiUrl = resolveApiUrl();\n const tunnel = await createTunnel({ apiUrl, token, port: input.port });\n connectTunnel({\n apiUrl,\n tunnelId: tunnel.tunnelId,\n token: tunnel.token,\n port: input.port,\n });\n const tunnelUrl = tunnel.publicUrl;\n\n const run = await createLocalRun({\n apiUrl,\n token,\n title: input.title ?? \"Local MCP run\",\n featureSpec: input.instructions,\n startUrl: undefined,\n tunnelUrl,\n });\n\n return toolJson({\n runId: run.runId,\n watchUrl: run.watchUrl,\n tunnelUrl,\n note:\n \"Testing is asynchronous. Use local_wait_for_results with the runId to wait for completion.\",\n });\n }\n\n if (tool === \"local_wait_for_results\") {\n const input = req.params.arguments as WaitForRunInput;\n const apiUrl = resolveApiUrl();\n const report = await waitForResult({ apiUrl, token, runId: input.runId });\n return toolJson(report);\n }\n\n if (tool === \"local_browser_start\") {\n const input = req.params.arguments as LocalBrowserStartInput;\n const apiUrl = resolveApiUrl();\n const mode = input.mode ?? \"playwright\";\n\n // Create session with cloud API\n const sessionResponse = await fetch(`${apiUrl}/local-browser/sessions`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({\n browserMode: mode,\n instructions: input.instructions ?? null,\n }),\n });\n\n if (!sessionResponse.ok) {\n const text = await sessionResponse.text();\n return toolJson({ ok: false, error: `Failed to create session: ${text}` });\n }\n\n const session = (await sessionResponse.json()) as {\n ok: boolean;\n sessionId: string;\n wsToken: string;\n expiresAt: string;\n };\n\n // Start the local browser host\n const host = new LocalBrowserHost({\n apiUrl,\n wsToken: session.wsToken,\n sessionId: session.sessionId,\n browserMode: mode,\n cdpUrl: input.cdpUrl,\n headless: input.headless ?? true,\n storageStatePath: input.storageStatePath,\n onLog: (level, message) => {\n // Silent logging for MCP context\n if (level === \"error\") {\n console.error(`[LocalBrowser] ${message}`);\n }\n },\n });\n\n // Start in background\n host.start().catch((err) => {\n console.error(\"Failed to start local browser:\", err);\n browserSessions.delete(session.sessionId);\n });\n\n browserSessions.set(session.sessionId, {\n sessionId: session.sessionId,\n host,\n startedAt: Date.now(),\n mode,\n });\n\n return toolJson({\n ok: true,\n sessionId: session.sessionId,\n mode,\n expiresAt: session.expiresAt,\n note: \"Browser session started. The cloud agent can now control this browser. Use local_browser_stop to end the session.\",\n });\n }\n\n if (tool === \"local_browser_status\") {\n const input = req.params.arguments as LocalBrowserStatusInput;\n const session = browserSessions.get(input.sessionId);\n\n if (!session) {\n return toolJson({ ok: false, error: \"Session not found\", sessionId: input.sessionId });\n }\n\n return toolJson({\n ok: true,\n sessionId: session.sessionId,\n mode: session.mode,\n startedAt: new Date(session.startedAt).toISOString(),\n uptimeMs: Date.now() - session.startedAt,\n });\n }\n\n if (tool === \"local_browser_stop\") {\n const input = req.params.arguments as LocalBrowserStopInput;\n const session = browserSessions.get(input.sessionId);\n\n if (!session) {\n return toolJson({ ok: false, error: \"Session not found\", sessionId: input.sessionId });\n }\n\n await session.host.stop();\n browserSessions.delete(input.sessionId);\n\n return toolJson({\n ok: true,\n sessionId: input.sessionId,\n note: \"Browser session stopped and browser closed.\",\n });\n }\n\n if (tool === \"local_browser_list\") {\n const sessions = Array.from(browserSessions.values()).map((s) => ({\n sessionId: s.sessionId,\n mode: s.mode,\n startedAt: new Date(s.startedAt).toISOString(),\n uptimeMs: Date.now() - s.startedAt,\n }));\n\n return toolJson({\n ok: true,\n count: sessions.length,\n sessions,\n });\n }\n\n if (tool === \"local_browser_run\") {\n const input = req.params.arguments as {\n sessionId: string;\n instructions: string;\n startUrl?: string;\n };\n const apiUrl = resolveApiUrl();\n\n // Verify session is active locally\n const session = browserSessions.get(input.sessionId);\n if (!session) {\n return toolJson({\n ok: false,\n error: \"Session not found locally. Make sure you started it with local_browser_start.\",\n sessionId: input.sessionId,\n });\n }\n\n // Call the API to start the run\n const response = await fetch(`${apiUrl}/local-browser/sessions/${input.sessionId}/run`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({\n instructions: input.instructions,\n startUrl: input.startUrl ?? null,\n }),\n });\n\n if (!response.ok) {\n const text = await response.text();\n return toolJson({ ok: false, error: `Failed to start run: ${text}` });\n }\n\n const result = (await response.json()) as { ok: boolean; jobId: string; sessionId: string };\n\n return toolJson({\n ok: true,\n jobId: result.jobId,\n sessionId: result.sessionId,\n note: \"Test run started. The cloud agent is now controlling your local browser. You can watch the browser to see the test in action.\",\n });\n }\n\n return toolText(`Unknown tool: ${tool}`);\n });\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n return new Promise<void>(() => undefined);\n}\n\nasync function waitForResult(input: { apiUrl: string; token: string; runId: string }) {\n await streamUntilComplete(input);\n const response = await fetch(`${input.apiUrl}/local-tests/runs/${input.runId}`, {\n credentials: \"include\",\n headers: { authorization: `Bearer ${input.token}` },\n });\n const data = await response.json();\n const run = data?.data?.run ?? data?.run ?? data?.data;\n const summary = run?.summaryJson;\n return formatReport({ run, summary });\n}\n\nasync function streamUntilComplete(input: { apiUrl: string; token: string; runId: string }) {\n const response = await fetch(`${input.apiUrl}/local-tests/runs/${input.runId}/stream`, {\n headers: { authorization: `Bearer ${input.token}` },\n });\n\n if (!response.body) return;\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n const parser = createParser({\n onEvent: (event: EventSourceMessage) => {\n if (event.event === \"status\") {\n try {\n const payload = JSON.parse(event.data);\n if (payload?.status === \"completed\" || payload?.status === \"failed\") {\n reader.cancel().catch(() => undefined);\n }\n } catch {}\n }\n if (event.event === \"complete\" || event.event === \"error\") {\n reader.cancel().catch(() => undefined);\n }\n },\n });\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n parser.feed(decoder.decode(value, { stream: true }));\n }\n}\n\nfunction formatReport(input: { run: any; summary: any }) {\n if (!input.summary) {\n return {\n runId: input.run?.id,\n status: input.run?.status ?? \"unknown\",\n summary: \"No final report available.\",\n };\n }\n\n const tested = Array.isArray(input.summary.testedItems) ? input.summary.testedItems : [];\n const status = input.summary.status ?? input.run?.status ?? \"unknown\";\n const issues =\n status === \"issues_found\"\n ? (input.summary.notes ? [input.summary.notes] : [\"Issues reported.\"])\n : [];\n\n return {\n runId: input.run?.id,\n status,\n summary: input.summary.summary ?? \"Run completed.\",\n testedItems: tested,\n issues,\n notes: input.summary.notes ?? null,\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,12 @@
1
+ import {
2
+ extractPdfText,
3
+ extractPdfTextFromBuffer,
4
+ setPdfExtractLogger
5
+ } from "./chunk-OE6O7H45.js";
6
+ import "./chunk-PLDDJCW6.js";
7
+ export {
8
+ extractPdfText,
9
+ extractPdfTextFromBuffer,
10
+ setPdfExtractLogger
11
+ };
12
+ //# sourceMappingURL=pdf-extract-YIDRKYUD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}