@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,90 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { Client } from 'chrome-remote-interface';
|
|
3
|
+
import type { BrowserManager } from '../browser.js';
|
|
4
|
+
|
|
5
|
+
export const WaitParamsSchema = z
|
|
6
|
+
.object({
|
|
7
|
+
selector: z.string().optional(),
|
|
8
|
+
selectorVisible: z.string().optional(),
|
|
9
|
+
networkIdle: z.boolean().optional(),
|
|
10
|
+
ms: z.number().int().min(0).optional(),
|
|
11
|
+
timeoutMs: z.number().int().positive().default(10000)
|
|
12
|
+
})
|
|
13
|
+
.refine(
|
|
14
|
+
(v) =>
|
|
15
|
+
v.selector !== undefined ||
|
|
16
|
+
v.selectorVisible !== undefined ||
|
|
17
|
+
v.networkIdle !== undefined ||
|
|
18
|
+
v.ms !== undefined,
|
|
19
|
+
{ message: 'At least one wait condition is required' }
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
export type WaitParams = z.infer<typeof WaitParamsSchema>;
|
|
23
|
+
|
|
24
|
+
export interface WaitResult {
|
|
25
|
+
ok: boolean;
|
|
26
|
+
elapsed: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function waitForSelectorPresent(
|
|
30
|
+
client: Client,
|
|
31
|
+
selector: string,
|
|
32
|
+
timeoutMs: number
|
|
33
|
+
): Promise<void> {
|
|
34
|
+
const start = Date.now();
|
|
35
|
+
while (Date.now() - start < timeoutMs) {
|
|
36
|
+
const result = await client.Runtime.evaluate({
|
|
37
|
+
expression: `!!document.querySelector(${JSON.stringify(selector)})`,
|
|
38
|
+
returnByValue: true
|
|
39
|
+
});
|
|
40
|
+
if (result.result.value === true) return;
|
|
41
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
42
|
+
}
|
|
43
|
+
throw new Error(`timeout waiting for selector: ${selector}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function waitForSelectorVisible(
|
|
47
|
+
client: Client,
|
|
48
|
+
selector: string,
|
|
49
|
+
timeoutMs: number
|
|
50
|
+
): Promise<void> {
|
|
51
|
+
const start = Date.now();
|
|
52
|
+
while (Date.now() - start < timeoutMs) {
|
|
53
|
+
const result = await client.Runtime.evaluate({
|
|
54
|
+
expression: `(function() {
|
|
55
|
+
const el = document.querySelector(${JSON.stringify(selector)});
|
|
56
|
+
if (!el) return false;
|
|
57
|
+
const rect = el.getBoundingClientRect();
|
|
58
|
+
return rect.width > 0 && rect.height > 0;
|
|
59
|
+
})()`,
|
|
60
|
+
returnByValue: true
|
|
61
|
+
});
|
|
62
|
+
if (result.result.value === true) return;
|
|
63
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
64
|
+
}
|
|
65
|
+
throw new Error(`timeout waiting for visible selector: ${selector}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function executeWait(
|
|
69
|
+
client: Client,
|
|
70
|
+
manager: BrowserManager,
|
|
71
|
+
params: WaitParams
|
|
72
|
+
): Promise<WaitResult> {
|
|
73
|
+
const start = Date.now();
|
|
74
|
+
|
|
75
|
+
if (params.ms !== undefined) {
|
|
76
|
+
await new Promise((r) => setTimeout(r, params.ms));
|
|
77
|
+
} else if (params.selector !== undefined) {
|
|
78
|
+
await waitForSelectorPresent(client, params.selector, params.timeoutMs);
|
|
79
|
+
} else if (params.selectorVisible !== undefined) {
|
|
80
|
+
await waitForSelectorVisible(
|
|
81
|
+
client,
|
|
82
|
+
params.selectorVisible,
|
|
83
|
+
params.timeoutMs
|
|
84
|
+
);
|
|
85
|
+
} else if (params.networkIdle) {
|
|
86
|
+
await manager.waitForNetworkIdle(params.timeoutMs);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return { ok: true, elapsed: Date.now() - start };
|
|
90
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export type ConsoleLevel = 'log' | 'warn' | 'error' | 'info' | 'debug';
|
|
2
|
+
|
|
3
|
+
export interface ConsoleMessage {
|
|
4
|
+
level: ConsoleLevel;
|
|
5
|
+
text: string;
|
|
6
|
+
url: string;
|
|
7
|
+
lineNumber: number;
|
|
8
|
+
timestamp: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ConsoleFilter {
|
|
12
|
+
level?: ConsoleLevel | ConsoleLevel[];
|
|
13
|
+
sinceMs?: number;
|
|
14
|
+
limit?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class ConsoleBuffer {
|
|
18
|
+
private buffer: ConsoleMessage[] = [];
|
|
19
|
+
private head = 0;
|
|
20
|
+
private count = 0;
|
|
21
|
+
|
|
22
|
+
constructor(private readonly maxSize: number = 1000) {}
|
|
23
|
+
|
|
24
|
+
push(msg: ConsoleMessage): void {
|
|
25
|
+
if (this.count < this.maxSize) {
|
|
26
|
+
this.buffer.push(msg);
|
|
27
|
+
this.count++;
|
|
28
|
+
} else {
|
|
29
|
+
this.buffer[this.head] = msg;
|
|
30
|
+
this.head = (this.head + 1) % this.maxSize;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getAll(filter?: ConsoleFilter): ConsoleMessage[] {
|
|
35
|
+
const messages = this.toArray();
|
|
36
|
+
return this.applyFilter(messages, filter);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getErrors(sinceMs?: number): ConsoleMessage[] {
|
|
40
|
+
return this.getAll({
|
|
41
|
+
level: 'error',
|
|
42
|
+
sinceMs
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
clear(): number {
|
|
47
|
+
const cleared = this.count;
|
|
48
|
+
this.buffer = [];
|
|
49
|
+
this.head = 0;
|
|
50
|
+
this.count = 0;
|
|
51
|
+
return cleared;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
size(): number {
|
|
55
|
+
return this.count;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private toArray(): ConsoleMessage[] {
|
|
59
|
+
if (this.count < this.maxSize) {
|
|
60
|
+
return [...this.buffer];
|
|
61
|
+
}
|
|
62
|
+
return [
|
|
63
|
+
...this.buffer.slice(this.head),
|
|
64
|
+
...this.buffer.slice(0, this.head)
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private applyFilter(
|
|
69
|
+
messages: ConsoleMessage[],
|
|
70
|
+
filter?: ConsoleFilter
|
|
71
|
+
): ConsoleMessage[] {
|
|
72
|
+
if (!filter) return messages;
|
|
73
|
+
|
|
74
|
+
let result = messages;
|
|
75
|
+
|
|
76
|
+
if (filter.level) {
|
|
77
|
+
const levels = Array.isArray(filter.level)
|
|
78
|
+
? filter.level
|
|
79
|
+
: [filter.level];
|
|
80
|
+
result = result.filter((m) => levels.includes(m.level));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (filter.sinceMs !== undefined) {
|
|
84
|
+
const cutoff = Date.now() - filter.sinceMs;
|
|
85
|
+
result = result.filter((m) => m.timestamp >= cutoff);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (filter.limit !== undefined && result.length > filter.limit) {
|
|
89
|
+
result = result.slice(-filter.limit);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
}
|
package/src/mcp/index.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync } from 'fs';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { dirname, join } from 'path';
|
|
6
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
7
|
+
import { createMcpServer } from './server.js';
|
|
8
|
+
import { loadConfig } from '../util/config.js';
|
|
9
|
+
import { createLogger } from '../util/logger.js';
|
|
10
|
+
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const pkg = JSON.parse(
|
|
13
|
+
readFileSync(join(__dirname, '../../package.json'), 'utf-8')
|
|
14
|
+
) as { version: string; description: string; name: string };
|
|
15
|
+
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
|
|
18
|
+
async function run(): Promise<void> {
|
|
19
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
20
|
+
console.log(pkg.version);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
25
|
+
console.log(`${pkg.name} v${pkg.version}`);
|
|
26
|
+
console.log(pkg.description);
|
|
27
|
+
console.log('');
|
|
28
|
+
console.log('Usage: pilot [command] [options]');
|
|
29
|
+
console.log('');
|
|
30
|
+
console.log('Commands:');
|
|
31
|
+
console.log(
|
|
32
|
+
' init [--force] Install the Claude Code debug-browser skill'
|
|
33
|
+
);
|
|
34
|
+
console.log(' (default) Start the MCP server on stdio');
|
|
35
|
+
console.log('');
|
|
36
|
+
console.log('Options:');
|
|
37
|
+
console.log(' --version, -v Show version number');
|
|
38
|
+
console.log(' --help, -h Show this help message');
|
|
39
|
+
console.log('');
|
|
40
|
+
console.log('Environment variables:');
|
|
41
|
+
console.log(
|
|
42
|
+
' PILOT_CDP_PORT Chrome DevTools Protocol port (default: 9222)'
|
|
43
|
+
);
|
|
44
|
+
console.log(
|
|
45
|
+
' PILOT_CDP_HOST Chrome DevTools Protocol host (default: 127.0.0.1)'
|
|
46
|
+
);
|
|
47
|
+
console.log(
|
|
48
|
+
' PILOT_MCP_BUFFER_SIZE Console event buffer size (default: 1000)'
|
|
49
|
+
);
|
|
50
|
+
console.log(
|
|
51
|
+
' PILOT_LOG_LEVEL Log level: trace|debug|info|warn|error|fatal (default: info)'
|
|
52
|
+
);
|
|
53
|
+
console.log(
|
|
54
|
+
' PILOT_PROFILE_NAME Browser profile name (default: profile1)'
|
|
55
|
+
);
|
|
56
|
+
console.log(
|
|
57
|
+
' PILOT_CHROME_NO_SANDBOX Override Chrome sandbox (true/false)'
|
|
58
|
+
);
|
|
59
|
+
console.log(' PILOT_VIEWPORT Viewport preset (default: desktop)');
|
|
60
|
+
console.log(
|
|
61
|
+
' Presets: desktop, desktop-small, tablet,'
|
|
62
|
+
);
|
|
63
|
+
console.log(
|
|
64
|
+
' tablet-landscape, mobile, mobile-landscape, mobile-small'
|
|
65
|
+
);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const subcommand = args[0] && !args[0].startsWith('-') ? args[0] : null;
|
|
70
|
+
|
|
71
|
+
const baseConfig = loadConfig();
|
|
72
|
+
createLogger(baseConfig.logLevel);
|
|
73
|
+
|
|
74
|
+
if (subcommand === 'init') {
|
|
75
|
+
const { runInit } = await import('../cli/init.js');
|
|
76
|
+
const force = args.includes('--force');
|
|
77
|
+
await runInit({ force });
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const config = {
|
|
82
|
+
bufferSize: parseInt(process.env.PILOT_MCP_BUFFER_SIZE ?? '1000', 10),
|
|
83
|
+
cdpPort: process.env.PILOT_CDP_PORT
|
|
84
|
+
? parseInt(process.env.PILOT_CDP_PORT, 10)
|
|
85
|
+
: undefined,
|
|
86
|
+
cdpHost: process.env.PILOT_CDP_HOST
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const { server, cleanup } = await createMcpServer(config);
|
|
90
|
+
const transport = new StdioServerTransport();
|
|
91
|
+
|
|
92
|
+
async function shutdown(): Promise<void> {
|
|
93
|
+
await cleanup();
|
|
94
|
+
process.exit(0);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
process.on('SIGINT', () => void shutdown());
|
|
98
|
+
process.on('SIGTERM', () => void shutdown());
|
|
99
|
+
|
|
100
|
+
await server.connect(transport);
|
|
101
|
+
console.error('[pilot] MCP server running on stdio');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
run().catch((err) => {
|
|
105
|
+
console.error('[pilot] Failed to start:', err);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { BrowserManager } from '../browser.js';
|
|
3
|
+
import { loadConfig } from '../util/config.js';
|
|
4
|
+
import { ConsoleBuffer, type ConsoleMessage } from './console-buffer.js';
|
|
5
|
+
import { registerBrowserTools } from './tools/browser.js';
|
|
6
|
+
import { registerErrorTools } from './tools/errors.js';
|
|
7
|
+
|
|
8
|
+
export interface McpConfig {
|
|
9
|
+
bufferSize: number;
|
|
10
|
+
cdpPort?: number;
|
|
11
|
+
cdpHost?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface BrowserContext {
|
|
15
|
+
manager: BrowserManager | null;
|
|
16
|
+
consoleBuffer: ConsoleBuffer;
|
|
17
|
+
baseConfig: McpConfig;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function createMcpServer(config: McpConfig): Promise<{
|
|
21
|
+
server: McpServer;
|
|
22
|
+
cleanup: () => Promise<void>;
|
|
23
|
+
}> {
|
|
24
|
+
const server = new McpServer({
|
|
25
|
+
name: 'pilot',
|
|
26
|
+
version: '0.1.0'
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const consoleBuffer = new ConsoleBuffer(config.bufferSize);
|
|
30
|
+
|
|
31
|
+
const context: BrowserContext = {
|
|
32
|
+
manager: null,
|
|
33
|
+
consoleBuffer,
|
|
34
|
+
baseConfig: config
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function attachConsoleBuffer(manager: BrowserManager): void {
|
|
38
|
+
manager.setEventCallback((event, data) => {
|
|
39
|
+
if (event === 'console_message') {
|
|
40
|
+
consoleBuffer.push(data as ConsoleMessage);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function makeBrowserManager(): BrowserManager {
|
|
46
|
+
const browserConfig = loadConfig({
|
|
47
|
+
cdpPort: config.cdpPort,
|
|
48
|
+
cdpHost: config.cdpHost
|
|
49
|
+
});
|
|
50
|
+
const manager = new BrowserManager(browserConfig);
|
|
51
|
+
attachConsoleBuffer(manager);
|
|
52
|
+
return manager;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
registerBrowserTools(server, context, makeBrowserManager);
|
|
56
|
+
registerErrorTools(server, consoleBuffer);
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
server,
|
|
60
|
+
cleanup: async () => {
|
|
61
|
+
if (context.manager) {
|
|
62
|
+
await context.manager.destroy();
|
|
63
|
+
context.manager = null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|