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.
package/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # pi-to-chrome
2
+
3
+ ## Why?
4
+
5
+ I've used Claude and OpenCode for frontend development for a long time. Their MCP integrations for Chrome — built on Playwright and the like — feel like starships: massive, feature-bloated, 90% of which I never use. And they burn through tokens like there's no tomorrow.
6
+
7
+ It frustrated me.
8
+
9
+ Suddenly I found Pi. Its simplicity, elegance, restraint — so refreshing. Just 4 essential tools. That's it. That's all you need.
10
+
11
+ Inspired and led by Pi's philosophy, I built **pi-to-chrome** for myself. The exact 4 tools I actually use. No more, no less.
12
+
13
+ The world went quiet, quickly, and clean.
14
+
15
+ ---
16
+
17
+ ## Only 4 tools
18
+
19
+ 1. **find_elements** — Searching a jungle of hundreds of components and DOM nodes for the "Select Date" button? Use this to search the whole page and returns the element's id, className, and the full DOM hierarchy.
20
+ 2. **inspect_styles** — When a CSS rule sneaks in from nowhere and ruins your layout, use this to dump every style definition — inherited, computed, cascading — for the LLM to untangle.
21
+ 3. **read_console** — Chrome's console log is too tiny to read. Hundreds of entries and you still can't find what you want. Use this to stream all logs to the LLM for analysis.
22
+ 4. **execute_js** — Throw any JavaScript at Chrome. Execute it, get results back, debug on the fly.
23
+
24
+ ## Only 2 commands
25
+
26
+ - `/chrome-start` — launch a Chrome instance (or connect to one already running), enable this toolbox
27
+ - `/chrome-stop` — closes the browser, and disable this toolbox
28
+
29
+
30
+ ---
31
+
32
+ ## Install
33
+
34
+ ```bash
35
+ pi install npm:pi-to-chrome
36
+ ```
37
+
38
+ ---
39
+
40
+
41
+ ## Requirements
42
+
43
+ - [Pi](https://pi.dev) — you already have it
44
+ - Chrome browser with DevTools protocol enabled
45
+
46
+ ---
47
+
48
+ ### Sollawen
49
+
50
+ email: sollawen@163.com
@@ -0,0 +1,121 @@
1
+ /**
2
+ * ConsoleBuffer - Ring buffer for Chrome console messages
3
+ *
4
+ * Listens to CDP console events via puppeteer's page.on('console') API.
5
+ * Captures log/warn/error/info from ALL tabs automatically.
6
+ */
7
+
8
+ import type { Browser, Page, ConsoleMessage } from 'puppeteer-core';
9
+
10
+ export type ConsoleLevel = 'log' | 'warn' | 'error' | 'info';
11
+
12
+ export interface BufferedConsoleMessage {
13
+ type: ConsoleLevel;
14
+ text: string;
15
+ url: string;
16
+ timestamp: number;
17
+ }
18
+
19
+ const MAX_BUFFER_SIZE = 1000;
20
+
21
+ export class ConsoleBuffer {
22
+ private buffer: BufferedConsoleMessage[] = [];
23
+ private listeners: Array<{ page: Page; handler: (msg: ConsoleMessage) => void }> = [];
24
+ private browser: Browser | null = null;
25
+
26
+ /**
27
+ * Start listening to console events from all current and future pages.
28
+ */
29
+ startListening(browser: Browser): void {
30
+ this.browser = browser;
31
+
32
+ // Listen to all existing pages
33
+ browser.pages().then(pages => {
34
+ for (const page of pages) {
35
+ this.attachPageListener(page);
36
+ }
37
+ }).catch((error) => {
38
+ console.error('[pi-to-chrome] console-buffer: browser.pages() 失败', error);
39
+ });
40
+
41
+ // Listen to new pages opened after connection
42
+ browser.on('targetcreated', async (target) => {
43
+ if (target.type() === 'page') {
44
+ try {
45
+ const page = await target.page();
46
+ if (page) {
47
+ this.attachPageListener(page);
48
+ }
49
+ } catch (error) {
50
+ console.error('[pi-to-chrome] console-buffer: targetcreated page() 失败', error);
51
+ }
52
+ }
53
+ });
54
+ }
55
+
56
+ /**
57
+ * Stop all listeners and clear buffer.
58
+ */
59
+ stopListening(): void {
60
+ for (const { page, handler } of this.listeners) {
61
+ try {
62
+ page.off('console', handler);
63
+ } catch (error) {
64
+ console.error('[pi-to-chrome] console-buffer: page.off() 失败', error);
65
+ }
66
+ }
67
+ this.listeners = [];
68
+ this.browser = null;
69
+ this.buffer = [];
70
+ }
71
+
72
+ /**
73
+ * Get messages from buffer, optionally filtered by level and limited.
74
+ */
75
+ getMessages(level: ConsoleLevel | 'all' = 'all', limit = 50): BufferedConsoleMessage[] {
76
+ let filtered = this.buffer;
77
+ if (level !== 'all') {
78
+ filtered = filtered.filter(m => m.type === level);
79
+ }
80
+ return filtered.slice(-limit);
81
+ }
82
+
83
+ /**
84
+ * Clear all buffered messages.
85
+ */
86
+ clear(): void {
87
+ this.buffer = [];
88
+ }
89
+
90
+ /**
91
+ * Total number of messages in buffer.
92
+ */
93
+ get count(): number {
94
+ return this.buffer.length;
95
+ }
96
+
97
+ private attachPageListener(page: Page): void {
98
+ const handler = (msg: ConsoleMessage) => {
99
+ const type = msg.type() as ConsoleLevel;
100
+ if (!['log', 'warn', 'error', 'info'].includes(type)) return;
101
+
102
+ this.addMessage({
103
+ type,
104
+ text: msg.text(),
105
+ url: page.url(),
106
+ timestamp: Date.now()
107
+ });
108
+ };
109
+
110
+ page.on('console', handler);
111
+ this.listeners.push({ page, handler });
112
+ }
113
+
114
+ private addMessage(msg: BufferedConsoleMessage): void {
115
+ this.buffer.push(msg);
116
+ // Ring buffer: drop oldest when over capacity
117
+ if (this.buffer.length > MAX_BUFFER_SIZE) {
118
+ this.buffer.shift();
119
+ }
120
+ }
121
+ }
@@ -0,0 +1,254 @@
1
+ /**
2
+ * core/browser - Browser lifecycle and utilities
3
+ *
4
+ * All browser-related logic centralized here:
5
+ * - Lifecycle (connect/disconnect/spawn/kill)
6
+ * - Page utilities (getActivePage)
7
+ * - Proxy bypass
8
+ * - Profile management
9
+ */
10
+
11
+ import type { Browser, Page } from 'puppeteer-core';
12
+ import { spawn, execSync } from 'child_process';
13
+ import { existsSync, mkdirSync, symlinkSync } from 'fs';
14
+ import { homedir } from 'os';
15
+ import * as path from 'path';
16
+
17
+ // ─── Connection mode ───
18
+ export type ConnectionMode = 'local' | 'remote';
19
+
20
+ // ─── Configurable state ───
21
+ let connectionMode: ConnectionMode = 'local';
22
+ let debugHost: string = '127.0.0.1';
23
+ let debugPort: number = 9222;
24
+
25
+ // ─── Chrome paths (local mode only) ───
26
+ const CHROME_PATH = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
27
+ const DEFAULT_PROFILE_DIR = path.join(homedir(), 'Library/Application Support/Google/Chrome');
28
+ export const DEBUG_PROFILE_DIR = path.join(homedir(), 'Library/Application Support/Google/Chrome-Debug');
29
+
30
+ // ─── Module state ───
31
+ let browser: Browser | null = null;
32
+
33
+ // ─── 断线回调 ───
34
+ let disconnectedCallback: (() => void) | null = null;
35
+
36
+ // ─── 健康检查缓存 ───
37
+ let lastHealthCheckTime = 0;
38
+ let lastHealthCheckResult = true;
39
+ const HEALTH_CHECK_TTL_MS = 5000; // 5 秒内有效
40
+
41
+ // ─── Connection configuration ───
42
+ export function configureConnection(options: {
43
+ mode?: ConnectionMode;
44
+ host?: string;
45
+ port?: number;
46
+ }): void {
47
+ if (options.mode !== undefined) connectionMode = options.mode;
48
+ if (options.host !== undefined) debugHost = options.host;
49
+ if (options.port !== undefined) debugPort = options.port;
50
+ }
51
+
52
+ export function getMode(): ConnectionMode {
53
+ return connectionMode;
54
+ }
55
+
56
+ export function getDebugUrl(): string {
57
+ return `http://${debugHost}:${debugPort}`;
58
+ }
59
+
60
+ export function isConnected(): boolean {
61
+ return browser !== null;
62
+ }
63
+
64
+ export function getBrowser(): Browser {
65
+ if (!browser) throw new Error('Chrome 未连接,请先执行 /chrome-start');
66
+ return browser;
67
+ }
68
+
69
+ /** 注册断线回调(由 index.ts 调用,回调中无 pi ctx,只有 pi 可用) */
70
+ export function onDisconnected(cb: () => void): void {
71
+ disconnectedCallback = cb;
72
+ }
73
+
74
+ /** 健康检查:确认 browser 连接有效。失败则 throw 明确错误。 */
75
+ export async function ensureConnection(): Promise<void> {
76
+ if (!browser) {
77
+ throw new Error('Chrome 连接已断开,请执行 /chrome-start 重连');
78
+ }
79
+
80
+ // 利用缓存避免并发工具调用时重复 CDP 请求
81
+ const now = Date.now();
82
+ if (now - lastHealthCheckTime < HEALTH_CHECK_TTL_MS && lastHealthCheckResult) {
83
+ return;
84
+ }
85
+
86
+ try {
87
+ await browser.version();
88
+ lastHealthCheckTime = now;
89
+ lastHealthCheckResult = true;
90
+ } catch (error) {
91
+ lastHealthCheckResult = false;
92
+ console.error('[pi-to-chrome] ensureConnection: browser.version() 失败', error);
93
+ throw new Error('Chrome 连接已断开,请执行 /chrome-start 重连');
94
+ }
95
+ }
96
+
97
+ // ─── Proxy bypass ───
98
+ export async function fetchChromeDebug(urlPath: string, init?: RequestInit): Promise<Response> {
99
+ const prevNoProxy = process.env.NO_PROXY;
100
+ const prevNoProxy2 = process.env.no_proxy;
101
+ process.env.NO_PROXY = '127.0.0.1';
102
+ process.env.no_proxy = '127.0.0.1';
103
+ try {
104
+ return await fetch(`${getDebugUrl()}${urlPath}`, init);
105
+ } finally {
106
+ if (prevNoProxy !== undefined) process.env.NO_PROXY = prevNoProxy;
107
+ else delete process.env.NO_PROXY;
108
+ if (prevNoProxy2 !== undefined) process.env.no_proxy = prevNoProxy2;
109
+ else delete process.env.no_proxy;
110
+ }
111
+ }
112
+
113
+ // ─── Profile management ───
114
+ export function ensureDebugProfile(): void {
115
+ try {
116
+ if (!existsSync(DEBUG_PROFILE_DIR)) {
117
+ mkdirSync(DEBUG_PROFILE_DIR, { recursive: true });
118
+ }
119
+ const defaultDataDir = path.join(DEFAULT_PROFILE_DIR, 'Default');
120
+ const debugDefaultLink = path.join(DEBUG_PROFILE_DIR, 'Default');
121
+ if (!existsSync(debugDefaultLink) && existsSync(defaultDataDir)) {
122
+ symlinkSync(defaultDataDir, debugDefaultLink);
123
+ }
124
+ } catch (error: any) {
125
+ console.error('Failed to setup debug profile:', error.message);
126
+ }
127
+ }
128
+
129
+ // ─── Page utilities ───
130
+ /** 判断是否为 Chrome 内部页面(非用户页面) */
131
+ function isChromeInternalPage(url: string): boolean {
132
+ return url.startsWith('chrome://') ||
133
+ url.startsWith('chrome-search://') ||
134
+ url.startsWith('chrome-extension://') ||
135
+ url.startsWith('devtools://');
136
+ }
137
+
138
+ export async function getActivePage(): Promise<Page> {
139
+ const b = getBrowser();
140
+
141
+ try {
142
+ const pages = await b.pages();
143
+
144
+ // 优先从用户页面中找 visible 的
145
+ for (const page of pages) {
146
+ if (isChromeInternalPage(page.url())) continue;
147
+ try {
148
+ const visibilityState = await page.evaluate(() => document.visibilityState);
149
+ if (visibilityState === 'visible') {
150
+ return page;
151
+ }
152
+ } catch (error) {
153
+ console.error(`[pi-to-chrome] getActivePage: page.evaluate() 失败 (url=${page.url()})`, error);
154
+ }
155
+ }
156
+
157
+ // 没有 visible 的用户页面,返回第一个用户页面
158
+ const userPage = pages.find(p => !isChromeInternalPage(p.url()));
159
+ if (userPage) return userPage;
160
+
161
+ // 兜底:只有内部页面时返回第一个
162
+ if (pages.length > 0) {
163
+ return pages[0];
164
+ }
165
+ } catch (error) {
166
+ console.error('[pi-to-chrome] getActivePage: b.pages() 失败', error);
167
+ }
168
+
169
+ throw new Error('无法获取当前页面');
170
+ }
171
+
172
+ // ─── Lifecycle ───
173
+ export async function isChromeRunning(): Promise<boolean> {
174
+ try {
175
+ const response = await fetchChromeDebug('/json/version', {
176
+ signal: AbortSignal.timeout(1000)
177
+ });
178
+ return response.ok;
179
+ } catch {
180
+ return false;
181
+ }
182
+ }
183
+
184
+ export async function killChrome(): Promise<void> {
185
+ return new Promise((resolve) => {
186
+ const kill = spawn('pkill', ['-f', 'Google Chrome'], {
187
+ stdio: 'ignore',
188
+ shell: true
189
+ });
190
+ kill.on('close', async () => {
191
+ const deadline = Date.now() + 5000;
192
+ while (Date.now() < deadline) {
193
+ try {
194
+ const out = execSync('pgrep -f "Google Chrome"', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] });
195
+ if (!out.trim()) break;
196
+ } catch { break; }
197
+ await new Promise(r => setTimeout(r, 300));
198
+ }
199
+ resolve();
200
+ });
201
+ kill.on('error', () => resolve());
202
+ });
203
+ }
204
+
205
+ export function spawnChrome(): void {
206
+ ensureDebugProfile();
207
+
208
+ spawn(CHROME_PATH, [
209
+ '--remote-debugging-port=9222',
210
+ `--user-data-dir=${DEBUG_PROFILE_DIR}`,
211
+ '--restore-last-session'
212
+ ], {
213
+ detached: true,
214
+ stdio: 'ignore'
215
+ }).unref();
216
+ }
217
+
218
+ export async function waitForChromeReady(timeoutMs: number = 15000): Promise<boolean> {
219
+ const startTime = Date.now();
220
+ while (Date.now() - startTime < timeoutMs) {
221
+ if (await isChromeRunning()) {
222
+ return true;
223
+ }
224
+ await new Promise(r => setTimeout(r, 500));
225
+ }
226
+ return false;
227
+ }
228
+
229
+ export async function connectChrome(): Promise<Browser> {
230
+ const puppeteer = await import('puppeteer-core');
231
+ browser = await puppeteer.connect({ browserURL: getDebugUrl(), defaultViewport: null });
232
+
233
+ // 注册断线监听
234
+ browser.on('disconnected', () => {
235
+ console.error('[pi-to-chrome] browser disconnected 事件触发');
236
+ browser = null; // 直接置 null,不做任何 CDP 请求
237
+ lastHealthCheckResult = false; // 失效化健康检查缓存
238
+ disconnectedCallback?.(); // 通知 index.ts 做清理和通知
239
+ });
240
+
241
+ return browser;
242
+ }
243
+
244
+ export async function disconnectChrome(): Promise<void> {
245
+ if (browser) {
246
+ try {
247
+ browser.disconnect();
248
+ } catch (error) {
249
+ console.error('[pi-to-chrome] disconnectChrome: browser.disconnect() 失败', error);
250
+ }
251
+ browser = null;
252
+ lastHealthCheckResult = false;
253
+ }
254
+ }
package/core/types.ts ADDED
@@ -0,0 +1,29 @@
1
+ /**
2
+ * core/types - Shared types for chrome extension tools
3
+ */
4
+
5
+ import type { Page } from 'puppeteer-core';
6
+ import type { ConsoleBuffer } from '../console-buffer';
7
+ import type { TSchema } from '@sinclair/typebox';
8
+
9
+ // Tool unified return format
10
+ export interface ToolResult {
11
+ content: [{ type: 'text'; text: string }];
12
+ details: Record<string, any>;
13
+ }
14
+
15
+ // Tool dependencies (injected at call time)
16
+ export interface ToolDeps {
17
+ consoleBuffer: ConsoleBuffer;
18
+ }
19
+
20
+ // Tool definition object (generic preserves parameter types)
21
+ export interface ToolDefinition<TParams = any> {
22
+ name: string;
23
+ label: string;
24
+ description: string;
25
+ promptSnippet: string;
26
+ promptGuidelines: string[];
27
+ parameters: TSchema;
28
+ execute: (page: Page, params: TParams, deps?: ToolDeps) => Promise<ToolResult>;
29
+ }
package/index.ts ADDED
@@ -0,0 +1,173 @@
1
+ /**
2
+ * chrome-inspect - Chrome Browser Inspection Extension for pi
3
+ *
4
+ * Provides tools to inspect DOM, CSS styles, console logs, and execute JS
5
+ * in a Chrome browser connected via remote debugging port.
6
+ */
7
+
8
+ import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
9
+
10
+ import { ConsoleBuffer } from './console-buffer';
11
+ import * as browser from './core/browser';
12
+ import { registerTools } from './tool-registry';
13
+
14
+ export default async function(pi: ExtensionAPI) {
15
+ const consoleBuffer = new ConsoleBuffer();
16
+ let toolNames: string[] = [];
17
+
18
+ // ─── /chrome-start ───
19
+ pi.registerCommand('chrome-start', {
20
+ description: '连接 Chrome 浏览器并启用页面检查工具 (--remote 用于 SSH 隧道连接)',
21
+ handler: async (args: any, ctx: any) => {
22
+ // Parse args: --remote, --host <value>, --port <value>
23
+ const argStr = typeof args === 'string' ? args : '';
24
+ const argParts = argStr.trim().split(/\s+/);
25
+ let isRemote = false;
26
+ let host = '127.0.0.1';
27
+ let port = 9222;
28
+
29
+ for (let i = 0; i < argParts.length; i++) {
30
+ const part = argParts[i];
31
+ if (part === '--remote' || part === '-r') {
32
+ isRemote = true;
33
+ } else if (part === '--host' && i + 1 < argParts.length) {
34
+ host = argParts[++i];
35
+ } else if (part === '--port' && i + 1 < argParts.length) {
36
+ const parsed = parseInt(argParts[++i], 10);
37
+ if (isNaN(parsed) || parsed < 1 || parsed > 65535) {
38
+ ctx.ui.notify('❌ 无效端口号,范围: 1-65535', 'error');
39
+ return;
40
+ }
41
+ port = parsed;
42
+ }
43
+ }
44
+
45
+ // Configure connection mode (always reset all fields to avoid stale state)
46
+ browser.configureConnection({
47
+ mode: isRemote ? 'remote' : 'local',
48
+ host: isRemote ? host : '127.0.0.1',
49
+ port: isRemote ? port : 9222,
50
+ });
51
+
52
+ ctx.ui.notify(isRemote ? '正在连接远程 Chrome...' : '正在连接 Chrome...', 'info');
53
+
54
+ try {
55
+ try {
56
+ await browser.connectChrome();
57
+ ctx.ui.notify('✅ 已连接到运行中的 Chrome', 'info');
58
+ } catch {
59
+ if (isRemote) {
60
+ ctx.ui.notify(
61
+ '❌ 无法连接到远程 Chrome,请确认:\n' +
62
+ '1. Mac 上 Chrome 已启动(--remote-debugging-port=9222)\n' +
63
+ '2. SSH 隧道已建立(ssh -R 9222:127.0.0.1:9222 ...)',
64
+ 'error'
65
+ );
66
+ return;
67
+ }
68
+
69
+ if (await browser.isChromeRunning()) {
70
+ const ok = await ctx.ui.confirm(
71
+ '关闭 Chrome?',
72
+ '需要关闭现有 Chrome,未保存内容可能丢失。是否继续?'
73
+ );
74
+ if (!ok) {
75
+ ctx.ui.notify('已取消', 'warning');
76
+ return;
77
+ }
78
+ await browser.killChrome();
79
+ }
80
+
81
+ browser.spawnChrome();
82
+
83
+ if (!(await browser.waitForChromeReady())) {
84
+ ctx.ui.notify('Chrome 启动超时,请手动检查', 'error');
85
+ return;
86
+ }
87
+
88
+ await browser.connectChrome();
89
+ ctx.ui.notify('✅ Chrome 启动成功', 'info');
90
+ }
91
+
92
+ // 注册断线回调(在 startListening/registerTools 之前,消除 race window)
93
+ browser.onDisconnected(() => {
94
+ if (toolNames.length === 0) return;
95
+
96
+ consoleBuffer.stopListening();
97
+ const allActive = pi.getActiveTools();
98
+ const remaining = allActive.filter((name: string) => !toolNames.includes(name));
99
+ pi.setActiveTools(remaining);
100
+ toolNames = [];
101
+
102
+ pi.sendMessage({
103
+ customType: 'chrome-disconnected',
104
+ content: '⚠️ Chrome 连接已断开!\n如需查看原因,请立即切换到 Chrome 查看 console 日志\n如需重新连接,请执行 /chrome-start',
105
+ display: true,
106
+ });
107
+ });
108
+
109
+ consoleBuffer.startListening(browser.getBrowser());
110
+
111
+ toolNames = registerTools(pi, consoleBuffer);
112
+ // 将新注册的工具加入 active tools
113
+ const currentActive = pi.getActiveTools();
114
+ pi.setActiveTools([...currentActive, ...toolNames]);
115
+ ctx.ui.notify('✅ Chrome 检查工具已就绪(4 个工具已注册)', 'info');
116
+
117
+ } catch (err: any) {
118
+ ctx.ui.notify('❌ 连接失败: ' + err.message, 'error');
119
+ }
120
+ }
121
+ });
122
+
123
+ // ─── /chrome-stop ───
124
+ pi.registerCommand('chrome-stop', {
125
+ description: '断开 Chrome 连接并关闭浏览器',
126
+ handler: async (args: any, ctx: any) => {
127
+ const isRemote = browser.getMode() === 'remote';
128
+
129
+ if (isRemote) {
130
+ // 远程模式:只断开连接,不关闭 Chrome
131
+ const allActive = pi.getActiveTools();
132
+ const remaining = allActive.filter((name: string) => !toolNames.includes(name));
133
+ pi.setActiveTools(remaining);
134
+ toolNames = [];
135
+
136
+ consoleBuffer.stopListening();
137
+ await browser.disconnectChrome();
138
+
139
+ ctx.ui.notify('✅ 已断开远程 Chrome 连接(浏览器未关闭)', 'info');
140
+ } else {
141
+ // 本地模式:断开并关闭
142
+ const ok = await ctx.ui.confirm(
143
+ '关闭 Chrome?',
144
+ '将关闭 Chrome 浏览器,是否继续?'
145
+ );
146
+ if (!ok) {
147
+ ctx.ui.notify('已取消', 'warning');
148
+ return;
149
+ }
150
+
151
+ const allActive = pi.getActiveTools();
152
+ const remaining = allActive.filter((name: string) => !toolNames.includes(name));
153
+ pi.setActiveTools(remaining);
154
+ toolNames = [];
155
+
156
+ consoleBuffer.stopListening();
157
+ await browser.disconnectChrome();
158
+ await browser.killChrome();
159
+
160
+ ctx.ui.notify('✅ Chrome 已断开并关闭', 'info');
161
+ }
162
+ }
163
+ });
164
+
165
+ // ─── session_shutdown cleanup ───
166
+ pi.on('session_shutdown', async () => {
167
+ if (browser.isConnected()) {
168
+ toolNames = []; // 先清空,防止 disconnectChrome 触发断线回调发送误导通知
169
+ consoleBuffer.stopListening();
170
+ await browser.disconnectChrome();
171
+ }
172
+ });
173
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "pi-to-chrome",
3
+ "version": "0.1.2",
4
+ "description": "The tinest Pi extension for Chrome — Only 4 tools what you need.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/sollawen/pi-to-chrome.git"
8
+ },
9
+ "bugs": {
10
+ "url": "https://github.com/sollawen/pi-to-chrome/issues"
11
+ },
12
+ "homepage": "https://github.com/sollawen/pi-to-chrome#readme",
13
+ "keywords": [
14
+ "pi-extension",
15
+ "pi-package",
16
+ "chrome",
17
+ "devtools",
18
+ "puppeteer"
19
+ ],
20
+ "type": "module",
21
+ "dependencies": {
22
+ "@sinclair/typebox": "^0.34.49",
23
+ "puppeteer-core": "^24.0.0"
24
+ },
25
+ "peerDependencies": {
26
+ "@earendil-works/pi-coding-agent": "*"
27
+ },
28
+ "files": [
29
+ "index.ts",
30
+ "core/",
31
+ "tools/",
32
+ "tool-registry.ts",
33
+ "console-buffer.ts"
34
+ ],
35
+ "pi": {
36
+ "extensions": [
37
+ "./index.ts"
38
+ ]
39
+ },
40
+ "devDependencies": {
41
+ "typescript": "^6.0.3"
42
+ }
43
+ }