diff --git a/coverage/base.css b/coverage/base.css deleted file mode 100644 index f418035..0000000 --- a/coverage/base.css +++ /dev/null @@ -1,224 +0,0 @@ -body, html { - margin:0; padding: 0; - height: 100%; -} -body { - font-family: Helvetica Neue, Helvetica, Arial; - font-size: 14px; - color:#333; -} -.small { font-size: 12px; } -*, *:after, *:before { - -webkit-box-sizing:border-box; - -moz-box-sizing:border-box; - box-sizing:border-box; - } -h1 { font-size: 20px; margin: 0;} -h2 { font-size: 14px; } -pre { - font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; - margin: 0; - padding: 0; - -moz-tab-size: 2; - -o-tab-size: 2; - tab-size: 2; -} -a { color:#0074D9; text-decoration:none; } -a:hover { text-decoration:underline; } -.strong { font-weight: bold; } -.space-top1 { padding: 10px 0 0 0; } -.pad2y { padding: 20px 0; } -.pad1y { padding: 10px 0; } -.pad2x { padding: 0 20px; } -.pad2 { padding: 20px; } -.pad1 { padding: 10px; } -.space-left2 { padding-left:55px; } -.space-right2 { padding-right:20px; } -.center { text-align:center; } -.clearfix { display:block; } -.clearfix:after { - content:''; - display:block; - height:0; - clear:both; - visibility:hidden; - } -.fl { float: left; } -@media only screen and (max-width:640px) { - .col3 { width:100%; max-width:100%; } - .hide-mobile { display:none!important; } -} - -.quiet { - color: #7f7f7f; - color: rgba(0,0,0,0.5); -} -.quiet a { opacity: 0.7; } - -.fraction { - font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; - font-size: 10px; - color: #555; - background: #E8E8E8; - padding: 4px 5px; - border-radius: 3px; - vertical-align: middle; -} - -div.path a:link, div.path a:visited { color: #333; } -table.coverage { - border-collapse: collapse; - margin: 10px 0 0 0; - padding: 0; -} - -table.coverage td { - margin: 0; - padding: 0; - vertical-align: top; -} -table.coverage td.line-count { - text-align: right; - padding: 0 5px 0 20px; -} -table.coverage td.line-coverage { - text-align: right; - padding-right: 10px; - min-width:20px; -} - -table.coverage td span.cline-any { - display: inline-block; - padding: 0 5px; - width: 100%; -} -.missing-if-branch { - display: inline-block; - margin-right: 5px; - border-radius: 3px; - position: relative; - padding: 0 4px; - background: #333; - color: yellow; -} - -.skip-if-branch { - display: none; - margin-right: 10px; - position: relative; - padding: 0 4px; - background: #ccc; - color: white; -} -.missing-if-branch .typ, .skip-if-branch .typ { - color: inherit !important; -} -.coverage-summary { - border-collapse: collapse; - width: 100%; -} -.coverage-summary tr { border-bottom: 1px solid #bbb; } -.keyline-all { border: 1px solid #ddd; } -.coverage-summary td, .coverage-summary th { padding: 10px; } -.coverage-summary tbody { border: 1px solid #bbb; } -.coverage-summary td { border-right: 1px solid #bbb; } -.coverage-summary td:last-child { border-right: none; } -.coverage-summary th { - text-align: left; - font-weight: normal; - white-space: nowrap; -} -.coverage-summary th.file { border-right: none !important; } -.coverage-summary th.pct { } -.coverage-summary th.pic, -.coverage-summary th.abs, -.coverage-summary td.pct, -.coverage-summary td.abs { text-align: right; } -.coverage-summary td.file { white-space: nowrap; } -.coverage-summary td.pic { min-width: 120px !important; } -.coverage-summary tfoot td { } - -.coverage-summary .sorter { - height: 10px; - width: 7px; - display: inline-block; - margin-left: 0.5em; - background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; -} -.coverage-summary .sorted .sorter { - background-position: 0 -20px; -} -.coverage-summary .sorted-desc .sorter { - background-position: 0 -10px; -} -.status-line { height: 10px; } -/* yellow */ -.cbranch-no { background: yellow !important; color: #111; } -/* dark red */ -.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } -.low .chart { border:1px solid #C21F39 } -.highlighted, -.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ - background: #C21F39 !important; -} -/* medium red */ -.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } -/* light red */ -.low, .cline-no { background:#FCE1E5 } -/* light green */ -.high, .cline-yes { background:rgb(230,245,208) } -/* medium green */ -.cstat-yes { background:rgb(161,215,106) } -/* dark green */ -.status-line.high, .high .cover-fill { background:rgb(77,146,33) } -.high .chart { border:1px solid rgb(77,146,33) } -/* dark yellow (gold) */ -.status-line.medium, .medium .cover-fill { background: #f9cd0b; } -.medium .chart { border:1px solid #f9cd0b; } -/* light yellow */ -.medium { background: #fff4c2; } - -.cstat-skip { background: #ddd; color: #111; } -.fstat-skip { background: #ddd; color: #111 !important; } -.cbranch-skip { background: #ddd !important; color: #111; } - -span.cline-neutral { background: #eaeaea; } - -.coverage-summary td.empty { - opacity: .5; - padding-top: 4px; - padding-bottom: 4px; - line-height: 1; - color: #888; -} - -.cover-fill, .cover-empty { - display:inline-block; - height: 12px; -} -.chart { - line-height: 0; -} -.cover-empty { - background: white; -} -.cover-full { - border-right: none !important; -} -pre.prettyprint { - border: none !important; - padding: 0 !important; - margin: 0 !important; -} -.com { color: #999 !important; } -.ignore-none { color: #999; font-weight: normal; } - -.wrapper { - min-height: 100%; - height: auto !important; - height: 100%; - margin: 0 auto -48px; -} -.footer, .push { - height: 48px; -} diff --git a/coverage/block-navigation.js b/coverage/block-navigation.js deleted file mode 100644 index 530d1ed..0000000 --- a/coverage/block-navigation.js +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable */ -var jumpToCode = (function init() { - // Classes of code we would like to highlight in the file view - var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; - - // Elements to highlight in the file listing view - var fileListingElements = ['td.pct.low']; - - // We don't want to select elements that are direct descendants of another match - var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` - - // Selector that finds elements on the page to which we can jump - var selector = - fileListingElements.join(', ') + - ', ' + - notSelector + - missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` - - // The NodeList of matching elements - var missingCoverageElements = document.querySelectorAll(selector); - - var currentIndex; - - function toggleClass(index) { - missingCoverageElements - .item(currentIndex) - .classList.remove('highlighted'); - missingCoverageElements.item(index).classList.add('highlighted'); - } - - function makeCurrent(index) { - toggleClass(index); - currentIndex = index; - missingCoverageElements.item(index).scrollIntoView({ - behavior: 'smooth', - block: 'center', - inline: 'center' - }); - } - - function goToPrevious() { - var nextIndex = 0; - if (typeof currentIndex !== 'number' || currentIndex === 0) { - nextIndex = missingCoverageElements.length - 1; - } else if (missingCoverageElements.length > 1) { - nextIndex = currentIndex - 1; - } - - makeCurrent(nextIndex); - } - - function goToNext() { - var nextIndex = 0; - - if ( - typeof currentIndex === 'number' && - currentIndex < missingCoverageElements.length - 1 - ) { - nextIndex = currentIndex + 1; - } - - makeCurrent(nextIndex); - } - - return function jump(event) { - if ( - document.getElementById('fileSearch') === document.activeElement && - document.activeElement != null - ) { - // if we're currently focused on the search input, we don't want to navigate - return; - } - - switch (event.which) { - case 78: // n - case 74: // j - goToNext(); - break; - case 66: // b - case 75: // k - case 80: // p - goToPrevious(); - break; - } - }; -})(); -window.addEventListener('keydown', jumpToCode); diff --git a/coverage/browser.ts.html b/coverage/browser.ts.html deleted file mode 100644 index 8b63d79..0000000 --- a/coverage/browser.ts.html +++ /dev/null @@ -1,691 +0,0 @@ - - - - -
-- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 | - - - - -7x -7x -7x - - - -7x - - - - - - - -1x -1x - - - - - -1x - - - -2x -1x -1x -1x -1x - - - - -44x -12x - -32x - - - -9x -9x -8x - - - -3x -3x -2x -2x - - - -1x -1x - - - -4x -4x - - - - - - - - - - - - - - - -4x -4x -4x -3x - - - -4x -4x - - - -4x -4x -3x - - - -2x -2x -1x - - - -2x -2x -1x - - - -3x -3x -2x - - - -3x -3x - - - -3x -1x - -2x - - - -5x -5x - - - -28x -28x - -4x -3x - - -2x -1x - - -1x - - - -2x -1x - - -2x -1x - - -2x -1x - - -2x -1x - - -2x -1x - - -2x -1x - - -2x -1x - - -2x -1x - - -2x -1x - - -1x -1x - - -2x -1x - - - - - - - -13x - - - - | import { resolve } from 'node:path';
-import { type Browser, type BrowserContext, type Page, webkit } from 'playwright';
-import type { BrowserCommand, BrowserOptions, CommandResponse, ElementInfo } from './types.js';
-
-export class ClaudeBrowser {
- private browser: Browser | null = null;
- private context: BrowserContext | null = null;
- private page: Page | null = null;
- private options: Required<BrowserOptions>;
-
- constructor(options: BrowserOptions = {}) {
- this.options = {
- headless: options.headless ?? true,
- width: options.width ?? 1280,
- height: options.height ?? 800,
- };
- }
-
- async launch(): Promise<void> {
- this.browser = await webkit.launch({ headless: this.options.headless });
- this.context = await this.browser.newContext({
- viewport: {
- width: this.options.width,
- height: this.options.height,
- },
- });
- this.page = await this.context.newPage();
- }
-
- async close(): Promise<void> {
- if (this.browser) {
- await this.browser.close();
- this.browser = null;
- this.context = null;
- this.page = null;
- }
- }
-
- private ensurePage(): Page {
- if (!this.page) {
- throw new Error('Browser not launched. Call launch() first.');
- }
- return this.page;
- }
-
- async goto(url: string): Promise<{ url: string; title: string }> {
- const page = this.ensurePage();
- await page.goto(url, { waitUntil: 'networkidle' });
- return { url: page.url(), title: await page.title() };
- }
-
- async click(selector: string): Promise<{ url: string }> {
- const page = this.ensurePage();
- await page.click(selector);
- await page.waitForLoadState('networkidle').catch(() => {});
- return { url: page.url() };
- }
-
- async type(selector: string, text: string): Promise<void> {
- const page = this.ensurePage();
- await page.fill(selector, text);
- }
-
- async query(selector: string): Promise<ElementInfo[]> {
- const page = this.ensurePage();
- return page.$$eval(selector, (nodes) =>
- nodes.map((el) => {
- const attrs: Record<string, string> = {};
- for (const attr of el.attributes) {
- attrs[attr.name] = attr.value;
- }
- return {
- tag: el.tagName.toLowerCase(),
- text: el.textContent?.trim().slice(0, 200) || '',
- attributes: attrs,
- };
- })
- );
- }
-
- async screenshot(path?: string, fullPage = false): Promise<{ path: string; buffer?: Buffer }> {
- const page = this.ensurePage();
- const resolvedPath = resolve(path || 'screenshot.png');
- const buffer = await page.screenshot({ path: resolvedPath, fullPage });
- return { path: resolvedPath, buffer };
- }
-
- async getUrl(): Promise<{ url: string; title: string }> {
- const page = this.ensurePage();
- return { url: page.url(), title: await page.title() };
- }
-
- async getHtml(full = false): Promise<string> {
- const page = this.ensurePage();
- const html = await page.content();
- return full ? html : html.slice(0, 10000);
- }
-
- async back(): Promise<{ url: string }> {
- const page = this.ensurePage();
- await page.goBack();
- return { url: page.url() };
- }
-
- async forward(): Promise<{ url: string }> {
- const page = this.ensurePage();
- await page.goForward();
- return { url: page.url() };
- }
-
- async reload(): Promise<{ url: string }> {
- const page = this.ensurePage();
- await page.reload();
- return { url: page.url() };
- }
-
- async wait(ms = 1000): Promise<void> {
- const page = this.ensurePage();
- await page.waitForTimeout(ms);
- }
-
- async newPage(): Promise<void> {
- if (!this.context) {
- throw new Error('Browser not launched. Call launch() first.');
- }
- this.page = await this.context.newPage();
- }
-
- async eval(script: string): Promise<unknown> {
- const page = this.ensurePage();
- return page.evaluate(script);
- }
-
- async executeCommand(cmd: BrowserCommand): Promise<CommandResponse> {
- try {
- switch (cmd.cmd) {
- case 'goto': {
- const result = await this.goto(cmd.url);
- return { ok: true, ...result };
- }
- case 'click': {
- const result = await this.click(cmd.selector);
- return { ok: true, ...result };
- }
- case 'type': {
- await this.type(cmd.selector, cmd.text);
- return { ok: true };
- }
- case 'query': {
- const elements = await this.query(cmd.selector);
- return { ok: true, count: elements.length, elements };
- }
- case 'screenshot': {
- const result = await this.screenshot(cmd.path, cmd.fullPage);
- return { ok: true, path: result.path };
- }
- case 'url': {
- const result = await this.getUrl();
- return { ok: true, ...result };
- }
- case 'html': {
- const html = await this.getHtml(cmd.full);
- return { ok: true, html };
- }
- case 'back': {
- const result = await this.back();
- return { ok: true, ...result };
- }
- case 'forward': {
- const result = await this.forward();
- return { ok: true, ...result };
- }
- case 'reload': {
- const result = await this.reload();
- return { ok: true, ...result };
- }
- case 'wait': {
- await this.wait(cmd.ms);
- return { ok: true };
- }
- case 'newpage': {
- await this.newPage();
- return { ok: true };
- }
- case 'close': {
- await this.close();
- return { ok: true };
- }
- case 'eval': {
- const result = await this.eval(cmd.script);
- return { ok: true, result };
- }
- default: {
- const _exhaustive: never = cmd;
- return { ok: false, error: `Unknown command: ${(_exhaustive as { cmd: string }).cmd}` };
- }
- }
- } catch (err) {
- return { ok: false, error: (err as Error).message };
- }
- }
-}
- |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -| File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
|---|---|---|---|---|---|---|---|---|---|
| browser.ts | -
-
- |
- 91.01% | -81/89 | -89.18% | -33/37 | -85.71% | -18/21 | -91.01% | -81/89 | -
| logger.ts | -
-
- |
- 97.36% | -37/38 | -85.71% | -36/42 | -93.75% | -15/16 | -100% | -37/37 | -
| server.ts | -
-
- |
- 50% | -26/52 | -70% | -7/10 | -61.53% | -8/13 | -49.01% | -25/51 | -
| types.ts | -
-
- |
- 0% | -0/0 | -0% | -0/0 | -0% | -0/0 | -0% | -0/0 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 | - - - -2x - - - - - - - - - - - - - - - - - -2x - - - - - - - - - - - - - - - - - -45x - - - -8x - - - - - - - - - - - - - - -32x - -4x - - -6x - -2x - -3x - -3x - -3x - -3x - -8x - - - - -19x -19x -19x -19x -19x - - - - - - - - - - - - - -2x -3x -3x -2x -2x -3x -2x -2x - - - -25x -1x - -24x -24x -25x -25x - - - - - -6x - -17x - - -16x - - - - - -2x - - -2x - | import chalk from 'chalk';
-import logSymbols from 'log-symbols';
-
-// Icons for commands
-export const icons: Record<string, string> = {
- goto: '→',
- click: '◉',
- type: '⌨',
- query: '?',
- screenshot: '📷',
- url: '🔗',
- html: '<>',
- back: '←',
- forward: '→',
- reload: '↻',
- wait: '⏳',
- newpage: '+',
- close: '✕',
- eval: '⚡',
-};
-
-// Colors for command types
-export const cmdColor: Record<string, (s: string) => string> = {
- goto: chalk.cyan,
- click: chalk.yellow,
- type: chalk.magenta,
- query: chalk.blue,
- screenshot: chalk.green,
- url: chalk.cyan,
- html: chalk.blue,
- back: chalk.yellow,
- forward: chalk.yellow,
- reload: chalk.yellow,
- wait: chalk.gray,
- newpage: chalk.green,
- close: chalk.red,
- eval: chalk.magenta,
-};
-
-export function ts(): string {
- return chalk.gray(`[${new Date().toISOString()}]`);
-}
-
-export function truncate(str: string, max: number): string {
- return str.length > max ? `${str.slice(0, max)}...` : str;
-}
-
-export interface CommandLike {
- cmd: string;
- url?: string;
- selector?: string;
- text?: string;
- path?: string;
- full?: boolean;
- ms?: number;
- script?: string;
-}
-
-export function getCommandDetail(cmd: CommandLike): string | undefined {
- switch (cmd.cmd) {
- case 'goto':
- return chalk.white(cmd.url);
- case 'click':
- case 'query':
- return chalk.white(cmd.selector);
- case 'type':
- return `${chalk.white(cmd.selector)} ${chalk.dim(`="${cmd.text}"`)}`;
- case 'screenshot':
- return chalk.dim(cmd.path || 'screenshot.png');
- case 'html':
- return cmd.full ? chalk.dim('(full)') : undefined;
- case 'wait':
- return chalk.dim(`${cmd.ms || 1000}ms`);
- case 'eval':
- return chalk.dim(truncate(cmd.script || '', 50));
- default:
- return undefined;
- }
-}
-
-export function formatCommand(cmd: CommandLike): string {
- const color = cmdColor[cmd.cmd] || chalk.white;
- const icon = icons[cmd.cmd] || '•';
- const detail = getCommandDetail(cmd);
- const suffix = detail ? ` ${detail}` : '';
- return `${ts()} ${chalk.bold(color(icon))} ${color(cmd.cmd.toUpperCase())}${suffix}`;
-}
-
-export interface ResultLike {
- ok: boolean;
- error?: string;
- title?: string;
- url?: string;
- count?: number;
- path?: string;
- html?: string;
- result?: unknown;
-}
-
-const resultFormatters: Record<string, (r: ResultLike) => string | undefined> = {
- goto: (r) => r.title,
- click: (r) => (r.url ? `→ ${r.url}` : undefined),
- query: (r) => (r.count !== undefined ? `Found ${r.count} element(s)` : undefined),
- screenshot: (r) => (r.path ? `Saved to ${r.path}` : undefined),
- url: (r) => r.url,
- html: (r) => (r.html !== undefined ? `${r.html.length} chars` : undefined),
- eval: (r) => (r.result !== undefined ? truncate(JSON.stringify(r.result), 80) : undefined),
-};
-
-export function formatResult(cmd: CommandLike, result: ResultLike): string {
- if (!result.ok) {
- return `${ts()} ${logSymbols.error} ${chalk.red(result.error)}`;
- }
- const formatter = resultFormatters[cmd.cmd];
- const msg = formatter ? formatter(result) : undefined;
- const suffix = msg ? ` ${chalk.dim(msg)}` : '';
- return `${ts()} ${logSymbols.success}${suffix}`;
-}
-
-export type LogFn = (msg: string) => void;
-
-export function createLogger(logFn: LogFn = console.log) {
- return {
- command(cmd: CommandLike): void {
- logFn(formatCommand(cmd));
- },
- result(cmd: CommandLike, result: ResultLike): void {
- logFn(formatResult(cmd, result));
- },
- };
-}
-
-// Default logger to stdout
-export const logger = createLogger();
-
-// Logger to stderr (for MCP)
-export const stderrLogger = createLogger((msg) => process.stderr.write(`${msg}\n`));
- |