diff --git a/README.md b/README.md
index b6b8170..0f2d430 100644
--- a/README.md
+++ b/README.md
@@ -63,7 +63,7 @@ pnpm run build
pnpm run test
# Lint code
-pnpm run lint
+pnpm run format
```
diff --git a/README.zh-CN.md b/README.zh-CN.md
index dc8a7d3..616111a 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -63,7 +63,7 @@ pnpm run build
pnpm run test
# 代码检查
-pnpm run lint
+pnpm run format
```
diff --git a/packages/browser-ui/README.md b/packages/browser-ui/README.md
index 6f68308..1bdfb1a 100644
--- a/packages/browser-ui/README.md
+++ b/packages/browser-ui/README.md
@@ -40,6 +40,33 @@ BrowserUI.create({
});
```
+Or use the unpkg CDN to use it on any webpage:
+
+- **CDN URL**: https://unpkg.com/@agent-infra/browser/dist/bundle/index.js
+
+```html
+
+
+
+
+
+
+
+
+```
+
A complete usable example, which can be run directly with `npm run dev` in the current directory or viewed in the `/examples` directory within the package.
## Features
diff --git a/packages/browser-ui/README.zh-CN.md b/packages/browser-ui/README.zh-CN.md
index ba8131c..9eb4e0b 100644
--- a/packages/browser-ui/README.zh-CN.md
+++ b/packages/browser-ui/README.zh-CN.md
@@ -40,8 +40,37 @@ BrowserUI.create({
});
```
+或者直接使用 unpkg CDN 在任意网页中使用:
+
+- **CDN URL**: https://unpkg.com/@agent-infra/browser/dist/bundle/index.js
+
+```html
+
+
+
+
+
+
+
+
+```
+
完整的可用示例,可直接在当前目录下运行 `npm run dev` 或查看包中的 `/examples` 目录。
+
+
## 核心功能
关于所有功能的详细文档和 API 参考,请访问我们的[完整文档](https://github.com/agent-infra/browser/blob/main/docs/browser-ui.zh-CN.md)。
diff --git a/packages/browser-ui/examples/bundle/boot.ts b/packages/browser-ui/examples/bundle/boot.ts
new file mode 100644
index 0000000..f798241
--- /dev/null
+++ b/packages/browser-ui/examples/bundle/boot.ts
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2025 Bytedance, Inc. and its affiliates.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+import puppeteer from 'puppeteer-core';
+import { createServer } from 'http';
+import { readFile, stat } from 'fs/promises';
+import { URL, fileURLToPath } from 'url';
+import { join, dirname } from 'path';
+
+async function main() {
+ console.log('🚀 launch ...');
+
+ const browser = await puppeteer.launch({
+ executablePath:
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
+ headless: false,
+ defaultViewport: {
+ width: 900,
+ height: 900,
+ deviceScaleFactor: 0,
+ },
+ args: [
+ '--mute-audio',
+ '--no-default-browser-check',
+ '--window-size=900,990',
+ '--remote-allow-origins=http://127.0.0.1:3000',
+ 'https://www.bytedance.com/en/',
+ ],
+ ignoreDefaultArgs: ['--enable-automation'],
+ });
+
+ const wsEndpoint = browser.wsEndpoint();
+
+ console.log('✅ launched successfully!');
+ console.log('📡 CDP WebSocket:', wsEndpoint);
+
+ const port = 3000;
+ const server = createServer(async (req, res) => {
+ try {
+ console.log(`Request: ${req.method} ${req.url}`);
+
+ if (req.url === '/' || req.url === '/index.html') {
+ const indexPath = new URL('./index.html', import.meta.url).pathname;
+ console.log(`Reading file: ${indexPath}`);
+
+ let html = await readFile(indexPath, 'utf-8');
+ console.log('File read successfully');
+
+ // Replace the import.meta.WSEndpoint with the actual wsEndpoint
+ html = html.replace(
+ 'import.meta.WSEndpoint',
+ JSON.stringify(wsEndpoint),
+ );
+
+ console.log('HTML length:', html.length);
+
+ res.writeHead(200, { 'Content-Type': 'text/html' });
+ res.end(html);
+ } else if (req.url?.startsWith('/dist/')) {
+ // Handle static files from /dist/ directory
+ const __filename = fileURLToPath(import.meta.url);
+ const __dirname = dirname(__filename);
+ const staticPath = join(
+ __dirname,
+ '..',
+ '..',
+ 'dist',
+ req.url.replace('/dist/', ''),
+ );
+
+ console.log(`Static file request: ${staticPath}`);
+
+ try {
+ const fileStat = await stat(staticPath);
+ if (fileStat.isFile()) {
+ const content = await readFile(staticPath);
+ res.writeHead(200, { 'Content-Type': 'application/javascript' });
+ res.end(content);
+ return;
+ }
+ } catch (staticError) {
+ console.error('Static file not found:', staticError);
+ }
+
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
+ res.end('Static file not found');
+ } else {
+ console.log('404 for:', req.url);
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
+ res.end('Not Found');
+ }
+ } catch (error) {
+ console.error('Server error:', error);
+ res.writeHead(500, { 'Content-Type': 'text/plain' });
+ res.end(`Internal Server Error: ${(error as Error).message}`);
+ }
+ });
+
+ server.listen(port, () => {
+ console.log(`🌐 Server running at http://localhost:${port}`);
+ console.log('🔄 Ctrl+C to exit...\n');
+ });
+
+ const cleanup = async () => {
+ try {
+ console.log('\n🛑 closing server and browser...');
+ if (server) {
+ server.close();
+ }
+ if (browser) {
+ await browser.close();
+ }
+ } catch (e) {
+ // ignore
+ } finally {
+ process.exit(0);
+ }
+ };
+
+ process.on('SIGINT', cleanup);
+ process.on('SIGTERM', cleanup);
+}
+
+main().catch(console.error);
diff --git a/packages/browser-ui/examples/bundle/index.html b/packages/browser-ui/examples/bundle/index.html
new file mode 100644
index 0000000..2639fee
--- /dev/null
+++ b/packages/browser-ui/examples/bundle/index.html
@@ -0,0 +1,47 @@
+
+
+
+
+
+ CanvasBrowser Demo
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/browser-ui/examples/boot.ts b/packages/browser-ui/examples/core/boot.ts
similarity index 100%
rename from packages/browser-ui/examples/boot.ts
rename to packages/browser-ui/examples/core/boot.ts
diff --git a/packages/browser-ui/package.json b/packages/browser-ui/package.json
index 455e447..e296e28 100644
--- a/packages/browser-ui/package.json
+++ b/packages/browser-ui/package.json
@@ -29,8 +29,8 @@
],
"scripts": {
"prepublishOnly": "pnpm run build",
- "build": "rslib build",
- "dev": "tsx examples/boot.ts",
+ "build": "rslib build && rslib build --config rslib.config.bundle.ts",
+ "dev": "tsx examples/core/boot.ts",
"test": "vitest run"
},
"dependencies": {
diff --git a/packages/browser-ui/rslib.config.bundle.ts b/packages/browser-ui/rslib.config.bundle.ts
new file mode 100644
index 0000000..7652857
--- /dev/null
+++ b/packages/browser-ui/rslib.config.bundle.ts
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2025 Bytedance, Inc. and its affiliates.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+// import { pluginReact } from '@rsbuild/plugin-react';
+import { defineConfig } from '@rslib/core';
+
+const BANNER = `/**
+* Copyright (c) 2025 Bytedance, Inc. and its affiliates.
+* SPDX-License-Identifier: Apache-2.0
+*/`;
+
+export default defineConfig({
+ source: {
+ entry: {
+ index: 'src/index.ts',
+ },
+ decorators: {
+ version: 'legacy',
+ },
+ },
+ lib: [
+ {
+ format: 'umd',
+ syntax: 'es2021',
+ bundle: true,
+ dts: false,
+ banner: { js: BANNER },
+ umdName: 'agent_infra_browser_ui',
+ output: {
+ minify: true,
+ externals: ['chromium-bidi/lib/cjs/bidiMapper/BidiMapper.js'],
+ distPath: {
+ root: 'dist/bundle/',
+ },
+ },
+ },
+ ],
+ // performance: {
+ // bundleAnalyze: {},
+ // },
+ output: {
+ target: 'web',
+ sourceMap: true,
+ },
+});
diff --git a/packages/browser-ui/rslib.config.ts b/packages/browser-ui/rslib.config.ts
index f3848d0..01aaba1 100644
--- a/packages/browser-ui/rslib.config.ts
+++ b/packages/browser-ui/rslib.config.ts
@@ -39,6 +39,5 @@ export default defineConfig({
target: 'web',
cleanDistPath: true,
sourceMap: true,
- externals: ['chromium-bidi/lib/cjs/bidiMapper/BidiMapper.js'],
},
});
diff --git a/packages/browser/src/actions/keyboard.ts b/packages/browser/src/actions/keyboard.ts
index 1070d17..a07d2d1 100644
--- a/packages/browser/src/actions/keyboard.ts
+++ b/packages/browser/src/actions/keyboard.ts
@@ -46,7 +46,11 @@ export class Keyboard {
const formattedHotkey = this.#formatHotkey(key);
if (this.#env.osName === 'macOS' && this.#env.browserName === 'Chrome') {
- const success = await this.#macOSCDPHotKey(formattedHotkey, options);
+ const success = await this.#macOSCDPHotKey(
+ formattedHotkey,
+ options,
+ true,
+ );
if (success) {
return { success: true };
}
@@ -78,7 +82,11 @@ export class Keyboard {
const formattedHotkey = this.#formatHotkey(key);
if (this.#env.osName === 'macOS' && this.#env.browserName === 'Chrome') {
- const success = await this.#macOSCDPHotKey(formattedHotkey, options);
+ const success = await this.#macOSCDPHotKey(
+ formattedHotkey,
+ options,
+ false,
+ );
if (success) {
return { success: true };
}
@@ -163,6 +171,7 @@ export class Keyboard {
async #macOSCDPHotKey(
keys: KeyInput[],
options: Readonly,
+ isPress: boolean,
): Promise {
const hotkey = keys
.map((key) => {
@@ -180,7 +189,10 @@ export class Keyboard {
commands: [command.commands],
});
await delay(options.delay ?? 0);
- await this.#page.keyboard.up(command.key);
+
+ if (isPress) {
+ await this.#page.keyboard.up(command.key);
+ }
return true;
}