@pmcai/codex-chat-widget
v0.3.8
Published
Embeddable React chat widget for a Codex harness — plus a zero-dependency standalone server and CLI.
Maintainers
Readme
@pmcai/codex-chat-widget
Embeddable React chat UI for a Codex harness — plus a zero-dependency standalone server
with a pmcai service CLI (start / stop / status).
@pmcai/codex-chat-widget
├─ widget → React chat UI (sessions, SSE streaming, model settings)
├─ server → node:http harness that spawns `codex exec` (0 dependencies)
├─ pmcai → service CLI: start / stop / restart / status / logs / check
└─ pmcai-widget → single-shot runner (foreground)The widget is frontend only: it never holds API keys and never runs Codex itself.
Quick start
npm install -g @pmcai/codex-chat-widget
export OPENROUTER_API_KEY=sk-or-...
pmcai start
pmcai status
pmcai logs -f
pmcai stopWithout a global install:
npx @pmcai/codex-chat-widget start● started pid 12345 http://127.0.0.1:4231
workspace: /home/you/.pmai/workspace
log : /home/you/.pmai/run/harness.logOpen http://127.0.0.1:4231 — the server ships its own chat page. It installs the Codex CLI
into ~/.pmai/node_modules automatically if it is missing.
Service commands
| Command | Behaviour |
|---------|-----------|
| pmcai start | Start in the background (detached, survives the terminal) |
| pmcai stop | Graceful stop — --force sends SIGKILL |
| pmcai restart | Stop then start |
| pmcai status | Running state, pid, URL, health — exit code 3 when stopped |
| pmcai logs | Print the daemon log — -f follows, -n N sets line count |
| pmcai run | Run in the foreground (Ctrl-C to stop) |
| pmcai check | Verify the Codex CLI, paths, keys, Node version |
start is idempotent, and stop falls back to a port lookup (lsof/ss) when the pidfile is
missing — so a service started by hand can still be stopped.
Options
--port <n> listen port (default 4231)
--host <addr> bind address (default 127.0.0.1)
--pmai-home <dir> runtime home (default ~/.pmai)
--workspace <dir> folder Codex edits (default <pmai-home>/workspace)
--data-dir <dir> sessions + settings storage (default <pmai-home>/data)
--token <secret> require Authorization: Bearer (default none)
--allowed-origin <l> CORS allowlist, comma-separated (default *)
--codex <path> codex executable (default auto-detect)
--model <id> default model (default cohere/north-mini-code:free)Programmatic use
import { createHarnessServer } from '@pmcai/codex-chat-widget/server'
const harness = await createHarnessServer({
port: 4231,
allowedOrigin: 'https://my-site.example',
token: process.env.CHAT_TOKEN,
})
console.log(await harness.listen())Quick start (widget in your own app)
npm install @pmcai/codex-chat-widget
npm install react react-dom react-markdown remark-gfmimport CodexChatWidget from '@pmcai/codex-chat-widget'
import '@pmcai/codex-chat-widget/styles.css'
export default function Page() {
return (
<div style={{ height: '100dvh' }}>
<CodexChatWidget
apiBase="https://pmcai.samsyncs.com"
storageKey="my-site"
title="Codex Harness"
/>
</div>
)
}Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| apiBase | string | '' (same origin) | Base URL of the harness server. Trailing / is stripped. |
| storageKey | string | 'codex-chat' | Namespace for the remembered active session in localStorage. Use a unique value per host site. |
| initialSessionId | string | — | Session to open on first mount. |
| title | string | 'Codex Harness' | Header title. |
Styles are scoped under .codex-chat-app, so the widget will not leak CSS into your site.
Server API
GET /api/health
GET /api/sessions POST /api/sessions
GET /api/sessions?id=... PATCH /api/sessions DELETE /api/sessions?id=...
GET /api/settings PUT /api/settings
GET /api/models POST /api/models
POST /api/chat (SSE: status / final / error)
GET /api/v1/models POST /api/v1/responses (OpenAI-compatible Gemini proxy)// GET /api/sessions -> [{ id, title, updatedAt }]
// GET /api/sessions?id=... -> { id, title, messages: [{ role: "user"|"assistant"|"system", text }] }
// POST /api/chat { sessionId, prompt } -> text/event-stream
// data: {"type":"status","text":"..."} // progress step
// data: {"type":"final","text":"..."} // final answer
// data: {"type":"error","text":"..."} // failureProvider modes
| provider | Behaviour |
|------------|-----------|
| server | Uses OPENROUTER_API_KEY from the server environment (default) |
| openrouter | Uses the API key entered in the Settings dialog |
| compatible | Any OpenAI-compatible base URL + key |
| google-ai-proxy | Routes through the built-in /api/v1/responses Gemini adapter |
GOOGLE_API_KEY enables the Gemini paths; CODEX_CHAT_ALLOWED_ORIGIN and CODEX_CHAT_TOKEN
mirror --allowed-origin / --token.
CORS
Every /api/* route answers OPTIONS preflight and returns Access-Control-Allow-Origin only
for origins on the allowlist. Requests with no Origin header (same-origin fetches, direct
navigation, <iframe> embedding) are always allowed.
Runtime layout
~/.pmai/
├─ .codex/ # Codex state + per-run profiles
├─ node_modules/ # @openai/codex CLI
├─ workspace/ # the folder Codex reads and edits
└─ data/ # session JSON + settings SQLiteOverride the root with --pmai-home. Requires Node 22.5+ (node:sqlite).
Tests
npm test # widget smoke render + 28 server + 26 daemon tests (60 checks)
pmcai check # verify the Codex CLI, paths, keys, Node versionThe server suite boots a real harness on a temp home, installs a fake codex shim, and drives
every endpoint — sessions CRUD, settings round-trip, CORS allow/deny/preflight, SSE streaming,
persistence, failure paths, static assets. The daemon suite covers start/stop/restart/status,
pidfile handling, stale pids, idempotency, and the port-lookup fallback.
License
MIT
