@everydaydevopsio/pilot 0.5.0
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.
- package/.ai-agent-bridge.yaml +10 -0
- package/.dockerignore +9 -0
- package/CLAUDE.md +61 -0
- package/LICENSE +21 -0
- package/README.md +252 -0
- package/dist/browser.d.ts +100 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +654 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli/init.d.ts +4 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +56 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/skill-template.d.ts +2 -0
- package/dist/cli/skill-template.d.ts.map +1 -0
- package/dist/cli/skill-template.js +114 -0
- package/dist/cli/skill-template.js.map +1 -0
- package/dist/commands/click.d.ts +21 -0
- package/dist/commands/click.d.ts.map +1 -0
- package/dist/commands/click.js +64 -0
- package/dist/commands/click.js.map +1 -0
- package/dist/commands/evaluate.d.ts +14 -0
- package/dist/commands/evaluate.d.ts.map +1 -0
- package/dist/commands/evaluate.js +28 -0
- package/dist/commands/evaluate.js.map +1 -0
- package/dist/commands/navigate.d.ts +19 -0
- package/dist/commands/navigate.d.ts.map +1 -0
- package/dist/commands/navigate.js +78 -0
- package/dist/commands/navigate.js.map +1 -0
- package/dist/commands/page_info.d.ts +8 -0
- package/dist/commands/page_info.d.ts.map +1 -0
- package/dist/commands/page_info.js +13 -0
- package/dist/commands/page_info.js.map +1 -0
- package/dist/commands/screenshot.d.ts +18 -0
- package/dist/commands/screenshot.d.ts.map +1 -0
- package/dist/commands/screenshot.js +37 -0
- package/dist/commands/screenshot.js.map +1 -0
- package/dist/commands/tabs.d.ts +10 -0
- package/dist/commands/tabs.d.ts.map +1 -0
- package/dist/commands/tabs.js +15 -0
- package/dist/commands/tabs.js.map +1 -0
- package/dist/commands/type.d.ts +14 -0
- package/dist/commands/type.d.ts.map +1 -0
- package/dist/commands/type.js +61 -0
- package/dist/commands/type.js.map +1 -0
- package/dist/commands/wait.d.ts +17 -0
- package/dist/commands/wait.d.ts.map +1 -0
- package/dist/commands/wait.js +61 -0
- package/dist/commands/wait.js.map +1 -0
- package/dist/mcp/console-buffer.d.ts +28 -0
- package/dist/mcp/console-buffer.d.ts.map +1 -0
- package/dist/mcp/console-buffer.js +68 -0
- package/dist/mcp/console-buffer.js.map +1 -0
- package/dist/mcp/index.d.ts +3 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +74 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server.d.ts +18 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +46 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/browser.d.ts +5 -0
- package/dist/mcp/tools/browser.d.ts.map +1 -0
- package/dist/mcp/tools/browser.js +435 -0
- package/dist/mcp/tools/browser.js.map +1 -0
- package/dist/mcp/tools/errors.d.ts +4 -0
- package/dist/mcp/tools/errors.d.ts.map +1 -0
- package/dist/mcp/tools/errors.js +117 -0
- package/dist/mcp/tools/errors.js.map +1 -0
- package/dist/mcp/tools/names.d.ts +7 -0
- package/dist/mcp/tools/names.d.ts.map +1 -0
- package/dist/mcp/tools/names.js +24 -0
- package/dist/mcp/tools/names.js.map +1 -0
- package/dist/util/config.d.ts +34 -0
- package/dist/util/config.d.ts.map +1 -0
- package/dist/util/config.js +53 -0
- package/dist/util/config.js.map +1 -0
- package/dist/util/logger.d.ts +4 -0
- package/dist/util/logger.d.ts.map +1 -0
- package/dist/util/logger.js +16 -0
- package/dist/util/logger.js.map +1 -0
- package/dist/viewport.d.ts +105 -0
- package/dist/viewport.d.ts.map +1 -0
- package/dist/viewport.js +145 -0
- package/dist/viewport.js.map +1 -0
- package/package.json +74 -0
- package/smoke/mcp-smoke.ts +161 -0
- package/src/browser.ts +798 -0
- package/src/cli/init.ts +62 -0
- package/src/cli/skill-template.ts +113 -0
- package/src/commands/click.ts +93 -0
- package/src/commands/evaluate.ts +45 -0
- package/src/commands/navigate.ts +105 -0
- package/src/commands/page_info.ts +21 -0
- package/src/commands/screenshot.ts +72 -0
- package/src/commands/tabs.ts +35 -0
- package/src/commands/type.ts +77 -0
- package/src/commands/wait.ts +90 -0
- package/src/mcp/console-buffer.ts +94 -0
- package/src/mcp/index.ts +107 -0
- package/src/mcp/server.ts +67 -0
- package/src/mcp/tools/browser.ts +605 -0
- package/src/mcp/tools/errors.ts +158 -0
- package/src/mcp/tools/names.ts +25 -0
- package/src/util/config.ts +71 -0
- package/src/util/logger.ts +18 -0
- package/src/viewport.ts +204 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const ConfigSchema = z.object({
|
|
3
|
+
cdpPort: z.number().int().min(1).max(65535).default(9222),
|
|
4
|
+
cdpHost: z.string().default('127.0.0.1'),
|
|
5
|
+
cdpRetryMs: z.number().int().min(100).default(2000),
|
|
6
|
+
cdpMaxRetryMs: z.number().int().min(1000).default(30000),
|
|
7
|
+
logLevel: z
|
|
8
|
+
.enum(['trace', 'debug', 'info', 'warn', 'error', 'fatal'])
|
|
9
|
+
.default('info'),
|
|
10
|
+
chromePath: z.string().optional(),
|
|
11
|
+
headless: z.boolean().default(true),
|
|
12
|
+
profileName: z.string().default('profile1'),
|
|
13
|
+
viewport: z.string().default('desktop'),
|
|
14
|
+
responsive: z.boolean().optional()
|
|
15
|
+
});
|
|
16
|
+
export function loadConfig(cliArgs = {}) {
|
|
17
|
+
const env = {
|
|
18
|
+
cdpPort: process.env.PILOT_CDP_PORT
|
|
19
|
+
? parseInt(process.env.PILOT_CDP_PORT, 10)
|
|
20
|
+
: undefined,
|
|
21
|
+
cdpHost: process.env.PILOT_CDP_HOST,
|
|
22
|
+
cdpRetryMs: process.env.PILOT_CDP_RETRY_MS
|
|
23
|
+
? parseInt(process.env.PILOT_CDP_RETRY_MS, 10)
|
|
24
|
+
: undefined,
|
|
25
|
+
cdpMaxRetryMs: process.env.PILOT_CDP_MAX_RETRY_MS
|
|
26
|
+
? parseInt(process.env.PILOT_CDP_MAX_RETRY_MS, 10)
|
|
27
|
+
: undefined,
|
|
28
|
+
logLevel: process.env.PILOT_LOG_LEVEL,
|
|
29
|
+
chromePath: process.env.PILOT_CHROME_PATH,
|
|
30
|
+
headless: process.env.PILOT_HEADLESS !== undefined
|
|
31
|
+
? process.env.PILOT_HEADLESS !== 'false'
|
|
32
|
+
: undefined,
|
|
33
|
+
profileName: process.env.PILOT_PROFILE_NAME,
|
|
34
|
+
viewport: process.env.PILOT_VIEWPORT,
|
|
35
|
+
responsive: process.env.PILOT_RESPONSIVE !== undefined
|
|
36
|
+
? process.env.PILOT_RESPONSIVE !== 'false'
|
|
37
|
+
: undefined
|
|
38
|
+
};
|
|
39
|
+
const merged = {
|
|
40
|
+
cdpPort: cliArgs.cdpPort ?? env.cdpPort,
|
|
41
|
+
cdpHost: cliArgs.cdpHost ?? env.cdpHost,
|
|
42
|
+
cdpRetryMs: env.cdpRetryMs,
|
|
43
|
+
cdpMaxRetryMs: env.cdpMaxRetryMs,
|
|
44
|
+
logLevel: cliArgs.logLevel ?? env.logLevel,
|
|
45
|
+
chromePath: cliArgs.chromePath ?? env.chromePath,
|
|
46
|
+
headless: cliArgs.headless ?? env.headless,
|
|
47
|
+
profileName: cliArgs.profileName ?? env.profileName,
|
|
48
|
+
viewport: cliArgs.viewport ?? env.viewport,
|
|
49
|
+
responsive: cliArgs.responsive ?? env.responsive
|
|
50
|
+
};
|
|
51
|
+
return ConfigSchema.parse(merged);
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/util/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;IACzD,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,WAAW,CAAC;IACxC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;IACnD,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;IACxD,QAAQ,EAAE,CAAC;SACR,IAAI,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;SAC1D,OAAO,CAAC,MAAM,CAAC;IAClB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IACnC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC;IAC3C,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC;IACvC,UAAU,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;CACnC,CAAC,CAAC;AAeH,MAAM,UAAU,UAAU,CAAC,UAAmB,EAAE;IAC9C,MAAM,GAAG,GAAG;QACV,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc;YACjC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,CAAC;YAC1C,CAAC,CAAC,SAAS;QACb,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc;QACnC,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,kBAAkB;YACxC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,EAAE,CAAC;YAC9C,CAAC,CAAC,SAAS;QACb,aAAa,EAAE,OAAO,CAAC,GAAG,CAAC,sBAAsB;YAC/C,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAsB,EAAE,EAAE,CAAC;YAClD,CAAC,CAAC,SAAS;QACb,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,eAAe;QACrC,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB;QACzC,QAAQ,EACN,OAAO,CAAC,GAAG,CAAC,cAAc,KAAK,SAAS;YACtC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,KAAK,OAAO;YACxC,CAAC,CAAC,SAAS;QACf,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,kBAAkB;QAC3C,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc;QACpC,UAAU,EACR,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,SAAS;YACxC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,OAAO;YAC1C,CAAC,CAAC,SAAS;KAChB,CAAC;IAEF,MAAM,MAAM,GAAG;QACb,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,GAAG,CAAC,OAAO;QACvC,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,GAAG,CAAC,OAAO;QACvC,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,aAAa,EAAE,GAAG,CAAC,aAAa;QAChC,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,GAAG,CAAC,QAAQ;QAC1C,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,GAAG,CAAC,UAAU;QAChD,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,GAAG,CAAC,QAAQ;QAC1C,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,GAAG,CAAC,WAAW;QACnD,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,GAAG,CAAC,QAAQ;QAC1C,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,GAAG,CAAC,UAAU;KACjD,CAAC;IAEF,OAAO,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;AACpC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/util/logger.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAIxB,wBAAgB,YAAY,CAAC,KAAK,SAAS,GAAG,IAAI,CAAC,MAAM,CAMxD;AAED,wBAAgB,SAAS,IAAI,IAAI,CAAC,MAAM,CAKvC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import pino from 'pino';
|
|
2
|
+
let _logger = null;
|
|
3
|
+
export function createLogger(level = 'info') {
|
|
4
|
+
// Always write to stderr: this is an MCP server that communicates over stdio,
|
|
5
|
+
// so stdout must carry only MCP protocol messages. pino-pretty is a
|
|
6
|
+
// devDependency and is not available in production/global installs.
|
|
7
|
+
_logger = pino({ level }, process.stderr);
|
|
8
|
+
return _logger;
|
|
9
|
+
}
|
|
10
|
+
export function getLogger() {
|
|
11
|
+
if (!_logger) {
|
|
12
|
+
_logger = createLogger();
|
|
13
|
+
}
|
|
14
|
+
return _logger;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/util/logger.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,IAAI,OAAO,GAAuB,IAAI,CAAC;AAEvC,MAAM,UAAU,YAAY,CAAC,KAAK,GAAG,MAAM;IACzC,8EAA8E;IAC9E,oEAAoE;IACpE,oEAAoE;IACpE,OAAO,GAAG,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,GAAG,YAAY,EAAE,CAAC;IAC3B,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { Client } from 'chrome-remote-interface';
|
|
2
|
+
export interface ViewportConfig {
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
deviceScaleFactor: number;
|
|
6
|
+
mobile: boolean;
|
|
7
|
+
userAgent?: string;
|
|
8
|
+
responsive?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare const VIEWPORT_PRESETS: {
|
|
11
|
+
desktop: {
|
|
12
|
+
width: number;
|
|
13
|
+
height: number;
|
|
14
|
+
deviceScaleFactor: number;
|
|
15
|
+
mobile: false;
|
|
16
|
+
responsive: true;
|
|
17
|
+
};
|
|
18
|
+
'desktop-small': {
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
deviceScaleFactor: number;
|
|
22
|
+
mobile: false;
|
|
23
|
+
responsive: true;
|
|
24
|
+
};
|
|
25
|
+
tablet: {
|
|
26
|
+
width: number;
|
|
27
|
+
height: number;
|
|
28
|
+
deviceScaleFactor: number;
|
|
29
|
+
mobile: true;
|
|
30
|
+
userAgent: string;
|
|
31
|
+
};
|
|
32
|
+
'tablet-landscape': {
|
|
33
|
+
width: number;
|
|
34
|
+
height: number;
|
|
35
|
+
deviceScaleFactor: number;
|
|
36
|
+
mobile: true;
|
|
37
|
+
userAgent: string;
|
|
38
|
+
};
|
|
39
|
+
mobile: {
|
|
40
|
+
width: number;
|
|
41
|
+
height: number;
|
|
42
|
+
deviceScaleFactor: number;
|
|
43
|
+
mobile: true;
|
|
44
|
+
userAgent: string;
|
|
45
|
+
};
|
|
46
|
+
'mobile-landscape': {
|
|
47
|
+
width: number;
|
|
48
|
+
height: number;
|
|
49
|
+
deviceScaleFactor: number;
|
|
50
|
+
mobile: true;
|
|
51
|
+
userAgent: string;
|
|
52
|
+
};
|
|
53
|
+
'mobile-small': {
|
|
54
|
+
width: number;
|
|
55
|
+
height: number;
|
|
56
|
+
deviceScaleFactor: number;
|
|
57
|
+
mobile: true;
|
|
58
|
+
userAgent: string;
|
|
59
|
+
};
|
|
60
|
+
'desktop-scaled': {
|
|
61
|
+
width: number;
|
|
62
|
+
height: number;
|
|
63
|
+
deviceScaleFactor: number;
|
|
64
|
+
mobile: false;
|
|
65
|
+
};
|
|
66
|
+
'desktop-qhd': {
|
|
67
|
+
width: number;
|
|
68
|
+
height: number;
|
|
69
|
+
deviceScaleFactor: number;
|
|
70
|
+
mobile: false;
|
|
71
|
+
responsive: true;
|
|
72
|
+
};
|
|
73
|
+
'mobile-pro': {
|
|
74
|
+
width: number;
|
|
75
|
+
height: number;
|
|
76
|
+
deviceScaleFactor: number;
|
|
77
|
+
mobile: true;
|
|
78
|
+
userAgent: string;
|
|
79
|
+
};
|
|
80
|
+
'mobile-large': {
|
|
81
|
+
width: number;
|
|
82
|
+
height: number;
|
|
83
|
+
deviceScaleFactor: number;
|
|
84
|
+
mobile: true;
|
|
85
|
+
userAgent: string;
|
|
86
|
+
};
|
|
87
|
+
'mobile-android': {
|
|
88
|
+
width: number;
|
|
89
|
+
height: number;
|
|
90
|
+
deviceScaleFactor: number;
|
|
91
|
+
mobile: true;
|
|
92
|
+
userAgent: string;
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
export type ViewportPresetName = keyof typeof VIEWPORT_PRESETS;
|
|
96
|
+
export interface ResolveViewportOptions {
|
|
97
|
+
preset?: string;
|
|
98
|
+
width?: number;
|
|
99
|
+
height?: number;
|
|
100
|
+
deviceScaleFactor?: number;
|
|
101
|
+
responsive?: boolean;
|
|
102
|
+
}
|
|
103
|
+
export declare function resolveViewport(opts?: ResolveViewportOptions): ViewportConfig;
|
|
104
|
+
export declare function applyViewport(client: Client, config: ViewportConfig): Promise<void>;
|
|
105
|
+
//# sourceMappingURL=viewport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viewport.d.ts","sourceRoot":"","sources":["../src/viewport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AA0BtD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB,EAAE,MAAM,CAAC;IAC1B,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAKD,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoFa,CAAC;AAE3C,MAAM,MAAM,kBAAkB,GAAG,MAAM,OAAO,gBAAgB,CAAC;AAE/D,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,wBAAgB,eAAe,CAC7B,IAAI,GAAE,sBAA2B,GAChC,cAAc,CAoBhB;AAED,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,IAAI,CAAC,CA0Cf"}
|
package/dist/viewport.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
const MOBILE_USER_AGENT = 'Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Mobile Safari/537.36';
|
|
2
|
+
export const VIEWPORT_PRESETS = {
|
|
3
|
+
desktop: {
|
|
4
|
+
width: 1920,
|
|
5
|
+
height: 1080,
|
|
6
|
+
deviceScaleFactor: 1,
|
|
7
|
+
mobile: false,
|
|
8
|
+
responsive: true
|
|
9
|
+
},
|
|
10
|
+
'desktop-small': {
|
|
11
|
+
width: 1366,
|
|
12
|
+
height: 768,
|
|
13
|
+
deviceScaleFactor: 1,
|
|
14
|
+
mobile: false,
|
|
15
|
+
responsive: true
|
|
16
|
+
},
|
|
17
|
+
tablet: {
|
|
18
|
+
width: 768,
|
|
19
|
+
height: 1024,
|
|
20
|
+
deviceScaleFactor: 2,
|
|
21
|
+
mobile: true,
|
|
22
|
+
userAgent: MOBILE_USER_AGENT
|
|
23
|
+
},
|
|
24
|
+
'tablet-landscape': {
|
|
25
|
+
width: 1024,
|
|
26
|
+
height: 768,
|
|
27
|
+
deviceScaleFactor: 2,
|
|
28
|
+
mobile: true,
|
|
29
|
+
userAgent: MOBILE_USER_AGENT
|
|
30
|
+
},
|
|
31
|
+
mobile: {
|
|
32
|
+
width: 390,
|
|
33
|
+
height: 844,
|
|
34
|
+
deviceScaleFactor: 3,
|
|
35
|
+
mobile: true,
|
|
36
|
+
userAgent: MOBILE_USER_AGENT
|
|
37
|
+
},
|
|
38
|
+
'mobile-landscape': {
|
|
39
|
+
width: 844,
|
|
40
|
+
height: 390,
|
|
41
|
+
deviceScaleFactor: 3,
|
|
42
|
+
mobile: true,
|
|
43
|
+
userAgent: MOBILE_USER_AGENT
|
|
44
|
+
},
|
|
45
|
+
'mobile-small': {
|
|
46
|
+
width: 360,
|
|
47
|
+
height: 800,
|
|
48
|
+
deviceScaleFactor: 3,
|
|
49
|
+
mobile: true,
|
|
50
|
+
userAgent: MOBILE_USER_AGENT
|
|
51
|
+
},
|
|
52
|
+
'desktop-scaled': {
|
|
53
|
+
width: 1536,
|
|
54
|
+
height: 864,
|
|
55
|
+
deviceScaleFactor: 1.25,
|
|
56
|
+
mobile: false
|
|
57
|
+
},
|
|
58
|
+
'desktop-qhd': {
|
|
59
|
+
width: 2560,
|
|
60
|
+
height: 1440,
|
|
61
|
+
deviceScaleFactor: 1,
|
|
62
|
+
mobile: false,
|
|
63
|
+
responsive: true
|
|
64
|
+
},
|
|
65
|
+
'mobile-pro': {
|
|
66
|
+
width: 393,
|
|
67
|
+
height: 852,
|
|
68
|
+
deviceScaleFactor: 3,
|
|
69
|
+
mobile: true,
|
|
70
|
+
userAgent: MOBILE_USER_AGENT
|
|
71
|
+
},
|
|
72
|
+
'mobile-large': {
|
|
73
|
+
width: 430,
|
|
74
|
+
height: 932,
|
|
75
|
+
deviceScaleFactor: 3,
|
|
76
|
+
mobile: true,
|
|
77
|
+
userAgent: MOBILE_USER_AGENT
|
|
78
|
+
},
|
|
79
|
+
'mobile-android': {
|
|
80
|
+
width: 412,
|
|
81
|
+
height: 915,
|
|
82
|
+
deviceScaleFactor: 2.625,
|
|
83
|
+
mobile: true,
|
|
84
|
+
userAgent: MOBILE_USER_AGENT
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
export function resolveViewport(opts = {}) {
|
|
88
|
+
const presetName = opts.preset ?? 'desktop';
|
|
89
|
+
const presets = VIEWPORT_PRESETS;
|
|
90
|
+
const base = presets[presetName];
|
|
91
|
+
if (!base) {
|
|
92
|
+
const valid = Object.keys(VIEWPORT_PRESETS).join(', ');
|
|
93
|
+
throw new Error(`Unknown viewport preset "${presetName}". Valid presets: ${valid}`);
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
...base,
|
|
97
|
+
...(opts.width !== undefined && { width: opts.width }),
|
|
98
|
+
...(opts.height !== undefined && { height: opts.height }),
|
|
99
|
+
...(opts.deviceScaleFactor !== undefined && {
|
|
100
|
+
deviceScaleFactor: opts.deviceScaleFactor
|
|
101
|
+
}),
|
|
102
|
+
...(opts.responsive !== undefined && { responsive: opts.responsive })
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export async function applyViewport(client, config) {
|
|
106
|
+
const emulation = client.Emulation;
|
|
107
|
+
const browser = client.Browser;
|
|
108
|
+
// Resize the actual browser window so it matches the viewport dimensions.
|
|
109
|
+
// This is needed because --window-size is unreliable (Chrome may restore
|
|
110
|
+
// a previous size from the profile) and setDeviceMetricsOverride only
|
|
111
|
+
// overrides the rendering viewport, not the window itself.
|
|
112
|
+
try {
|
|
113
|
+
const { windowId } = await browser.getWindowForTarget();
|
|
114
|
+
await browser.setWindowBounds({
|
|
115
|
+
windowId,
|
|
116
|
+
bounds: { width: config.width, height: config.height }
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Best-effort: headless Chrome or older versions may not support this.
|
|
121
|
+
}
|
|
122
|
+
// In responsive mode, skip setDeviceMetricsOverride so the page uses the
|
|
123
|
+
// real window dimensions and reflows naturally when the window is resized
|
|
124
|
+
// — just like a normal browser. This is the default for desktop presets.
|
|
125
|
+
if (!config.responsive) {
|
|
126
|
+
await emulation.setDeviceMetricsOverride({
|
|
127
|
+
width: config.width,
|
|
128
|
+
height: config.height,
|
|
129
|
+
deviceScaleFactor: config.deviceScaleFactor,
|
|
130
|
+
mobile: config.mobile
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
if (config.mobile) {
|
|
134
|
+
await emulation.setTouchEmulationEnabled({
|
|
135
|
+
enabled: true,
|
|
136
|
+
maxTouchPoints: 5
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
if (config.userAgent) {
|
|
140
|
+
await emulation.setUserAgentOverride({
|
|
141
|
+
userAgent: config.userAgent
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=viewport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viewport.js","sourceRoot":"","sources":["../src/viewport.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@everydaydevopsio/pilot",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "DevTools-grade browser control for AI agents via MCP + CDP",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/mcp/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"pilot": "dist/mcp/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "rimraf ./dist && tsc",
|
|
12
|
+
"start": "node dist/mcp/index.js",
|
|
13
|
+
"dev": "tsx src/mcp/index.ts",
|
|
14
|
+
"lint": "eslint .",
|
|
15
|
+
"lint:fix": "eslint . --fix",
|
|
16
|
+
"prettier": "prettier . --check",
|
|
17
|
+
"prettier:fix": "prettier . --write",
|
|
18
|
+
"test": "jest",
|
|
19
|
+
"test:coverage": "jest --coverage",
|
|
20
|
+
"test:e2e:mcp": "PILOT_HEADLESS=false jest --config jest.e2e-mcp.config.js --verbose",
|
|
21
|
+
"test:e2e:mcp:ci": "jest --config jest.e2e-mcp.config.js --verbose",
|
|
22
|
+
"test:e2e:mcp:docker": "docker compose -f docker-compose.e2e.yml up --build --abort-on-container-exit --exit-code-from e2e",
|
|
23
|
+
"pretest:smoke": "pnpm run build",
|
|
24
|
+
"test:smoke": "tsx smoke/mcp-smoke.ts",
|
|
25
|
+
"test:smoke:docker": "docker compose -f docker-compose.smoke.yml up --build --abort-on-container-exit --exit-code-from smoke"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"browser",
|
|
29
|
+
"cdp",
|
|
30
|
+
"ai-agent",
|
|
31
|
+
"websocket",
|
|
32
|
+
"chrome"
|
|
33
|
+
],
|
|
34
|
+
"author": "",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=22"
|
|
38
|
+
},
|
|
39
|
+
"packageManager": "pnpm@10.33.2+sha512.a90faf6feeab71ad6c6e57f94e0fe1a12f5dcc22cd754db40ae9593eb6a3e0b6b12e3540218bb37ae083404b1f2ce6db2a4121e979829b4aff94b99f49da1cf8",
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
42
|
+
"ajv": "^8.17.1",
|
|
43
|
+
"chrome-remote-interface": "^0.34.0",
|
|
44
|
+
"pino": "^10.3.1",
|
|
45
|
+
"zod": "^4.4.3"
|
|
46
|
+
},
|
|
47
|
+
"pnpm": {
|
|
48
|
+
"overrides": {
|
|
49
|
+
"picomatch": ">=4.0.4"
|
|
50
|
+
},
|
|
51
|
+
"onlyBuiltDependencies": [
|
|
52
|
+
"esbuild",
|
|
53
|
+
"unrs-resolver"
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@eslint/js": "^10.0.1",
|
|
58
|
+
"@types/chrome-remote-interface": "^0.34.0",
|
|
59
|
+
"@types/jest": "^30.0.0",
|
|
60
|
+
"@types/node": "^25.4.0",
|
|
61
|
+
"eslint": "^10.8.1",
|
|
62
|
+
"eslint-config-prettier": "^10.1.8",
|
|
63
|
+
"eslint-plugin-prettier": "^5.5.6",
|
|
64
|
+
"globals": "^17.11.0",
|
|
65
|
+
"jest": "^30.4.2",
|
|
66
|
+
"pino-pretty": "^13.1.3",
|
|
67
|
+
"prettier": "^3.9.6",
|
|
68
|
+
"rimraf": "^6.1.3",
|
|
69
|
+
"ts-jest": "^29.4.12",
|
|
70
|
+
"tsx": "^4.23.12",
|
|
71
|
+
"typescript": "^5.9.3",
|
|
72
|
+
"typescript-eslint": "^8.67.0"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Smoke Test
|
|
3
|
+
*
|
|
4
|
+
* Verifies the MCP server starts and exposes the expected tool list.
|
|
5
|
+
* Does NOT require a running browser — no Chrome/Chromium needed.
|
|
6
|
+
*
|
|
7
|
+
* Output: one [PASS] / [FAIL] line per use case, then a final summary.
|
|
8
|
+
* Exit code: 0 if all checks pass, 1 on any failure.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
12
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
13
|
+
import { resolve } from 'node:path';
|
|
14
|
+
import { EXPECTED_TOOLS } from '../src/mcp/tools/names.js';
|
|
15
|
+
|
|
16
|
+
const CONNECT_TIMEOUT_MS = 15_000;
|
|
17
|
+
|
|
18
|
+
interface CheckResult {
|
|
19
|
+
name: string;
|
|
20
|
+
passed: boolean;
|
|
21
|
+
detail?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const results: CheckResult[] = [];
|
|
25
|
+
|
|
26
|
+
function pass(name: string, detail?: string): void {
|
|
27
|
+
results.push({ name, passed: true, detail });
|
|
28
|
+
const suffix = detail ? ` — ${detail}` : '';
|
|
29
|
+
console.log(`[PASS] ${name}${suffix}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function fail(name: string, detail?: string): void {
|
|
33
|
+
results.push({ name, passed: false, detail });
|
|
34
|
+
const suffix = detail ? ` — ${detail}` : '';
|
|
35
|
+
console.log(`[FAIL] ${name}${suffix}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function printSummary(): void {
|
|
39
|
+
const total = results.length;
|
|
40
|
+
const passed = results.filter((r) => r.passed).length;
|
|
41
|
+
const failed = total - passed;
|
|
42
|
+
console.log(`\n=== Results: ${passed}/${total} passed ===`);
|
|
43
|
+
if (failed === 0) {
|
|
44
|
+
console.log('SMOKE TEST PASSED');
|
|
45
|
+
} else {
|
|
46
|
+
const failures = results
|
|
47
|
+
.filter((r) => !r.passed)
|
|
48
|
+
.map((r) => ` - ${r.name}${r.detail ? `: ${r.detail}` : ''}`)
|
|
49
|
+
.join('\n');
|
|
50
|
+
console.log(`Failures:\n${failures}`);
|
|
51
|
+
console.log('SMOKE TEST FAILED');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function withTimeout<T>(
|
|
56
|
+
promise: Promise<T>,
|
|
57
|
+
ms: number,
|
|
58
|
+
label: string
|
|
59
|
+
): Promise<T> {
|
|
60
|
+
return Promise.race([
|
|
61
|
+
promise,
|
|
62
|
+
new Promise<never>((_, reject) =>
|
|
63
|
+
setTimeout(
|
|
64
|
+
() => reject(new Error(`Timed out after ${ms}ms: ${label}`)),
|
|
65
|
+
ms
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
]);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function main(): Promise<void> {
|
|
72
|
+
console.log('=== MCP Smoke Test ===\n');
|
|
73
|
+
|
|
74
|
+
const transport = new StdioClientTransport({
|
|
75
|
+
command: 'node',
|
|
76
|
+
args: [resolve('dist/mcp/index.js')],
|
|
77
|
+
env: {
|
|
78
|
+
...process.env,
|
|
79
|
+
NODE_ENV: 'test'
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const client = new Client({ name: 'mcp-smoke-test', version: '1.0.0' });
|
|
84
|
+
|
|
85
|
+
// ── Use case 1: server starts and accepts a connection ─────────────────────
|
|
86
|
+
try {
|
|
87
|
+
await withTimeout(
|
|
88
|
+
client.connect(transport),
|
|
89
|
+
CONNECT_TIMEOUT_MS,
|
|
90
|
+
'client.connect'
|
|
91
|
+
);
|
|
92
|
+
pass('MCP server starts and accepts connections');
|
|
93
|
+
} catch (err) {
|
|
94
|
+
fail('MCP server starts and accepts connections', String(err));
|
|
95
|
+
printSummary();
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ── Use case 2: tools/list returns a non-empty list ────────────────────────
|
|
100
|
+
let toolNames: string[] = [];
|
|
101
|
+
try {
|
|
102
|
+
const response = await withTimeout(
|
|
103
|
+
client.listTools(),
|
|
104
|
+
CONNECT_TIMEOUT_MS,
|
|
105
|
+
'client.listTools'
|
|
106
|
+
);
|
|
107
|
+
toolNames = response.tools.map((t) => t.name);
|
|
108
|
+
if (toolNames.length > 0) {
|
|
109
|
+
pass('Tool list is non-empty', `${toolNames.length} tools returned`);
|
|
110
|
+
} else {
|
|
111
|
+
fail('Tool list is non-empty', 'server returned 0 tools');
|
|
112
|
+
}
|
|
113
|
+
} catch (err) {
|
|
114
|
+
fail('Tool list is non-empty', String(err));
|
|
115
|
+
await client.close().catch(() => undefined);
|
|
116
|
+
printSummary();
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ── Use case 3: expected tool count matches ────────────────────────────────
|
|
121
|
+
if (toolNames.length === EXPECTED_TOOLS.length) {
|
|
122
|
+
pass(
|
|
123
|
+
`Tool count matches expected`,
|
|
124
|
+
`${toolNames.length} of ${EXPECTED_TOOLS.length}`
|
|
125
|
+
);
|
|
126
|
+
} else {
|
|
127
|
+
fail(
|
|
128
|
+
`Tool count matches expected`,
|
|
129
|
+
`got ${toolNames.length}, want ${EXPECTED_TOOLS.length}`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ── Use case 4: each expected tool is present ──────────────────────────────
|
|
134
|
+
for (const name of EXPECTED_TOOLS) {
|
|
135
|
+
if (toolNames.includes(name)) {
|
|
136
|
+
pass(`Tool registered: ${name}`);
|
|
137
|
+
} else {
|
|
138
|
+
fail(`Tool registered: ${name}`, 'missing from tool list');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ── Use case 5: no unrecognised extra tools ────────────────────────────────
|
|
143
|
+
const extra = toolNames.filter((t) => !EXPECTED_TOOLS.includes(t as never));
|
|
144
|
+
if (extra.length === 0) {
|
|
145
|
+
pass('No unrecognised tools registered');
|
|
146
|
+
} else {
|
|
147
|
+
fail('No unrecognised tools registered', `unexpected: ${extra.join(', ')}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
await client.close().catch(() => undefined);
|
|
151
|
+
|
|
152
|
+
printSummary();
|
|
153
|
+
|
|
154
|
+
const anyFailed = results.some((r) => !r.passed);
|
|
155
|
+
process.exit(anyFailed ? 1 : 0);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
main().catch((err) => {
|
|
159
|
+
console.error('Unexpected error:', err);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
});
|