feat(host): add headless op-host-web-server binary (Phase 6, Task 6.1)

A thin binary linking ONLY op-web-daemon — no winit/glutin/muda/accesskit-adapters/skia-GL.
Routes --serve-web/--mcp/--mcp-http argv to op_web_daemon::web_canvas_server::run_web_canvas +
op_web_daemon::mcp_serve::{run,run_http} (mirrors the desktop dispatcher, headless-only — no GUI
fallback). Added to root default-members. VERIFIED via cargo tree: 0 winit/glutin/casement/muda/
accesskit-adapters + skia-safe without gl (raster) — the web-server image now excludes all
desktop/GL code, answering the original question. (Lock also drops the actor-removed op-app crate.)
This commit is contained in:
Kayshen-X 2026-06-19 22:09:58 +08:00
parent c6743453b6
commit f198cdf76a
4 changed files with 100 additions and 10 deletions

16
Cargo.lock generated
View file

@ -3088,15 +3088,6 @@ dependencies = [
"include_dir",
]
[[package]]
name = "op-app"
version = "0.8.0"
dependencies = [
"op-editor-ui",
"op-host-native",
"op-host-web",
]
[[package]]
name = "op-cli"
version = "0.8.0"
@ -3307,6 +3298,13 @@ dependencies = [
"web-sys",
]
[[package]]
name = "op-host-web-server"
version = "0.8.0"
dependencies = [
"op-web-daemon",
]
[[package]]
name = "op-i18n"
version = "0.8.0"

View file

@ -21,7 +21,7 @@ exclude = [
# is always `cargo build -p op-host-web --target wasm32-unknown-unknown`. CI and
# the `package.json` cargo:* scripts are unaffected: they all pass `--workspace`
# explicitly, which overrides `default-members`.
default-members = ["crates/op-host-desktop", "crates/op-cli"]
default-members = ["crates/op-host-desktop", "crates/op-cli", "crates/op-host-web-server"]
[workspace.package]
version = "0.8.0"

View file

@ -0,0 +1,14 @@
[package]
name = "op-host-web-server"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
description = "OpenPencil headless web/MCP server binary — links op-web-daemon only, no winit/skia-GL"
[[bin]]
name = "op-host-web-server"
path = "src/main.rs"
[dependencies]
op-web-daemon = { path = "../op-web-daemon" }

View file

@ -0,0 +1,78 @@
//! Headless OpenPencil web / MCP server binary.
//!
//! Links only `op-web-daemon` (the extracted headless daemon) — no
//! winit / glutin / muda / accesskit adapters / skia-GL — so the
//! container / server image stays GUI-free. It mirrors the `--serve-web`
//! / `--mcp` / `--mcp-http` argv arms the desktop binary's
//! `run_cli_if_requested` dispatcher routes, but here that IS the whole
//! program: there is no GUI fallback, so an unknown / missing mode is a
//! usage error rather than "open the editor window".
use std::path::PathBuf;
use std::process::exit;
fn main() {
let mut args = std::env::args().skip(1);
let Some(first) = args.next() else {
eprintln!(
"op-host-web-server: missing mode. Usage:\n \
--serve-web <port> [doc] [--host <addr>] headless web-canvas daemon\n \
--mcp <path> JSON-RPC stdio MCP server\n \
--mcp-http <port> <path> Streamable-HTTP MCP server"
);
exit(2);
};
match first.as_str() {
"--serve-web" => {
// `--serve-web <port> [doc] [--host <addr>]`: doc optional
// (empty document otherwise); `--host` opts in to a
// non-loopback bind.
let (port, path, host) = op_web_daemon::web_canvas_server::parse_serve_web_args(args)
.unwrap_or_else(|e| {
eprintln!("op-host-web-server --serve-web: {e}");
exit(2);
});
if let Err(e) = op_web_daemon::web_canvas_server::run_web_canvas(path, port, &host) {
eprintln!("op-host-web-server --serve-web: {e}");
exit(1);
}
}
"--mcp" => {
let Some(path) = args.next() else {
eprintln!("op-host-web-server --mcp: missing <path> arg");
exit(2);
};
if let Err(e) = op_web_daemon::mcp_serve::run(PathBuf::from(path)) {
eprintln!("op-host-web-server --mcp: {e}");
exit(1);
}
}
"--mcp-http" => {
let Some(port_arg) = args.next() else {
eprintln!("op-host-web-server --mcp-http: missing <port> arg");
exit(2);
};
let Ok(port) = port_arg.parse::<u16>() else {
eprintln!(
"op-host-web-server --mcp-http: <port> must be a u16, got {port_arg:?}"
);
exit(2);
};
let Some(path) = args.next() else {
eprintln!("op-host-web-server --mcp-http: missing <path> arg");
exit(2);
};
if let Err(e) = op_web_daemon::mcp_serve::run_http(PathBuf::from(path), port) {
eprintln!("op-host-web-server --mcp-http: {e}");
exit(1);
}
}
other => {
eprintln!(
"op-host-web-server: unknown mode {other:?} \
(expected --serve-web / --mcp / --mcp-http)"
);
exit(2);
}
}
}