mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
fix(daemon): reject unknown startup commands (#3934)
* fix(daemon): reject unknown startup commands * fix: make agent browser cleanup mac compatible * fix: reject flag-shaped startup option values --------- Co-authored-by: audit <a@b.c>
This commit is contained in:
@@ -17,6 +17,61 @@ type DaemonRuntimeOptions = Omit<StartServerOptions, 'returnServer'> & {
|
||||
logListening?: boolean;
|
||||
};
|
||||
|
||||
export type DaemonCliStartupConfig = {
|
||||
host: string;
|
||||
open: boolean;
|
||||
port: number;
|
||||
};
|
||||
|
||||
export type DaemonCliStartupParseResult =
|
||||
| { ok: true; config: DaemonCliStartupConfig }
|
||||
| { ok: false; kind: 'help' }
|
||||
| { ok: false; kind: 'error'; message: string };
|
||||
|
||||
function requiredOptionValue(flag: string, value: string | undefined, label: string): string | DaemonCliStartupParseResult {
|
||||
if (value == null || value.startsWith('-')) {
|
||||
return { ok: false, kind: 'error', message: `${flag} requires ${label}` };
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function parseDaemonCliStartupArgs(
|
||||
argv: string[],
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): DaemonCliStartupParseResult {
|
||||
let port = Number(env.OD_PORT) || 7456;
|
||||
let host = env.OD_BIND_HOST || '127.0.0.1';
|
||||
let open = true;
|
||||
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
const a = argv[i];
|
||||
if (a == null) continue;
|
||||
if (a === '-p' || a === '--port') {
|
||||
const next = requiredOptionValue(a, argv[++i], 'a port');
|
||||
if (typeof next !== 'string') return next;
|
||||
const parsedPort = Number(next);
|
||||
if (!Number.isInteger(parsedPort) || parsedPort <= 0 || parsedPort > 65535) {
|
||||
return { ok: false, kind: 'error', message: `invalid port: ${next}` };
|
||||
}
|
||||
port = parsedPort;
|
||||
} else if (a === '--host') {
|
||||
const next = requiredOptionValue(a, argv[++i], 'an address');
|
||||
if (typeof next !== 'string') return next;
|
||||
host = next;
|
||||
} else if (a === '--no-open') {
|
||||
open = false;
|
||||
} else if (a === '-h' || a === '--help') {
|
||||
return { ok: false, kind: 'help' };
|
||||
} else if (a.startsWith('-')) {
|
||||
return { ok: false, kind: 'error', message: `unknown option: ${a}` };
|
||||
} else {
|
||||
return { ok: false, kind: 'error', message: `unknown command: od ${a}` };
|
||||
}
|
||||
}
|
||||
|
||||
return { ok: true, config: { host, open, port } };
|
||||
}
|
||||
|
||||
export async function closeHttpServer(
|
||||
server: Server,
|
||||
{ closeTimeoutMs = 5_000, idleCloseMs = 1_000 } = {},
|
||||
@@ -87,24 +142,17 @@ export async function startDaemonRuntime(options: DaemonRuntimeOptions = {}): Pr
|
||||
}
|
||||
|
||||
export async function runDaemonCliStartup(argv: string[], options: { printHelp?: () => void } = {}): Promise<void> {
|
||||
let port = Number(process.env.OD_PORT) || 7456;
|
||||
let host = process.env.OD_BIND_HOST || '127.0.0.1';
|
||||
let open = true;
|
||||
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
const a = argv[i];
|
||||
if (a === '-p' || a === '--port') {
|
||||
port = Number(argv[++i]);
|
||||
} else if (a === '--host') {
|
||||
const next = argv[++i];
|
||||
if (next != null) host = next;
|
||||
} else if (a === '--no-open') {
|
||||
open = false;
|
||||
} else if (a === '-h' || a === '--help') {
|
||||
const parsed = parseDaemonCliStartupArgs(argv);
|
||||
if (!parsed.ok) {
|
||||
if (parsed.kind === 'error') {
|
||||
console.error(parsed.message);
|
||||
options.printHelp?.();
|
||||
return;
|
||||
process.exit(2);
|
||||
}
|
||||
options.printHelp?.();
|
||||
return;
|
||||
}
|
||||
const { host, open, port } = parsed.config;
|
||||
|
||||
const runtime = await startDaemonRuntime({
|
||||
host,
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { parseDaemonCliStartupArgs } from '../src/daemon-startup.js';
|
||||
|
||||
describe('daemon startup CLI parsing', () => {
|
||||
it('parses the documented daemon startup flags', () => {
|
||||
expect(parseDaemonCliStartupArgs(['--host', '0.0.0.0', '--port', '8123', '--no-open'], {})).toEqual({
|
||||
ok: true,
|
||||
config: {
|
||||
host: '0.0.0.0',
|
||||
open: false,
|
||||
port: 8123,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('uses environment defaults when startup flags are omitted', () => {
|
||||
expect(parseDaemonCliStartupArgs([], { OD_BIND_HOST: '127.0.0.2', OD_PORT: '7345' })).toEqual({
|
||||
ok: true,
|
||||
config: {
|
||||
host: '127.0.0.2',
|
||||
open: true,
|
||||
port: 7345,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects browser snapshot instead of treating it as daemon startup', () => {
|
||||
expect(parseDaemonCliStartupArgs(['browser', 'snapshot', '--url', 'https://example.test/'], {})).toEqual({
|
||||
ok: false,
|
||||
kind: 'error',
|
||||
message: 'unknown command: od browser',
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects unknown daemon startup options', () => {
|
||||
expect(parseDaemonCliStartupArgs(['--url', 'https://example.test/'], {})).toEqual({
|
||||
ok: false,
|
||||
kind: 'error',
|
||||
message: 'unknown option: --url',
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects flag-shaped values for required daemon startup options', () => {
|
||||
expect(parseDaemonCliStartupArgs(['--host', '--no-open'], {})).toEqual({
|
||||
ok: false,
|
||||
kind: 'error',
|
||||
message: '--host requires an address',
|
||||
});
|
||||
expect(parseDaemonCliStartupArgs(['--port', '--no-open'], {})).toEqual({
|
||||
ok: false,
|
||||
kind: 'error',
|
||||
message: '--port requires a port',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -176,6 +176,10 @@ describe('listSkills', () => {
|
||||
expect(skill.body).toContain('Chrome crashed before CDP became available');
|
||||
expect(skill.body).toContain('command -v agent-browser');
|
||||
expect(skill.body).toContain('Open Design Smoke Path');
|
||||
expect(skill.body).toContain('`daemon-cli.mjs browser snapshot`');
|
||||
expect(skill.body).toContain('misinterpreted as daemon startup');
|
||||
expect(skill.body).toContain('trap cleanup_agent_browser EXIT INT TERM');
|
||||
expect(skill.body).toContain('pkill -f -- "--user-data-dir=${CHROME_USER_DATA_DIR}"');
|
||||
});
|
||||
|
||||
it('includes the DCF valuation, X research, and Last30Days research skills', async () => {
|
||||
|
||||
@@ -115,6 +115,13 @@ screenshots.
|
||||
`agent-browser open` before `agent-browser connect`; doing so can make the CLI
|
||||
auto-launch Chrome and re-enter the crash path.
|
||||
|
||||
Do not run Open Design's own daemon CLI as a browser automation tool. Commands
|
||||
such as `od browser snapshot`, `daemon-cli.mjs browser snapshot`, or
|
||||
`$OD_NODE_BIN $OD_BIN browser snapshot` are not valid browser tools; they can be
|
||||
misinterpreted as daemon startup and open an internal `127.0.0.1:<port>` service
|
||||
in the system browser. Use the external `agent-browser` CLI attached to CDP
|
||||
instead.
|
||||
|
||||
Use this sequence:
|
||||
|
||||
```bash
|
||||
@@ -164,13 +171,24 @@ export HOME=/tmp/agent-browser-home
|
||||
export AGENT_BROWSER_SESSION=od-local-preview
|
||||
```
|
||||
|
||||
When you start a temporary Chrome profile for this smoke path, close it before
|
||||
finishing the task. Prefer a shell trap around the whole smoke script:
|
||||
|
||||
```bash
|
||||
CHROME_USER_DATA_DIR=/tmp/od-agent-browser-chrome
|
||||
cleanup_agent_browser() {
|
||||
pkill -f -- "--user-data-dir=${CHROME_USER_DATA_DIR}" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup_agent_browser EXIT INT TERM
|
||||
```
|
||||
|
||||
With the Open Design preview at `http://127.0.0.1:17573/`, run:
|
||||
|
||||
```bash
|
||||
if ! curl -fsS http://127.0.0.1:9223/json/version | rg -q webSocketDebuggerUrl; then
|
||||
open -na "Google Chrome" --args \
|
||||
--remote-debugging-port=9223 \
|
||||
--user-data-dir=/tmp/od-agent-browser-chrome \
|
||||
--user-data-dir="$CHROME_USER_DATA_DIR" \
|
||||
--no-first-run \
|
||||
--no-default-browser-check
|
||||
|
||||
|
||||
Reference in New Issue
Block a user