Compare commits

...

4 Commits

Author SHA1 Message Date
daniel156161 1bf44c0eef update uv lock file
Testing / test (push) Successful in 28s
Package Extension / package-extension (push) Successful in 14s
Build & Publish Package / publish (push) Successful in 26s
2026-04-17 21:09:27 +02:00
daniel156161 cf0c9555d0 update version to 0.7.1
Testing / test (push) Successful in 28s
Package Extension / package-extension (push) Successful in 13s
Build & Publish Package / publish (push) Successful in 31s
2026-04-17 21:08:18 +02:00
daniel156161 a7da6cfab0 hardcode extension id and not prompt user
Testing / test (push) Has been cancelled
2026-04-17 21:07:30 +02:00
daniel156161 88b4f5ed11 add key generation script 2026-04-17 21:06:52 +02:00
5 changed files with 81 additions and 7 deletions
+4 -4
View File
@@ -36,6 +36,7 @@ from browser_cli.platform import install_base_dir, is_windows
console = Console() console = Console()
NATIVE_HOST_NAME = "com.browsercli.host" NATIVE_HOST_NAME = "com.browsercli.host"
EXTENSION_ID = "bfpmkhngkjnfhabmfckgeohlilokodkg"
NATIVE_HOST_DIRS = { NATIVE_HOST_DIRS = {
"chrome": { "chrome": {
@@ -284,7 +285,7 @@ def cmd_install(browser):
wrapper_content = f'@echo off\r\n"{sys.executable}" "{native_host_script_path}" %*\r\n' wrapper_content = f'@echo off\r\n"{sys.executable}" "{native_host_script_path}" %*\r\n'
wrapper_path.write_text(wrapper_content, encoding="utf-8") wrapper_path.write_text(wrapper_content, encoding="utf-8")
# Ask for extension ID # Load extension
ext_urls = { ext_urls = {
"chrome": "chrome://extensions", "chrome": "chrome://extensions",
"chromium": "chrome://extensions", "chromium": "chrome://extensions",
@@ -297,10 +298,9 @@ def cmd_install(browser):
console.print(f" 1. Open [cyan]{ext_url}[/cyan]") console.print(f" 1. Open [cyan]{ext_url}[/cyan]")
console.print(" 2. Enable [bold]Developer mode[/bold] (top-right toggle)") console.print(" 2. Enable [bold]Developer mode[/bold] (top-right toggle)")
console.print(f" 3. Click [bold]Load unpacked[/bold] → select: [cyan]{Path(__file__).parent.parent / 'extension'}[/cyan]") console.print(f" 3. Click [bold]Load unpacked[/bold] → select: [cyan]{Path(__file__).parent.parent / 'extension'}[/cyan]")
console.print(" 4. Copy the [bold]Extension ID[/bold] shown on the extension card\n") console.print(f" 4. Extension ID will be [cyan]{EXTENSION_ID}[/cyan] (fixed by built-in key)\n")
extension_id = click.prompt("Paste your extension ID here") extension_id = EXTENSION_ID
extension_id = extension_id.strip()
# Build native messaging manifest # Build native messaging manifest
manifest = { manifest = {
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "browser-cli", "name": "browser-cli",
"version": "0.7.0", "version": "0.7.1",
"description": "Control your browser from the terminal via browser-cli", "description": "Control your browser from the terminal via browser-cli",
"permissions": [ "permissions": [
"tabs", "tabs",
+1 -1
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "browser-cli" name = "browser-cli"
version = "0.7.0" version = "0.7.1"
description = "Control your real running browser from the terminal via a browser extension" description = "Control your real running browser from the terminal via a browser extension"
requires-python = ">=3.10" requires-python = ">=3.10"
dependencies = [ dependencies = [
+74
View File
@@ -0,0 +1,74 @@
#!/usr/bin/env -S uv run
"""Generate or derive Chrome extension key and ID.
Usage:
python scripts/gen_extension_key.py # generate new key pair
python scripts/gen_extension_key.py --from-manifest # derive ID from extension/manifest.json
python scripts/gen_extension_key.py --key <base64> # derive ID from given public key
"""
import argparse, hashlib
import base64, json, sys
from pathlib import Path
def public_key_to_extension_id(pub_key_der:bytes) -> str:
digest = hashlib.sha256(pub_key_der).hexdigest()
return "".join(chr(ord("a") + int(c, 16)) for c in digest[:32])
def derive_from_key_b64(key_b64:str) -> tuple[str, str]:
der = base64.b64decode(key_b64)
ext_id = public_key_to_extension_id(der)
return key_b64, ext_id
def generate_new_key() -> tuple[str, str, str]:
try:
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.serialization import (
Encoding,
PublicFormat,
PrivateFormat,
NoEncryption,
)
except ImportError:
print("Install 'cryptography' to generate new keys: pip install cryptography", file=sys.stderr)
sys.exit(1)
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
pub_der = private_key.public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)
priv_pem = private_key.private_bytes(Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption()).decode()
key_b64 = base64.b64encode(pub_der).decode()
ext_id = public_key_to_extension_id(pub_der)
return key_b64, ext_id, priv_pem
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Chrome extension key/ID tool")
group = parser.add_mutually_exclusive_group()
group.add_argument("--from-manifest", action="store_true", help="Derive ID from extension/manifest.json")
group.add_argument("--key", metavar="BASE64", help="Derive ID from given base64 public key")
args = parser.parse_args()
if args.from_manifest:
manifest_path = Path(__file__).parent.parent / "extension" / "manifest.json"
manifest = json.loads(manifest_path.read_text())
key_b64 = manifest.get("key")
if not key_b64:
print("No 'key' field in manifest.json", file=sys.stderr)
sys.exit(1)
key_b64, ext_id = derive_from_key_b64(key_b64)
print(f"Extension ID: {ext_id}")
print(f"Key (b64): {key_b64}")
elif args.key:
key_b64, ext_id = derive_from_key_b64(args.key)
print(f"Extension ID: {ext_id}")
else:
key_b64, ext_id, priv_pem = generate_new_key()
print(f"Extension ID: {ext_id}")
print(f"Key (b64): {key_b64}")
print()
print("Add this to extension/manifest.json:")
print(f' "key": "{key_b64}"')
print()
print("Private key (keep secret, needed to re-derive same ID):")
print(priv_pem)
Generated
+1 -1
View File
@@ -4,7 +4,7 @@ requires-python = ">=3.10"
[[package]] [[package]]
name = "browser-cli" name = "browser-cli"
version = "0.7.0" version = "0.7.1"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "click" }, { name = "click" },