diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx
index 91e4ae39ba2..5dfc59a4e17 100644
--- a/packages/app/src/app.tsx
+++ b/packages/app/src/app.tsx
@@ -33,7 +33,7 @@ const Loading = () =>
+
diff --git a/packages/desktop/src-tauri/Cargo.toml b/packages/desktop/src-tauri/Cargo.toml
index c8eb0846c8d..8033d4f147b 100644
--- a/packages/desktop/src-tauri/Cargo.toml
+++ b/packages/desktop/src-tauri/Cargo.toml
@@ -3,7 +3,7 @@ name = "opencode-desktop"
version = "0.0.0"
description = "The open source AI coding agent"
authors = ["Anomaly Innovations"]
-edition = "2021"
+edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
diff --git a/packages/desktop/src-tauri/src/cli.rs b/packages/desktop/src-tauri/src/cli.rs
index 8b76d1a7f8a..87ecf4997d0 100644
--- a/packages/desktop/src-tauri/src/cli.rs
+++ b/packages/desktop/src-tauri/src/cli.rs
@@ -1,8 +1,30 @@
-use tauri::Manager;
+use tauri::{path::BaseDirectory, AppHandle, Manager};
+use tauri_plugin_shell::{process::Command, ShellExt};
const CLI_INSTALL_DIR: &str = ".opencode/bin";
const CLI_BINARY_NAME: &str = "opencode";
+#[derive(serde::Deserialize)]
+pub struct ServerConfig {
+ pub hostname: Option,
+ pub port: Option,
+}
+
+#[derive(serde::Deserialize)]
+pub struct Config {
+ pub server: Option,
+}
+
+pub async fn get_config(app: &AppHandle) -> Option {
+ create_command(app, "debug config")
+ .output()
+ .await
+ .inspect_err(|e| eprintln!("Failed to read OC config: {e}"))
+ .ok()
+ .and_then(|out| String::from_utf8(out.stdout.to_vec()).ok())
+ .and_then(|s| serde_json::from_str::(&s).ok())
+}
+
fn get_cli_install_path() -> Option {
std::env::var("HOME").ok().map(|home| {
std::path::PathBuf::from(home)
@@ -117,3 +139,35 @@ pub fn sync_cli(app: tauri::AppHandle) -> Result<(), String> {
Ok(())
}
+
+fn get_user_shell() -> String {
+ std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string())
+}
+
+pub fn create_command(app: &tauri::AppHandle, args: &str) -> Command {
+ let state_dir = app
+ .path()
+ .resolve("", BaseDirectory::AppLocalData)
+ .expect("Failed to resolve app local data dir");
+
+ #[cfg(target_os = "windows")]
+ return app
+ .shell()
+ .sidecar("opencode-cli")
+ .unwrap()
+ .env("OPENCODE_EXPERIMENTAL_ICON_DISCOVERY", "true")
+ .env("OPENCODE_CLIENT", "desktop")
+ .env("XDG_STATE_HOME", &state_dir);
+
+ #[cfg(not(target_os = "windows"))]
+ return {
+ let sidecar = get_sidecar_path(app);
+ let shell = get_user_shell();
+ app.shell()
+ .command(&shell)
+ .env("OPENCODE_EXPERIMENTAL_ICON_DISCOVERY", "true")
+ .env("OPENCODE_CLIENT", "desktop")
+ .env("XDG_STATE_HOME", &state_dir)
+ .args(["-il", "-c", &format!("\"{}\" {}", sidecar.display(), args)])
+ };
+}
diff --git a/packages/desktop/src-tauri/src/lib.rs b/packages/desktop/src-tauri/src/lib.rs
index 5ed03fc66e7..b479ed0b61f 100644
--- a/packages/desktop/src-tauri/src/lib.rs
+++ b/packages/desktop/src-tauri/src/lib.rs
@@ -1,23 +1,18 @@
mod cli;
mod window_customizer;
-use cli::{get_sidecar_path, install_cli, sync_cli};
+use cli::{install_cli, sync_cli};
use futures::FutureExt;
use std::{
collections::VecDeque,
- net::{SocketAddr, TcpListener},
+ net::TcpListener,
sync::{Arc, Mutex},
time::{Duration, Instant},
};
-use tauri::{
- path::BaseDirectory, AppHandle, LogicalSize, Manager, RunEvent, State, WebviewUrl,
- WebviewWindow,
-};
+use tauri::{AppHandle, LogicalSize, Manager, RunEvent, State, WebviewUrl, WebviewWindow};
use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogResult};
use tauri_plugin_shell::process::{CommandChild, CommandEvent};
-use tauri_plugin_shell::ShellExt;
use tauri_plugin_store::StoreExt;
-use tokio::net::TcpSocket;
use crate::window_customizer::PinchZoomDisablePlugin;
@@ -27,13 +22,13 @@ const DEFAULT_SERVER_URL_KEY: &str = "defaultServerUrl";
#[derive(Clone)]
struct ServerState {
child: Arc>>,
- status: futures::future::Shared>>,
+ status: futures::future::Shared>>,
}
impl ServerState {
pub fn new(
child: Option,
- status: tokio::sync::oneshot::Receiver>,
+ status: tokio::sync::oneshot::Receiver>,
) -> Self {
Self {
child: Arc::new(Mutex::new(child)),
@@ -85,7 +80,7 @@ async fn get_logs(app: AppHandle) -> Result {
}
#[tauri::command]
-async fn ensure_server_started(state: State<'_, ServerState>) -> Result<(), String> {
+async fn ensure_server_ready(state: State<'_, ServerState>) -> Result {
state
.status
.clone()
@@ -94,7 +89,7 @@ async fn ensure_server_started(state: State<'_, ServerState>) -> Result<(), Stri
}
#[tauri::command]
-async fn get_default_server_url(app: AppHandle) -> Result
}
>
- {/* Trigger error boundary without rendering the returned value */}
- {(status(), null)}
- {props.children}
+ {serverUrl => props.children(serverUrl)}
)
}