feat(desktop): default to control-chrome executable without npx (#534)

This commit is contained in:
ben
2026-02-10 23:22:01 -08:00
committed by GitHub
parent 0b3c9ae602
commit b393d67605
7 changed files with 88 additions and 13 deletions

View File

@@ -24,7 +24,7 @@ description: Guide users through browser automation setup using Chrome DevTools
4. If DevTools MCP calls fail:
- Ask the user to open Chrome and keep it running.
- Retry `chrome-devtools_list_pages`.
- If it still fails, ensure `opencode.jsonc` includes `mcp.chrome-devtools` with command `['npx','-y','chrome-devtools-mcp@latest']` and ask the user to restart OpenWork/OpenCode.
- If it still fails, ensure `opencode.jsonc` includes `mcp.control-chrome` with command `['chrome-devtools-mcp']` and ask the user to restart OpenWork/OpenCode.
- Retry the DevTools MCP check.
5. If DevTools MCP is ready:
- Offer a first task ("Let's try opening a webpage").

View File

@@ -1,12 +1,10 @@
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"chrome-devtools": {
"control-chrome": {
"type": "local",
"command": [
"npx",
"-y",
"chrome-devtools-mcp@latest"
"chrome-devtools-mcp"
]
}
},

View File

@@ -75,10 +75,10 @@ export const MCP_QUICK_CONNECT: McpDirectoryInfo[] = [
oauth: false,
},
{
name: "Chrome DevTools",
name: "Control Chrome",
description: "Drive Chrome tabs with browser automation.",
type: "local",
command: ["npx", "-y", "chrome-devtools-mcp@latest"],
command: ["chrome-devtools-mcp"],
oauth: false,
},
];

View File

@@ -1,11 +1,12 @@
use std::path::Path;
use tauri::async_runtime::Receiver;
use tauri::AppHandle;
use tauri::{AppHandle, Manager};
use tauri_plugin_shell::process::{CommandChild, CommandEvent};
use tauri_plugin_shell::ShellExt;
use crate::paths::{candidate_xdg_config_dirs, candidate_xdg_data_dirs, maybe_infer_xdg_home};
use crate::paths::{prepended_path_env, sidecar_path_candidates};
pub fn find_free_port() -> Result<u16, String> {
let listener = std::net::TcpListener::bind(("127.0.0.1", 0)).map_err(|e| e.to_string())?;
@@ -77,6 +78,16 @@ pub fn spawn_engine(
command = command.env("OPENCODE_CLIENT", "openwork");
command = command.env("OPENWORK", "1");
let resource_dir = app.path().resource_dir().ok();
let current_bin_dir = tauri::process::current_binary(&app.env())
.ok()
.and_then(|path| path.parent().map(|parent| parent.to_path_buf()));
let sidecar_paths =
sidecar_path_candidates(resource_dir.as_deref(), current_bin_dir.as_deref());
if let Some(path_env) = prepended_path_env(&sidecar_paths) {
command = command.env("PATH", path_env);
}
if let Some(username) = opencode_username {
if !username.trim().is_empty() {
command = command.env("OPENCODE_SERVER_USERNAME", username);

View File

@@ -4,11 +4,12 @@ use std::path::{Path, PathBuf};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use tauri::AppHandle;
use tauri::{AppHandle, Manager};
use tauri_plugin_shell::process::{CommandChild, CommandEvent};
use tauri_plugin_shell::ShellExt;
use crate::paths::home_dir;
use crate::paths::{prepended_path_env, sidecar_path_candidates};
use crate::types::{
OpenwrkBinaryState, OpenwrkDaemonState, OpenwrkOpencodeState, OpenwrkSidecarInfo,
OpenwrkStatus, OpenwrkWorkspace,
@@ -237,8 +238,19 @@ pub fn spawn_openwrk_daemon(
}
}
let mut command = command.args(args);
let resource_dir = app.path().resource_dir().ok();
let current_bin_dir = tauri::process::current_binary(&app.env())
.ok()
.and_then(|path| path.parent().map(|parent| parent.to_path_buf()));
let sidecar_paths =
sidecar_path_candidates(resource_dir.as_deref(), current_bin_dir.as_deref());
if let Some(path_env) = prepended_path_env(&sidecar_paths) {
command = command.env("PATH", path_env);
}
command
.args(args)
.spawn()
.map_err(|e| format!("Failed to start openwrk: {e}"))
}

View File

@@ -90,3 +90,57 @@ pub fn resolve_in_path(name: &str) -> Option<PathBuf> {
}
None
}
pub fn sidecar_path_candidates(
resource_dir: Option<&Path>,
current_bin_dir: Option<&Path>,
) -> Vec<PathBuf> {
let mut candidates = Vec::new();
if let Some(current_bin_dir) = current_bin_dir {
candidates.push(current_bin_dir.to_path_buf());
}
if let Some(resource_dir) = resource_dir {
candidates.push(resource_dir.join("sidecars"));
candidates.push(resource_dir.to_path_buf());
}
candidates.push(PathBuf::from("src-tauri/sidecars"));
let mut unique = Vec::new();
for candidate in candidates {
if !candidate.is_dir() {
continue;
}
if unique
.iter()
.any(|existing: &PathBuf| existing == &candidate)
{
continue;
}
unique.push(candidate);
}
unique
}
pub fn prepended_path_env(prefixes: &[PathBuf]) -> Option<std::ffi::OsString> {
let mut entries = Vec::<PathBuf>::new();
for prefix in prefixes {
if prefix.is_dir() {
entries.push(prefix.clone());
}
}
if let Some(existing) = env::var_os("PATH") {
entries.extend(env::split_paths(&existing));
}
if entries.is_empty() {
return None;
}
env::join_paths(entries).ok()
}

View File

@@ -460,12 +460,12 @@ pub fn ensure_workspace_files(workspace_path: &str, preset: &str) -> Result<(),
_ => serde_json::Map::new(),
};
if !mcp_obj.contains_key("chrome-devtools") {
if !mcp_obj.contains_key("control-chrome") {
mcp_obj.insert(
"chrome-devtools".to_string(),
"control-chrome".to_string(),
serde_json::json!({
"type": "local",
"command": ["npx", "-y", "chrome-devtools-mcp@latest"]
"command": ["chrome-devtools-mcp"]
}),
);
config_changed = true;