pi-to-chrome 0.1.2

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.
@@ -0,0 +1,74 @@
1
+ /**
2
+ * chrome_read_console - Read console messages from the buffer
3
+ *
4
+ * Returns messages captured via CDP event listener since chrome-start.
5
+ * No DevTools window needed.
6
+ */
7
+
8
+ import type { ToolDefinition, ToolDeps } from '../core/types';
9
+ import { Type } from '@sinclair/typebox';
10
+ import type { ConsoleLevel } from '../console-buffer';
11
+
12
+ export const readConsoleTool: ToolDefinition<{
13
+ level?: ConsoleLevel | 'all';
14
+ limit?: number;
15
+ }> = {
16
+ name: 'chrome_read_console',
17
+ label: 'Chrome Read Console',
18
+ description: '读取页面的 console 日志消息,支持按级别过滤(log/warn/error/info)。',
19
+ promptSnippet: '读取页面的 console 日志',
20
+ promptGuidelines: [
21
+ '【排错时的第一反应】当页面行为异常或功能不工作时,先用 chrome_read_console 查看 error 级别日志,定位 JS 报错。',
22
+ '用 level 参数过滤:调试 JS 报错用 "error",查警告用 "warn",看完整日志用 "all"。',
23
+ '在修改代码后刷新页面,再用 chrome_read_console 确认错误是否消失。'
24
+ ],
25
+ parameters: Type.Object({
26
+ level: Type.Optional(Type.Union([
27
+ Type.Literal('log'),
28
+ Type.Literal('warn'),
29
+ Type.Literal('error'),
30
+ Type.Literal('info'),
31
+ Type.Literal('all')
32
+ ])),
33
+ limit: Type.Optional(Type.Number({ minimum: 1, maximum: 500, default: 50 }))
34
+ }),
35
+ async execute(page, params, deps?: ToolDeps) {
36
+ const consoleBuffer = deps?.consoleBuffer;
37
+ if (!consoleBuffer) throw new Error('ConsoleBuffer not available');
38
+
39
+ const level = (params.level || 'all') as ConsoleLevel | 'all';
40
+ const limit = params.limit ?? 50;
41
+
42
+ const messages = consoleBuffer.getMessages(level, limit);
43
+
44
+ const typeEmoji: Record<string, string> = {
45
+ log: '📝',
46
+ warn: '⚠️',
47
+ error: '❌',
48
+ info: 'ℹ️'
49
+ };
50
+
51
+ const lines = messages.map(m => {
52
+ const time = new Date(m.timestamp).toLocaleTimeString('zh-CN', { hour12: false });
53
+ const shortUrl = m.url.length > 60 ? m.url.slice(0, 57) + '...' : m.url;
54
+ return `${typeEmoji[m.type] || '📝'} [${time}] ${m.text} (${shortUrl})`;
55
+ });
56
+
57
+ const totalCount = consoleBuffer.count;
58
+ const filtered = messages.length;
59
+ const errorCount = messages.filter(m => m.type === 'error').length;
60
+ const warnCount = messages.filter(m => m.type === 'warn').length;
61
+
62
+ const header = `缓冲区 ${totalCount} 条 | 返回 ${filtered} 条 (${errorCount} errors, ${warnCount} warnings)`;
63
+ const fullText = `${header}\n\n${lines.join('\n')}`;
64
+
65
+ const truncated = fullText.length > 10000
66
+ ? fullText.slice(0, 10000) + `\n\n... (截断,用更小的 limit 缩小范围)`
67
+ : fullText;
68
+
69
+ return {
70
+ content: [{ type: 'text', text: truncated || '暂无 console 日志(只捕获连接后的输出)' }],
71
+ details: { count: totalCount, filtered }
72
+ };
73
+ }
74
+ };
@@ -0,0 +1,89 @@
1
+ /**
2
+ * chrome_take_screenshot - Take a screenshot of the current page
3
+ *
4
+ * Automatically scales down large viewports and restores after capture.
5
+ */
6
+
7
+ import type { ToolDefinition } from '../core/types';
8
+ import { Type } from '@sinclair/typebox';
9
+
10
+ export const takeScreenshotTool: ToolDefinition<{
11
+ format?: 'png' | 'jpeg';
12
+ quality?: number;
13
+ }> = {
14
+ name: 'chrome_take_screenshot',
15
+ label: 'Chrome Take Screenshot',
16
+ description: 'Take a screenshot of the current page. Returns base64 encoded image.',
17
+ promptSnippet: 'Take a screenshot of the current page',
18
+ promptGuidelines: [
19
+ 'Use chrome_take_screenshot when the user asks to see what the page looks like visually.'
20
+ ],
21
+ parameters: Type.Object({
22
+ format: Type.Optional(Type.Union([Type.Literal('png'), Type.Literal('jpeg')])),
23
+ quality: Type.Optional(Type.Number({ minimum: 0, maximum: 100 }))
24
+ }),
25
+ async execute(page, params) {
26
+ const format = params.format || 'jpeg';
27
+ const quality = params.quality ?? 50;
28
+
29
+ // Save original viewport
30
+ const originalViewport = page.viewport();
31
+
32
+ try {
33
+ // Check viewport size and scale if needed
34
+ let viewport = originalViewport;
35
+ const maxDimension = 2000;
36
+
37
+ if (originalViewport) {
38
+ const width = originalViewport.width;
39
+ const height = originalViewport.height;
40
+
41
+ if (width > maxDimension || height > maxDimension) {
42
+ const scale = maxDimension / Math.max(width, height);
43
+ viewport = {
44
+ width: Math.round(width * scale),
45
+ height: Math.round(height * scale),
46
+ deviceScaleFactor: (originalViewport.deviceScaleFactor ?? 1) * scale
47
+ };
48
+ await page.setViewport(viewport);
49
+ }
50
+ }
51
+
52
+ // Take screenshot
53
+ const screenshot = await page.screenshot({
54
+ type: format,
55
+ encoding: 'base64',
56
+ ...(format === 'jpeg' ? { quality } : {})
57
+ });
58
+
59
+ // Restore original viewport
60
+ if (originalViewport) {
61
+ await page.setViewport(originalViewport);
62
+ }
63
+
64
+ const base64Data = typeof screenshot === 'string' ? screenshot : Buffer.from(screenshot as any).toString('base64');
65
+ const sizeBytes = Math.round(base64Data.length * 0.75); // Approximate decoded size
66
+ const sizeKB = Math.round(sizeBytes / 1024);
67
+
68
+ return {
69
+ content: [{ type: 'text', text: `截图已获取 (${format}, ${sizeKB}KB)` }],
70
+ details: {
71
+ format,
72
+ data: base64Data,
73
+ sizeBytes
74
+ }
75
+ };
76
+
77
+ } catch (error: any) {
78
+ // Ensure viewport is restored even on error
79
+ if (originalViewport) {
80
+ try {
81
+ await page.setViewport(originalViewport);
82
+ } catch (viewError) {
83
+ console.error('[pi-to-chrome] take-screenshot: viewport 恢复失败', viewError);
84
+ }
85
+ }
86
+ throw error;
87
+ }
88
+ }
89
+ };