Skip to content
Open
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ OpenCLI is not only for websites. It can also:
| Variable | Default | Description |
|----------|---------|-------------|
| `OPENCLI_DAEMON_PORT` | `19825` | HTTP port for the daemon-extension bridge |
| `OPENCLI_DAEMON_HOST` | `127.0.0.1` | Host the CLI uses to reach the daemon. Set to e.g. `host.docker.internal` to call a daemon running on the host from inside a container. |
| `OPENCLI_DAEMON_BIND` | `127.0.0.1` | Interface the daemon binds to. Default is loopback; set to `0.0.0.0` to accept connections from other hosts. The daemon has no built-in authentication, so restrict access with a firewall when binding off-loopback. |
| `OPENCLI_PROFILE` | — | Browser Bridge profile alias/contextId to use when multiple Chrome profiles are connected |
| `OPENCLI_WINDOW` | command default | Set to `foreground` or `background` to override Browser Bridge window placement. Browser-backed commands also accept `--window <foreground\|background>`. |
| `OPENCLI_KEEP_TAB` | command default | Set to `true` or `false` to keep or release the browser tab lease after a browser-backed adapter command. Browser-backed adapter commands also accept `--keep-tab <true\|false>`. |
Expand Down
2 changes: 2 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ OpenCLI 不只是网站 CLI,还可以:
| 变量 | 默认值 | 说明 |
|------|--------|------|
| `OPENCLI_DAEMON_PORT` | `19825` | daemon-extension 通信端口 |
| `OPENCLI_DAEMON_HOST` | `127.0.0.1` | CLI 连接 daemon 时使用的主机。例如在容器内调用宿主上的 daemon 时,设为 `host.docker.internal` |
| `OPENCLI_DAEMON_BIND` | `127.0.0.1` | daemon 监听的网卡地址。默认仅 loopback;设为 `0.0.0.0` 可接受外部连接。daemon 不带鉴权,绑非 loopback 时务必用防火墙限制访问 |
| `OPENCLI_WINDOW` | 命令默认值 | 设为 `foreground` 或 `background` 来覆盖 Browser Bridge 窗口位置。浏览器型命令也支持 `--window <foreground\|background>` |
| `OPENCLI_KEEP_TAB` | 命令默认值 | 设为 `true` 或 `false` 来控制浏览器型 adapter 命令结束后是否保留 tab lease。浏览器型 adapter 命令也支持 `--keep-tab <true\|false>` |
| `OPENCLI_BROWSER_CONNECT_TIMEOUT` | `30` | 浏览器连接超时(秒) |
Expand Down
25 changes: 25 additions & 0 deletions src/browser/daemon-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,31 @@ describe('daemon-client', () => {
await expect(fetchDaemonStatus()).resolves.toBeNull();
});

it('targets 127.0.0.1 by default', async () => {
const fetchMock = vi.mocked(fetch);
fetchMock.mockResolvedValue({
ok: true,
json: () => Promise.resolve({}),
} as Response);

await fetchDaemonStatus();

expect(fetchMock.mock.calls[0][0]).toMatch(/^http:\/\/127\.0\.0\.1:/);
});

it('targets OPENCLI_DAEMON_HOST when set', async () => {
vi.stubEnv('OPENCLI_DAEMON_HOST', 'host.docker.internal');
const fetchMock = vi.mocked(fetch);
fetchMock.mockResolvedValue({
ok: true,
json: () => Promise.resolve({}),
} as Response);

await fetchDaemonStatus();

expect(fetchMock.mock.calls[0][0]).toMatch(/^http:\/\/host\.docker\.internal:/);
});

it('requestDaemonShutdown POSTs to the shared shutdown endpoint', async () => {
const fetchMock = vi.mocked(fetch);
fetchMock.mockResolvedValue({ ok: true } as Response);
Expand Down
10 changes: 7 additions & 3 deletions src/browser/daemon-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ import { sleep } from '../utils.js';
import { classifyBrowserError } from './errors.js';
import { resolveProfileContextId } from './profile.js';

const DAEMON_PORT = parseInt(process.env.OPENCLI_DAEMON_PORT ?? String(DEFAULT_DAEMON_PORT), 10);
const DAEMON_URL = `http://127.0.0.1:${DAEMON_PORT}`;
const OPENCLI_HEADERS = { 'X-OpenCLI': '1' };

function getDaemonUrl(): string {
const port = parseInt(process.env.OPENCLI_DAEMON_PORT ?? String(DEFAULT_DAEMON_PORT), 10);
const host = process.env.OPENCLI_DAEMON_HOST ?? '127.0.0.1';
return `http://${host}:${port}`;
}

let _idCounter = 0;

function generateId(): string {
Expand Down Expand Up @@ -112,7 +116,7 @@ async function requestDaemon(pathname: string, init?: RequestInit & { timeout?:
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeout);
try {
return await fetch(`${DAEMON_URL}${pathname}`, {
return await fetch(`${getDaemonUrl()}${pathname}`, {
...rest,
headers: { ...OPENCLI_HEADERS, ...headers },
signal: controller.signal,
Expand Down
6 changes: 4 additions & 2 deletions src/daemon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -431,8 +431,10 @@ wss.on('connection', (ws: WebSocket) => {

// ─── Start ───────────────────────────────────────────────────────────

httpServer.listen(PORT, '127.0.0.1', () => {
log.info(`[daemon] Listening on http://127.0.0.1:${PORT}`);
const BIND = process.env.OPENCLI_DAEMON_BIND ?? '127.0.0.1';

httpServer.listen(PORT, BIND, () => {
log.info(`[daemon] Listening on http://${BIND}:${PORT}`);
});

httpServer.on('error', (err: NodeJS.ErrnoException) => {
Expand Down