Back to skills
SKILL.md
Chromex Ai Chrome Assistant
ASecurity> Skill by [ara.so](https://ara.so) — Daily 2026 Skills collection. Chromex is a Chrome MV3 side-panel extension that bridges the browser to OpenAI Codex through a local native messaging host. It supports chat with page content, selected tabs, uploaded files (PDF, DOCX, XLSX, images), voice transcription, live voice mode, screenshot workflows, and browser-control via content scripts — all without storing credentials in extension storage. ---
- 92 stars
- 0 votes
- 0 copies
- 0 views
- Added September 8, 2026
Works with
Security analysis
96/100- Installs packages at runtime which could introduce malicious dependencies
npx -y skills add Aradotso/trending-skills --skill chromex-ai-chrome-assistant --agent claude-codeAre you the author of Chromex Ai Chrome Assistant?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/aradotso-chromex-ai-chrome-assistant)```markdown
---
name: chromex-ai-chrome-assistant
description: Chromex is a Chrome MV3 side-panel assistant that connects Chrome to OpenAI Codex through a local native bridge, enabling page context, tab, voice, image, and browser-control workflows.
triggers:
- set up chromex chrome extension
- add chromex side panel assistant
- connect codex to chrome extension
- build chrome side panel with codex
- chromex native bridge setup
- install chromex local bridge
- chromex page context workflow
- chromex voice image tab assistant
---
# Chromex AI Chrome Assistant
> Skill by [ara.so](https://ara.so) — Daily 2026 Skills collection.
Chromex is a Chrome MV3 side-panel extension that bridges the browser to OpenAI Codex through a local native messaging host. It supports chat with page content, selected tabs, uploaded files (PDF, DOCX, XLSX, images), voice transcription, live voice mode, screenshot workflows, and browser-control via content scripts — all without storing credentials in extension storage.
---
## Architecture
```
Chrome Extension (MV3 Side Panel)
└─> Native Messaging Host (packages/native-host)
└─> Local Bridge Daemon (packages/bridge)
└─> codex app-server (OpenAI Codex CLI)
```
**Packages:**
| Package | Purpose |
|---|---|
| `packages/extension` | Chrome MV3 side-panel UI (TypeScript + React) |
| `packages/bridge` | Local bridge daemon: Codex app-server, multimodal workflows |
| `packages/native-host` | Chrome Native Messaging relay process |
| `packages/shared` | Shared types, policies, profiles, helpers |
---
## Prerequisites
- Node.js 20 LTS or newer
- Google Chrome (stable or dev)
- OpenAI Codex CLI: `npm install -g @openai/codex`
- Verify: `codex --version`
---
## Install & Build
### Clone and Build
```bash
git clone https://github.com/GENEXIS-AI/chromex.git
cd chromex
npm install
npm run build
```
### Install the Native Messaging Host
```bash
# macOS / Linux
node scripts/install-native-host.mjs
# Windows (PowerShell)
node scripts/install-native-host.mjs --browser=chrome
```
If Chrome assigns a non-standard extension ID, pass it explicitly:
```bash
node scripts/install-native-host.mjs <extension-id> --browser=chrome
```
The expected public release extension ID is `menmlhahmendmkiicbjihgjhppkgaeom`.
### Load Extension in Chrome
1. Open `chrome://extensions`
2. Enable **Developer mode**
3. Click **Load unpacked**
4. Select `packages/extension/dist`
---
## Key CLI / NPM Commands
```bash
npm run build # Build all packages
npm run typecheck # TypeScript type-check all packages
npm run test # Run unit tests
npm run release:audit # Pre-release audit checks
npm run smoke # Browser smoke test (Playwright)
npm run smoke:install-browser # Install Playwright Chromium runtime
```
---
## Configuration
### Windows: Codex Binary Path
If `codex` is not found on PATH, set the executable path in Chromex Settings:
- **Codex binary path:** `%APPDATA%\npm\codex.cmd`
- **Codex binary folder:** `%APPDATA%\npm`
> Do not put your workspace folder in the Codex binary field. The workspace folder and executable path are separate settings.
### App UI Language
Chromex auto-detects the browser language. To override:
**Settings → General → App UI language**
Supported locales include: `en`, `ko`, `ja`, `zh-CN`, `ar`, `fr`, `de`, `es`, `pt`, `hi`, `vi`, `th`, `tr`, `uk`, and more.
### Chat History
Conversation history is **session-only by default**. Enable persistent local chat history in:
**Settings → Privacy → Persistent local chat history**
---
## Extension Package Structure
```
packages/extension/
├── src/
│ ├── background/ # Service worker, native messaging client
│ ├── sidepanel/ # React side-panel UI
│ │ ├── components/ # Chat, attachments, pickers, markdown
│ │ ├── hooks/ # Page context, voice, tab selection
│ │ └── pages/ # Main panel, onboarding, settings
│ ├── content/ # Content scripts for page context & browser control
│ └── shared/ # Extension-local shared utilities
├── manifest.json
└── dist/ # Built output (load this in Chrome)
```
---
## Real Code Examples
### 1. Sending a Message to the Bridge (Native Messaging)
```typescript
// packages/extension/src/background/nativeClient.ts
interface BridgeMessage {
type: string;
payload: unknown;
requestId: string;
}
class NativeClient {
private port: chrome.runtime.Port | null = null;
connect(): void {
this.port = chrome.runtime.connectNative("com.genexisai.chromex.bridge");
this.port.onMessage.addListener((msg) => this.handleMessage(msg));
this.port.onDisconnect.addListener(() => {
this.port = null;
console.warn("Native host disconnected", chrome.runtime.lastError);
});
}
send(message: BridgeMessage): void {
if (!this.port) this.connect();
this.port!.postMessage(message);
}
private handleMessage(msg: unknown): void {
// Dispatch to side panel via chrome.runtime.sendMessage
chrome.runtime.sendMessage({ source: "native", data: msg });
}
}
export const nativeClient = new NativeClient();
```
### 2. Reading Page Context from a Content Script
```typescript
// packages/extension/src/content/pageContext.ts
export interface PageContext {
url: string;
title: string;
textContent: string;
selectedText: string;
metaDescription: string;
}
export function extractPageContext(): PageContext {
const selectedText = window.getSelection()?.toString() ?? "";
return {
url: location.href,
title: document.title,
textContent: document.body.innerText.slice(0, 20000), // truncate for context window
selectedText,
metaDescription:
document
.querySelector('meta[name="description"]')
?.getAttribute("content") ?? "",
};
}
// Listen for context requests from the background service worker
chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
if (msg.type === "GET_PAGE_CONTEXT") {
sendResponse(extractPageContext());
}
return true; // keep channel open for async
});
```
### 3. Requesting Page Context from the Background
```typescript
// packages/extension/src/background/contextBridge.ts
export async function getActiveTabContext(): Promise<PageContext | null> {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab?.id) return null;
try {
const context = await chrome.tabs.sendMessage(tab.id, {
type: "GET_PAGE_CONTEXT",
});
return context as PageContext;
} catch {
// Content script not injected yet — inject it
await chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ["content/pageContext.js"],
});
return chrome.tabs.sendMessage(tab.id, { type: "GET_PAGE_CONTEXT" });
}
}
```
### 4. Tab Picker — Selecting Multiple Tabs
```typescript
// packages/extension/src/sidepanel/hooks/useTabPicker.ts
import { useState, useEffect } from "react";
export interface TabItem {
id: number;
title: string;
url: string;
favIconUrl?: string;
}
export function useTabPicker() {
const [tabs, setTabs] = useState<TabItem[]>([]);
const [selected, setSelected] = useState<Set<number>>(new Set());
useEffect(() => {
chrome.tabs.query({ currentWindow: true }, (chromeTabs) => {
setTabs(
chromeTabs
.filter((t) => t.id !== undefined)
.map((t) => ({
id: t.id!,
title: t.title ?? "",
url: t.url ?? "",
favIconUrl: t.favIconUrl,
}))
);
});
}, []);
const toggle = (id: number) =>
setSelected((prev) => {
const next = new Set(prev);
next.has(id) ? next.delete(id) : next.add(id);
return next;
});
return { tabs, selected, toggle };
}
```
### 5. Bridge Daemon — Starting the Codex App-Server
```typescript
// packages/bridge/src/codexServer.ts
import { spawn, ChildProcess } from "child_process";
import path from "path";
export interface CodexServerConfig {
codexBin: string; // e.g. "codex" or full path
workspaceDir: string; // user workspace directory
port: number;
}
export class CodexAppServer {
private proc: ChildProcess | null = null;
start(config: CodexServerConfig): Promise<void> {
return new Promise((resolve, reject) => {
this.proc = spawn(
config.codexBin,
["app-server", "--port", String(config.port)],
{
cwd: config.workspaceDir,
env: {
// Reduced environment allowlist — never pass full process.env
PATH: process.env.PATH ?? "",
HOME: process.env.HOME ?? "",
USERPROFILE: process.env.USERPROFILE ?? "",
OPENAI_API_KEY: process.env.OPENAI_API_KEY ?? "", // optional local fallback
},
stdio: ["pipe", "pipe", "pipe"],
}
);
this.proc.stdout?.once("data", () => resolve());
this.proc.on("error", reject);
this.proc.stderr?.on("data", (d) =>
console.error("[codex-server]", d.toString())
);
});
}
stop(): void {
this.proc?.kill();
this.proc = null;
}
}
```
### 6. Shared Types — Chat Message
```typescript
// packages/shared/src/types/chat.ts
export type Role = "user" | "assistant" | "system";
export type AttachmentType =
| "image"
| "pdf"
| "docx"
| "xlsx"
| "csv"
| "text"
| "screenshot";
export interface Attachment {
type: AttachmentType;
name: string;
dataUrl?: string; // base64 data URI for images/screenshots
textContent?: string; // extracted text for documents
mimeType: string;
}
export interface ChatMessage {
id: string;
role: Role;
content: string;
attachments?: Attachment[];
timestamp: number;
pageContext?: {
url: string;
title: string;
};
}
```
### 7. Profile Selection with `/` Picker
```typescript
// packages/shared/src/profiles/types.ts
export interface CodexProfile {
id: string;
name: string;
systemPrompt: string;
model: string;
temperature?: number;
skillsEnabled: boolean;
skillPaths?: string[]; // paths to .codex/skills/*/SKILL.md
}
// packages/extension/src/sidepanel/hooks/useProfilePicker.ts
import { useState } from "react";
import type { CodexProfile } from "@chromex/shared/profiles/types";
export function useProfilePicker(profiles: CodexProfile[]) {
const [active, setActive] = useState<CodexProfile>(profiles[0]);
const [open, setOpen] = useState(false);
const select = (id: string) => {
const found = profiles.find((p) => p.id === id);
if (found) setActive(found);
setOpen(false);
};
return { active, open, setOpen, select };
}
```
### 8. Native Host Manifest (auto-generated by install script)
```json
{
"name": "com.genexisai.chromex.bridge",
"description": "Chromex local bridge native messaging host",
"path": "/absolute/path/to/packages/native-host/dist/host.js",
"type": "stdio",
"allowed_origins": [
"chrome-extension://menmlhahmendmkiicbjihgjhppkgaeom/"
]
}
```
On **macOS/Linux**, this file is placed at:
- `~/Library/Application Support/Google/Chrome/NativeMessagingHosts/` (macOS)
- `~/.config/google-chrome/NativeMessagingHosts/` (Linux)
On **Windows**, the path is written to the registry:
`HKCU\Software\Google\Chrome\NativeMessagingHosts\com.genexisai.chromex.bridge`
---
## Common Patterns
### Pattern: Page-Aware Chat Request
```typescript
// In the side-panel, combine page context with user message
async function sendPageAwareMessage(userText: string) {
const context = await chrome.runtime.sendMessage({ type: "GET_PAGE_CONTEXT" });
const payload = {
type: "CHAT",
messages: [
{
role: "system",
content: `Current page: ${context.title} (${context.url})\n\n${context.textContent}`,
},
{ role: "user", content: userText },
],
};
chrome.runtime.sendMessage({ type: "BRIDGE_SEND", payload });
}
```
### Pattern: Screenshot Attachment
```typescript
async function captureAndAttach(): Promise<Attachment> {
const dataUrl = await chrome.tabs.captureVisibleTab({ format: "png" });
return {
type: "screenshot",
name: "screenshot.png",
dataUrl,
mimeType: "image/png",
};
}
```
### Pattern: YouTube Adapter — Timestamp Context
```typescript
// packages/extension/src/content/adapters/youtube.ts
export function getYouTubeContext() {
const video = document.querySelector<HTMLVideoElement>("video");
const title =
document.querySelector("h1.ytd-video-primary-info-renderer")?.textContent ??
document.title;
return {
site: "youtube",
videoTitle: title.trim(),
currentTime: video ? Math.floor(video.currentTime) : null,
duration: video ? Math.floor(video.duration) : null,
url: location.href,
};
}
export function seekTo(seconds: number): void {
const video = document.querySelector<HTMLVideoElement>("video");
if (video) video.currentTime = seconds;
}
```
---
## Troubleshooting
| Problem | Fix |
|---|---|
| **Native host missing or forbidden** | Run `npm run build`, then `node scripts/install-native-host.mjs --browser=chrome`, reload extension in `chrome://extensions` |
| **Different extension ID** | Run `node scripts/install-native-host.mjs <your-extension-id> --browser=chrome` |
| **`Failed to start codex app-server`** | Run `codex --version`. On Windows, set Codex binary path to `%APPDATA%\npm\codex.cmd` in Chromex Settings |
| **Model list does not load** | Confirm native bridge is connected, then sign in through app-server login flow |
| **Page context unavailable** | Open Chromex from the target tab, approve the Chrome site permission prompt |
| **Old UI still showing** | Run `npm run build`, reload the extension card in `chrome://extensions` |
| **Smoke test fails: no browser** | Run `npm run smoke:install-browser`, then `npm run smoke` |
| **Windows: codex not found on PATH** | Set Codex binary folder to `%APPDATA%\npm` in Chromex Settings |
---
## Security Notes
- **Never** store raw `OPENAI_API_KEY` in Chrome extension storage or `manifest.json`.
- Credentials flow through the local Codex app-server OAuth/login, not extension storage.
- API-key login is an optional local fallback only, never automatic.
- Native-host child processes run with a **reduced environment allowlist** (see `CodexAppServer` example above).
- Page content, microphone input, and history are only accessed for user-requested workflows.
- Read [`SECURITY.md`](https://github.com/GENEXIS-AI/chromex/blob/main/SECURITY.md) and [`PRIVACY.md`](https://github.com/GENEXIS-AI/chromex/blob/main/PRIVACY.md) before deploying modified builds.
---
## Release & Contributing
- Versioning and PR flow: [`RELEASE.md`](https://github.com/GENEXIS-AI/chromex/blob/main/RELEASE.md)
- Pre-release check: `npm run release:audit`
- License: MIT
- Built extension output: `packages/extension/dist`
```
Attribution
Related skills
View all in development →Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.