Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added apps/chrome-extension/icons/icon-128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/chrome-extension/icons/icon-16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/chrome-extension/icons/icon-32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/chrome-extension/icons/icon-48.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 12 additions & 2 deletions apps/chrome-extension/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,20 @@
"manifest_version": 3,
"name": "Open Browser Use",
"description": "Open Browser Use Chrome automation extension.",
"version": "0.1.2",
"version": "0.1.3",
"icons": {
"16": "icons/icon-16.png",
"32": "icons/icon-32.png",
"48": "icons/icon-48.png",
"128": "icons/icon-128.png"
},
"action": {
"default_title": "Open Browser Use",
"default_popup": "popup.html"
"default_popup": "popup.html",
"default_icon": {
"16": "icons/icon-16.png",
"32": "icons/icon-32.png"
}
},
"background": {
"service_worker": "background.js",
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@codio/desktop",
"version": "0.1.2",
"version": "0.1.3",
"private": true,
"description": "Codio Electron desktop app.",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/main/browser-use/iab-backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export class IabBackend {
const sessionId = readOptionalString(params, "session_id");
return {
name: "Codio",
version: "0.1.2",
version: "0.1.3",
type: "iab",
capabilities: {
downloads: false,
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/main/codex-app-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ export class CodexAppServerClient extends EventEmitter {
clientInfo: {
name: "codio",
title: "Codio",
version: "0.1.2"
version: "0.1.3"
},
capabilities: {
experimentalApi: true
Expand Down
2 changes: 1 addition & 1 deletion cmd/open-browser-use/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
"github.com/spf13/cobra"
)

const version = "0.1.2"
const version = "0.1.3"

func main() {
if err := run(os.Args[1:]); err != nil {
Expand Down
2 changes: 2 additions & 0 deletions docs/CHROME_WEB_STORE_RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ pnpm package:chrome-extension
- `manifest_version` 必须是 3。
- `background.service_worker` 必须指向 `background.js`。
- manifest 必须包含 `nativeMessaging` 权限。
- manifest 必须声明 `16`、`32`、`48`、`128` 四个 PNG icons,并把 toolbar
action icon 指向 `16` 和 `32` 图标。
- `background.js`、`content-cursor.js`、`popup.js` 必须通过 `node --check`。

输出文件:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,6 @@ and SDKs that let upper-layer runtimes call the browser backend directly.
origin argv.
- 2026-05-08: Open Browser Use package, extension, CLI, SDK, and runtime
self-reported versions bumped to `0.1.2` for the CLI startup patch release.
- 2026-05-08: Open Browser Use package, extension, CLI, SDK, and runtime
self-reported versions bumped to `0.1.3` for the Chrome Web Store icon-ready
patch release.
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ upload and publish behavior.
- `package.json`
- `scripts/ci.sh`
- `scripts/chrome-web-store-oauth.mjs`
- `scripts/generate-chrome-extension-icons.mjs`
- `scripts/package-chrome-extension.sh`
- `scripts/publish-chrome-web-store.mjs`
- `scripts/release-package.sh`
Expand All @@ -73,3 +74,7 @@ upload and publish behavior.
time of release.
- Added a local OAuth helper for generating the Chrome Web Store API refresh
token with a loopback callback, keeping the token out of repository files.
- Added deterministic Chrome extension PNG icons and included them in the
manifest, toolbar action, package zip, and CI packaging checks.
- Bumped Open Browser Use versions from `0.1.2` to `0.1.3` so the next release
asset contains the icon-ready Chrome extension package.
1 change: 1 addition & 0 deletions docs/releases/feature-release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

| 日期 | 功能域 | 用户价值 | 变更摘要 |
| --- | --- | --- | --- |
| 2026-05-08 | Chrome Extension Release | Chrome extension 发布包现在包含 manifest PNG icons,减少首次商店提交时的基础素材缺口。 | 发布 `0.1.3` patch 版本,补齐 Chrome extension `16`、`32`、`48`、`128` 图标和 toolbar icon 声明,并把图标纳入打包校验。 |
| 2026-05-08 | Chrome Extension Release | 开发者可以从 GitHub Release 拿到可安装的 Chrome extension zip,并在 release workflow 中选择是否提交 Chrome Web Store 审核。 | 新增 extension 打包脚本、Chrome Web Store API v2 上传/发布脚本、release workflow 输入和发布文档。 |
| 2026-05-08 | Open Browser Use Chrome Route | 手工运行 `obu` / `open-browser-use` 时可以直接看到版本和用法,不会误启动 native host 长驻进程;Chrome extension 仍可通过 native messaging 正常拉起 host。 | 发布 `0.1.2` patch 版本,收敛 CLI 启动体验:无参数输出版本化帮助,Chrome-provided `chrome-extension://...` argv 继续作为 MV3 native messaging host 启动信号。 |
| 2026-05-08 | Open Browser Use Chrome Route | 开发者可以通过开源 MV3 Chrome extension、Go native host/CLI、JS SDK 和 Python SDK 操作真实 Chrome profile,并使用 `obu`/`open-browser-use` 执行常用浏览器动作。 | 发布 `0.1.1` patch 版本,包含 Cobra CLI、Chrome native messaging host、active socket discovery、核心 Browser Use 方法、CDP/history/download/cursor 事件转发和真实 Chrome smoke 覆盖。 |
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"scripts": {
"dev": "pnpm --filter @codio/desktop dev",
"build": "pnpm -r --if-present build",
"generate:chrome-extension-icons": "node scripts/generate-chrome-extension-icons.mjs",
"package:chrome-extension": "./scripts/package-chrome-extension.sh",
"chrome-web-store:oauth": "node scripts/chrome-web-store-oauth.mjs",
"publish:chrome-web-store": "node scripts/publish-chrome-web-store.mjs",
Expand Down
2 changes: 1 addition & 1 deletion packages/browser-use-protocol/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@codio/browser-use-protocol",
"version": "0.1.2",
"version": "0.1.3",
"private": true,
"description": "Browser Use native pipe framing and JSON-RPC helpers.",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion packages/open-browser-use-js/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@open-browser-use/sdk-js",
"version": "0.1.2",
"version": "0.1.3",
"private": true,
"description": "JavaScript/TypeScript SDK for Open Browser Use.",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion packages/open-browser-use-python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "open-browser-use"
version = "0.1.2"
version = "0.1.3"
description = "Python SDK for Open Browser Use."
requires-python = ">=3.10"

Expand Down
2 changes: 2 additions & 0 deletions scripts/ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
"${repo_root}/scripts/check-docs.sh"
"${repo_root}/scripts/check-repo-hygiene.sh"
"${repo_root}/scripts/check-action-pinning.sh"
node "${repo_root}/scripts/generate-chrome-extension-icons.mjs"
"${repo_root}/scripts/package-chrome-extension.sh" >/dev/null
node --check "${repo_root}/scripts/chrome-web-store-oauth.mjs"
node --check "${repo_root}/scripts/generate-chrome-extension-icons.mjs"
node --check "${repo_root}/scripts/publish-chrome-web-store.mjs"

while IFS= read -r file; do
Expand Down
174 changes: 174 additions & 0 deletions scripts/generate-chrome-extension-icons.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
#!/usr/bin/env node

import { mkdir, writeFile } from "node:fs/promises";
import path from "node:path";
import process from "node:process";
import { deflateSync } from "node:zlib";

const repoRoot = path.resolve(import.meta.dirname, "..");
const iconDir = path.join(repoRoot, "apps/chrome-extension/icons");
const sizes = [16, 32, 48, 128];

const crcTable = new Uint32Array(256);
for (let n = 0; n < 256; n += 1) {
let c = n;
for (let k = 0; k < 8; k += 1) {
c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
}
crcTable[n] = c >>> 0;
}

function crc32(buffer) {
let crc = 0xffffffff;
for (const byte of buffer) {
crc = crcTable[(crc ^ byte) & 0xff] ^ (crc >>> 8);
}
return (crc ^ 0xffffffff) >>> 0;
}

function chunk(type, data) {
const typeBuffer = Buffer.from(type);
const length = Buffer.alloc(4);
length.writeUInt32BE(data.length, 0);
const checksum = Buffer.alloc(4);
checksum.writeUInt32BE(crc32(Buffer.concat([typeBuffer, data])), 0);
return Buffer.concat([length, typeBuffer, data, checksum]);
}

function encodePng(width, height, pixels) {
const ihdr = Buffer.alloc(13);
ihdr.writeUInt32BE(width, 0);
ihdr.writeUInt32BE(height, 4);
ihdr[8] = 8;
ihdr[9] = 6;
ihdr[10] = 0;
ihdr[11] = 0;
ihdr[12] = 0;

const scanlines = Buffer.alloc(height * (1 + width * 4));
for (let y = 0; y < height; y += 1) {
const target = y * (1 + width * 4);
scanlines[target] = 0;
pixels.copy(scanlines, target + 1, y * width * 4, (y + 1) * width * 4);
}

return Buffer.concat([
Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
chunk("IHDR", ihdr),
chunk("IDAT", deflateSync(scanlines, { level: 9 })),
chunk("IEND", Buffer.alloc(0))
]);
}

function parseHex(hex) {
const value = hex.replace("#", "");
return [
Number.parseInt(value.slice(0, 2), 16),
Number.parseInt(value.slice(2, 4), 16),
Number.parseInt(value.slice(4, 6), 16)
];
}

function setPixel(pixels, width, x, y, color) {
if (x < 0 || y < 0 || x >= width) {
return;
}
const offset = (y * width + x) * 4;
pixels[offset] = color[0];
pixels[offset + 1] = color[1];
pixels[offset + 2] = color[2];
pixels[offset + 3] = color[3];
}

function fillRoundedRect(pixels, width, height, x, y, w, h, radius, color) {
const left = Math.max(0, Math.floor(x));
const top = Math.max(0, Math.floor(y));
const right = Math.min(width, Math.ceil(x + w));
const bottom = Math.min(height, Math.ceil(y + h));
for (let py = top; py < bottom; py += 1) {
for (let px = left; px < right; px += 1) {
const cx = px < x + radius ? x + radius : px >= x + w - radius ? x + w - radius - 1 : px;
const cy = py < y + radius ? y + radius : py >= y + h - radius ? y + h - radius - 1 : py;
const dx = px - cx;
const dy = py - cy;
if (dx * dx + dy * dy <= radius * radius) {
setPixel(pixels, width, px, py, color);
}
}
}
}

function fillRect(pixels, width, height, x, y, w, h, color) {
const left = Math.max(0, Math.floor(x));
const top = Math.max(0, Math.floor(y));
const right = Math.min(width, Math.ceil(x + w));
const bottom = Math.min(height, Math.ceil(y + h));
for (let py = top; py < bottom; py += 1) {
for (let px = left; px < right; px += 1) {
setPixel(pixels, width, px, py, color);
}
}
}

function fillCircle(pixels, width, height, cx, cy, radius, color) {
const left = Math.max(0, Math.floor(cx - radius));
const top = Math.max(0, Math.floor(cy - radius));
const right = Math.min(width, Math.ceil(cx + radius));
const bottom = Math.min(height, Math.ceil(cy + radius));
const radiusSquared = radius * radius;
for (let y = top; y < bottom; y += 1) {
for (let x = left; x < right; x += 1) {
const dx = x - cx;
const dy = y - cy;
if (dx * dx + dy * dy <= radiusSquared) {
setPixel(pixels, width, x, y, color);
}
}
}
}

function drawIcon(logicalSize) {
const scale = 4;
const size = logicalSize * scale;
const pixels = Buffer.alloc(size * size * 4);
const color = (hex, alpha = 255) => [...parseHex(hex), alpha];

fillRoundedRect(pixels, size, size, 0, 0, size, size, size * 0.2, color("#111827"));
fillRoundedRect(pixels, size, size, size * 0.14, size * 0.16, size * 0.72, size * 0.56, size * 0.06, color("#f8fafc"));
fillRoundedRect(pixels, size, size, size * 0.18, size * 0.21, size * 0.64, size * 0.46, size * 0.04, color("#0f172a"));
fillRect(pixels, size, size, size * 0.18, size * 0.21, size * 0.64, size * 0.12, color("#38bdf8"));
fillCircle(pixels, size, size, size * 0.26, size * 0.27, size * 0.025, color("#f8fafc"));
fillCircle(pixels, size, size, size * 0.34, size * 0.27, size * 0.025, color("#f8fafc"));
fillCircle(pixels, size, size, size * 0.42, size * 0.27, size * 0.025, color("#f8fafc"));
fillRoundedRect(pixels, size, size, size * 0.28, size * 0.42, size * 0.32, size * 0.08, size * 0.025, color("#22c55e"));
fillRoundedRect(pixels, size, size, size * 0.28, size * 0.54, size * 0.44, size * 0.08, size * 0.025, color("#a78bfa"));
fillCircle(pixels, size, size, size * 0.68, size * 0.72, size * 0.16, color("#f8fafc"));
fillCircle(pixels, size, size, size * 0.68, size * 0.72, size * 0.09, color("#111827"));

const downsampled = Buffer.alloc(logicalSize * logicalSize * 4);
for (let y = 0; y < logicalSize; y += 1) {
for (let x = 0; x < logicalSize; x += 1) {
const totals = [0, 0, 0, 0];
for (let sy = 0; sy < scale; sy += 1) {
for (let sx = 0; sx < scale; sx += 1) {
const offset = ((y * scale + sy) * size + (x * scale + sx)) * 4;
totals[0] += pixels[offset];
totals[1] += pixels[offset + 1];
totals[2] += pixels[offset + 2];
totals[3] += pixels[offset + 3];
}
}
const target = (y * logicalSize + x) * 4;
downsampled[target] = Math.round(totals[0] / (scale * scale));
downsampled[target + 1] = Math.round(totals[1] / (scale * scale));
downsampled[target + 2] = Math.round(totals[2] / (scale * scale));
downsampled[target + 3] = Math.round(totals[3] / (scale * scale));
}
}
return encodePng(logicalSize, logicalSize, downsampled);
}

await mkdir(iconDir, { recursive: true });
for (const size of sizes) {
await writeFile(path.join(iconDir, `icon-${size}.png`), drawIcon(size));
}
19 changes: 19 additions & 0 deletions scripts/package-chrome-extension.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ const requiredFiles = [
"manifest.json",
"background.js",
"content-cursor.js",
"icons/icon-16.png",
"icons/icon-32.png",
"icons/icon-48.png",
"icons/icon-128.png",
"popup.css",
"popup.html",
"popup.js"
Expand All @@ -48,6 +52,17 @@ if (manifest.background?.service_worker !== "background.js") {
if (!manifest.permissions?.includes("nativeMessaging")) {
errors.push("permissions must include nativeMessaging");
}
for (const size of ["16", "32", "48", "128"]) {
if (manifest.icons?.[size] !== `icons/icon-${size}.png`) {
errors.push(`icons.${size} must be icons/icon-${size}.png`);
}
}
if (manifest.action?.default_icon?.["16"] !== "icons/icon-16.png") {
errors.push("action.default_icon.16 must be icons/icon-16.png");
}
if (manifest.action?.default_icon?.["32"] !== "icons/icon-32.png") {
errors.push("action.default_icon.32 must be icons/icon-32.png");
}
if (!/^\d+\.\d+\.\d+(?:\.\d+)?$/.test(manifest.version ?? "")) {
errors.push("version must use Chrome extension numeric version format");
}
Expand Down Expand Up @@ -77,6 +92,10 @@ mkdir -p "${dist_dir}"
manifest.json \
background.js \
content-cursor.js \
icons/icon-16.png \
icons/icon-32.png \
icons/icon-48.png \
icons/icon-128.png \
popup.css \
popup.html \
popup.js
Expand Down