feat(v2): add SSH management, ctx integration, themes, detached mode, auto-updater

SSH session management:
- SshSession struct + ssh_sessions SQLite table in session.rs
- CRUD Tauri commands (ssh_session_list/save/delete) in lib.rs
- SshDialog.svelte (create/edit modal), SshSessionList.svelte (sidebar)
- SSH pane routes to TerminalPane with shell=/usr/bin/ssh + args

ctx context database integration:
- ctx.rs: read-only CtxDb (SQLITE_OPEN_READ_ONLY for ~/.claude-context/context.db)
- 5 Tauri commands (ctx_list_projects/get_context/get_shared/get_summaries/search)
- ContextPane.svelte with project selector, tabs, search
- ctx-bridge.ts adapter

Catppuccin theme flavors (Latte/Frappe/Macchiato/Mocha):
- themes.ts: all 4 palette definitions + buildXtermTheme/applyCssVariables
- theme.svelte.ts: reactive store with SQLite persistence
- SettingsDialog flavor dropdown, TerminalPane theme-aware

Detached pane mode (pop-out windows):
- detach.ts: isDetachedMode/getDetachedConfig from URL params
- App.svelte: conditional rendering of single pane without chrome

Other additions:
- Shiki syntax highlighting (highlight.ts, lazy singleton, 13 languages)
- Tauri auto-updater plugin (tauri-plugin-updater + updater.ts)
- AgentPane markdown rendering with Shiki code highlighting
- New deps: shiki, @tauri-apps/plugin-updater, tauri-plugin-updater
This commit is contained in:
Hibryda 2026-03-06 14:50:00 +01:00
parent 4f2614186d
commit 4db7ccff60
28 changed files with 2992 additions and 51 deletions

360
v2/src-tauri/Cargo.lock generated
View file

@ -87,6 +87,15 @@ version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "arbitrary"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
dependencies = [
"derive_arbitrary",
]
[[package]]
name = "arrayvec"
version = "0.7.6"
@ -243,6 +252,7 @@ dependencies = [
"tauri",
"tauri-build",
"tauri-plugin-log",
"tauri-plugin-updater",
"uuid",
]
@ -626,6 +636,17 @@ dependencies = [
"serde_core",
]
[[package]]
name = "derive_arbitrary"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
]
[[package]]
name = "derive_more"
version = "0.99.20"
@ -826,6 +847,16 @@ dependencies = [
"typeid",
]
[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
]
[[package]]
name = "fallible-iterator"
version = "0.3.0"
@ -838,6 +869,12 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fdeflate"
version = "0.3.7"
@ -1493,6 +1530,22 @@ dependencies = [
"want",
]
[[package]]
name = "hyper-rustls"
version = "0.27.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
dependencies = [
"http",
"hyper",
"hyper-util",
"rustls",
"rustls-pki-types",
"tokio",
"tokio-rustls",
"tower-service",
]
[[package]]
name = "hyper-util"
version = "0.1.20"
@ -1942,6 +1995,12 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "linux-raw-sys"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]]
name = "litemap"
version = "0.8.1"
@ -2033,6 +2092,12 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "minisign-verify"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22f9645cb765ea72b8111f36c522475d2daa0d22c957a9826437e97534bc4e9e"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
@ -2278,6 +2343,7 @@ checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [
"bitflags 2.11.0",
"block2",
"libc",
"objc2",
"objc2-core-foundation",
]
@ -2293,6 +2359,18 @@ dependencies = [
"objc2-core-foundation",
]
[[package]]
name = "objc2-osa-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0"
dependencies = [
"bitflags 2.11.0",
"objc2",
"objc2-app-kit",
"objc2-foundation",
]
[[package]]
name = "objc2-quartz-core"
version = "0.3.2"
@ -2337,12 +2415,32 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "openssl-probe"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "osakit"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b"
dependencies = [
"objc2",
"objc2-foundation",
"objc2-osa-kit",
"serde",
"serde_json",
"thiserror 2.0.18",
]
[[package]]
name = "pango"
version = "0.18.3"
@ -2965,15 +3063,20 @@ dependencies = [
"http-body",
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-util",
"js-sys",
"log",
"percent-encoding",
"pin-project-lite",
"rustls",
"rustls-pki-types",
"rustls-platform-verifier",
"serde",
"serde_json",
"sync_wrapper",
"tokio",
"tokio-rustls",
"tokio-util",
"tower",
"tower-http",
@ -2985,6 +3088,20 @@ dependencies = [
"web-sys",
]
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.17",
"libc",
"untrusted",
"windows-sys 0.52.0",
]
[[package]]
name = "rkyv"
version = "0.7.46"
@ -3053,6 +3170,92 @@ dependencies = [
"semver",
]
[[package]]
name = "rustix"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
"bitflags 2.11.0",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.61.2",
]
[[package]]
name = "rustls"
version = "0.23.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
dependencies = [
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-native-certs"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63"
dependencies = [
"openssl-probe",
"rustls-pki-types",
"schannel",
"security-framework",
]
[[package]]
name = "rustls-pki-types"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
dependencies = [
"zeroize",
]
[[package]]
name = "rustls-platform-verifier"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
dependencies = [
"core-foundation",
"core-foundation-sys",
"jni",
"log",
"once_cell",
"rustls",
"rustls-native-certs",
"rustls-platform-verifier-android",
"rustls-webpki",
"security-framework",
"security-framework-sys",
"webpki-root-certs",
"windows-sys 0.61.2",
]
[[package]]
name = "rustls-platform-verifier-android"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
[[package]]
name = "rustls-webpki"
version = "0.103.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "rustversion"
version = "1.0.22"
@ -3068,6 +3271,15 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "schannel"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "schemars"
version = "0.8.22"
@ -3131,6 +3343,29 @@ version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "security-framework"
version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
dependencies = [
"bitflags 2.11.0",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "selectors"
version = "0.24.0"
@ -3523,6 +3758,12 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "swift-rs"
version = "1.0.7"
@ -3644,6 +3885,17 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tar"
version = "0.4.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a"
dependencies = [
"filetime",
"libc",
"xattr",
]
[[package]]
name = "target-lexicon"
version = "0.12.16"
@ -3803,6 +4055,39 @@ dependencies = [
"time",
]
[[package]]
name = "tauri-plugin-updater"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fe8e9bebd88fc222938ffdfbdcfa0307081423bd01e3252fc337d8bde81fc61"
dependencies = [
"base64 0.22.1",
"dirs 6.0.0",
"flate2",
"futures-util",
"http",
"infer",
"log",
"minisign-verify",
"osakit",
"percent-encoding",
"reqwest",
"rustls",
"semver",
"serde",
"serde_json",
"tar",
"tauri",
"tauri-plugin",
"tempfile",
"thiserror 2.0.18",
"time",
"tokio",
"url",
"windows-sys 0.60.2",
"zip",
]
[[package]]
name = "tauri-runtime"
version = "2.10.1"
@ -3903,6 +4188,19 @@ dependencies = [
"toml 0.9.12+spec-1.1.0",
]
[[package]]
name = "tempfile"
version = "3.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0"
dependencies = [
"fastrand",
"getrandom 0.4.2",
"once_cell",
"rustix",
"windows-sys 0.61.2",
]
[[package]]
name = "tendril"
version = "0.4.3"
@ -4035,6 +4333,16 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "tokio-rustls"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [
"rustls",
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.7.18"
@ -4316,6 +4624,12 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
version = "2.5.8"
@ -4624,6 +4938,15 @@ dependencies = [
"system-deps",
]
[[package]]
name = "webpki-root-certs"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "webview2-com"
version = "0.38.2"
@ -4863,6 +5186,15 @@ dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
@ -5357,6 +5689,16 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "xattr"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
dependencies = [
"libc",
"rustix",
]
[[package]]
name = "yoke"
version = "0.8.1"
@ -5421,6 +5763,12 @@ dependencies = [
"synstructure",
]
[[package]]
name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
[[package]]
name = "zerotrie"
version = "0.2.3"
@ -5454,6 +5802,18 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "zip"
version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1"
dependencies = [
"arbitrary",
"crc32fast",
"indexmap 2.13.0",
"memchr",
]
[[package]]
name = "zmij"
version = "1.0.21"

View file

@ -27,3 +27,4 @@ uuid = { version = "1", features = ["v4"] }
rusqlite = { version = "0.31", features = ["bundled"] }
dirs = "5"
notify = { version = "6", features = ["macos_fsevent"] }
tauri-plugin-updater = "2.10.0"

172
v2/src-tauri/src/ctx.rs Normal file
View file

@ -0,0 +1,172 @@
// ctx — Read-only access to the Claude Code context manager database
// Database: ~/.claude-context/context.db (managed by ctx CLI tool)
use rusqlite::{Connection, params};
use serde::Serialize;
use std::sync::Mutex;
#[derive(Debug, Clone, Serialize)]
pub struct CtxProject {
pub name: String,
pub description: String,
pub work_dir: Option<String>,
pub created_at: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct CtxEntry {
pub project: String,
pub key: String,
pub value: String,
pub updated_at: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct CtxSummary {
pub project: String,
pub summary: String,
pub created_at: String,
}
pub struct CtxDb {
conn: Mutex<Option<Connection>>,
}
impl CtxDb {
pub fn new() -> Self {
let db_path = dirs::home_dir()
.unwrap_or_default()
.join(".claude-context")
.join("context.db");
let conn = if db_path.exists() {
Connection::open_with_flags(
&db_path,
rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY | rusqlite::OpenFlags::SQLITE_OPEN_NO_MUTEX,
).ok()
} else {
None
};
Self { conn: Mutex::new(conn) }
}
pub fn list_projects(&self) -> Result<Vec<CtxProject>, String> {
let lock = self.conn.lock().unwrap();
let conn = lock.as_ref().ok_or("ctx database not found")?;
let mut stmt = conn
.prepare("SELECT name, description, work_dir, created_at FROM sessions ORDER BY name")
.map_err(|e| format!("ctx query failed: {e}"))?;
let projects = stmt
.query_map([], |row| {
Ok(CtxProject {
name: row.get(0)?,
description: row.get(1)?,
work_dir: row.get(2)?,
created_at: row.get(3)?,
})
})
.map_err(|e| format!("ctx query failed: {e}"))?
.collect::<Result<Vec<_>, _>>()
.map_err(|e| format!("ctx row read failed: {e}"))?;
Ok(projects)
}
pub fn get_context(&self, project: &str) -> Result<Vec<CtxEntry>, String> {
let lock = self.conn.lock().unwrap();
let conn = lock.as_ref().ok_or("ctx database not found")?;
let mut stmt = conn
.prepare("SELECT project, key, value, updated_at FROM contexts WHERE project = ?1 ORDER BY key")
.map_err(|e| format!("ctx query failed: {e}"))?;
let entries = stmt
.query_map(params![project], |row| {
Ok(CtxEntry {
project: row.get(0)?,
key: row.get(1)?,
value: row.get(2)?,
updated_at: row.get(3)?,
})
})
.map_err(|e| format!("ctx query failed: {e}"))?
.collect::<Result<Vec<_>, _>>()
.map_err(|e| format!("ctx row read failed: {e}"))?;
Ok(entries)
}
pub fn get_shared(&self) -> Result<Vec<CtxEntry>, String> {
let lock = self.conn.lock().unwrap();
let conn = lock.as_ref().ok_or("ctx database not found")?;
let mut stmt = conn
.prepare("SELECT key, value, updated_at FROM shared ORDER BY key")
.map_err(|e| format!("ctx query failed: {e}"))?;
let entries = stmt
.query_map([], |row| {
Ok(CtxEntry {
project: "shared".to_string(),
key: row.get(0)?,
value: row.get(1)?,
updated_at: row.get(2)?,
})
})
.map_err(|e| format!("ctx query failed: {e}"))?
.collect::<Result<Vec<_>, _>>()
.map_err(|e| format!("ctx row read failed: {e}"))?;
Ok(entries)
}
pub fn get_summaries(&self, project: &str, limit: i64) -> Result<Vec<CtxSummary>, String> {
let lock = self.conn.lock().unwrap();
let conn = lock.as_ref().ok_or("ctx database not found")?;
let mut stmt = conn
.prepare("SELECT project, summary, created_at FROM summaries WHERE project = ?1 ORDER BY created_at DESC LIMIT ?2")
.map_err(|e| format!("ctx query failed: {e}"))?;
let summaries = stmt
.query_map(params![project, limit], |row| {
Ok(CtxSummary {
project: row.get(0)?,
summary: row.get(1)?,
created_at: row.get(2)?,
})
})
.map_err(|e| format!("ctx query failed: {e}"))?
.collect::<Result<Vec<_>, _>>()
.map_err(|e| format!("ctx row read failed: {e}"))?;
Ok(summaries)
}
pub fn search(&self, query: &str) -> Result<Vec<CtxEntry>, String> {
let lock = self.conn.lock().unwrap();
let conn = lock.as_ref().ok_or("ctx database not found")?;
let mut stmt = conn
.prepare("SELECT project, key, value FROM contexts_fts WHERE contexts_fts MATCH ?1 LIMIT 50")
.map_err(|e| format!("ctx search failed: {e}"))?;
let entries = stmt
.query_map(params![query], |row| {
Ok(CtxEntry {
project: row.get(0)?,
key: row.get(1)?,
value: row.get(2)?,
updated_at: String::new(),
})
})
.map_err(|e| format!("ctx search failed: {e}"))?
.collect::<Result<Vec<_>, _>>()
.map_err(|e| format!("ctx row read failed: {e}"))?;
Ok(entries)
}
}

View file

@ -1,10 +1,12 @@
mod ctx;
mod pty;
mod sidecar;
mod watcher;
mod session;
use ctx::CtxDb;
use pty::{PtyManager, PtyOptions};
use session::{Session, SessionDb, LayoutState};
use session::{Session, SessionDb, LayoutState, SshSession};
use sidecar::{AgentQueryOptions, SidecarManager};
use watcher::FileWatcherManager;
use std::sync::Arc;
@ -15,6 +17,7 @@ struct AppState {
sidecar_manager: Arc<SidecarManager>,
session_db: Arc<SessionDb>,
file_watcher: Arc<FileWatcherManager>,
ctx_db: Arc<CtxDb>,
}
// --- PTY commands ---
@ -149,6 +152,50 @@ fn settings_list(state: State<'_, AppState>) -> Result<Vec<(String, String)>, St
state.session_db.get_all_settings()
}
// --- SSH session commands ---
#[tauri::command]
fn ssh_session_list(state: State<'_, AppState>) -> Result<Vec<SshSession>, String> {
state.session_db.list_ssh_sessions()
}
#[tauri::command]
fn ssh_session_save(state: State<'_, AppState>, session: SshSession) -> Result<(), String> {
state.session_db.save_ssh_session(&session)
}
#[tauri::command]
fn ssh_session_delete(state: State<'_, AppState>, id: String) -> Result<(), String> {
state.session_db.delete_ssh_session(&id)
}
// --- ctx commands ---
#[tauri::command]
fn ctx_list_projects(state: State<'_, AppState>) -> Result<Vec<ctx::CtxProject>, String> {
state.ctx_db.list_projects()
}
#[tauri::command]
fn ctx_get_context(state: State<'_, AppState>, project: String) -> Result<Vec<ctx::CtxEntry>, String> {
state.ctx_db.get_context(&project)
}
#[tauri::command]
fn ctx_get_shared(state: State<'_, AppState>) -> Result<Vec<ctx::CtxEntry>, String> {
state.ctx_db.get_shared()
}
#[tauri::command]
fn ctx_get_summaries(state: State<'_, AppState>, project: String, limit: i64) -> Result<Vec<ctx::CtxSummary>, String> {
state.ctx_db.get_summaries(&project, limit)
}
#[tauri::command]
fn ctx_search(state: State<'_, AppState>, query: String) -> Result<Vec<ctx::CtxEntry>, String> {
state.ctx_db.search(&query)
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let pty_manager = Arc::new(PtyManager::new());
@ -163,12 +210,14 @@ pub fn run() {
);
let file_watcher = Arc::new(FileWatcherManager::new());
let ctx_db = Arc::new(CtxDb::new());
let app_state = AppState {
pty_manager,
sidecar_manager: sidecar_manager.clone(),
session_db,
file_watcher,
ctx_db,
};
tauri::Builder::default()
@ -195,7 +244,16 @@ pub fn run() {
settings_get,
settings_set,
settings_list,
ssh_session_list,
ssh_session_save,
ssh_session_delete,
ctx_list_projects,
ctx_get_context,
ctx_get_shared,
ctx_get_summaries,
ctx_search,
])
.plugin(tauri_plugin_updater::Builder::new().build())
.setup(move |app| {
if cfg!(debug_assertions) {
app.handle().plugin(

View file

@ -6,6 +6,20 @@ use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::sync::Mutex;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SshSession {
pub id: String,
pub name: String,
pub host: String,
pub port: i32,
pub username: String,
pub key_file: String,
pub folder: String,
pub color: String,
pub created_at: i64,
pub last_used_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Session {
pub id: String,
@ -73,6 +87,19 @@ impl SessionDb {
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS ssh_sessions (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
host TEXT NOT NULL,
port INTEGER NOT NULL DEFAULT 22,
username TEXT NOT NULL,
key_file TEXT DEFAULT '',
folder TEXT DEFAULT '',
color TEXT DEFAULT '#89b4fa',
created_at INTEGER NOT NULL,
last_used_at INTEGER NOT NULL
);
"
).map_err(|e| format!("Migration failed: {e}"))?;
Ok(())
@ -212,4 +239,80 @@ impl SessionDb {
Ok(LayoutState { preset, pane_ids })
}).map_err(|e| format!("Layout read failed: {e}"))
}
// --- SSH session methods ---
pub fn list_ssh_sessions(&self) -> Result<Vec<SshSession>, String> {
let conn = self.conn.lock().unwrap();
let mut stmt = conn
.prepare("SELECT id, name, host, port, username, key_file, folder, color, created_at, last_used_at FROM ssh_sessions ORDER BY last_used_at DESC")
.map_err(|e| format!("SSH query prepare failed: {e}"))?;
let sessions = stmt
.query_map([], |row| {
Ok(SshSession {
id: row.get(0)?,
name: row.get(1)?,
host: row.get(2)?,
port: row.get(3)?,
username: row.get(4)?,
key_file: row.get(5)?,
folder: row.get(6)?,
color: row.get(7)?,
created_at: row.get(8)?,
last_used_at: row.get(9)?,
})
})
.map_err(|e| format!("SSH query failed: {e}"))?
.collect::<Result<Vec<_>, _>>()
.map_err(|e| format!("SSH row read failed: {e}"))?;
Ok(sessions)
}
pub fn save_ssh_session(&self, session: &SshSession) -> Result<(), String> {
let conn = self.conn.lock().unwrap();
conn.execute(
"INSERT OR REPLACE INTO ssh_sessions (id, name, host, port, username, key_file, folder, color, created_at, last_used_at) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
params![
session.id,
session.name,
session.host,
session.port,
session.username,
session.key_file,
session.folder,
session.color,
session.created_at,
session.last_used_at,
],
).map_err(|e| format!("SSH insert failed: {e}"))?;
Ok(())
}
pub fn delete_ssh_session(&self, id: &str) -> Result<(), String> {
let conn = self.conn.lock().unwrap();
conn.execute("DELETE FROM ssh_sessions WHERE id = ?1", params![id])
.map_err(|e| format!("SSH delete failed: {e}"))?;
Ok(())
}
pub fn update_ssh_session(&self, session: &SshSession) -> Result<(), String> {
let conn = self.conn.lock().unwrap();
conn.execute(
"UPDATE ssh_sessions SET name = ?1, host = ?2, port = ?3, username = ?4, key_file = ?5, folder = ?6, color = ?7, last_used_at = ?8 WHERE id = ?9",
params![
session.name,
session.host,
session.port,
session.username,
session.key_file,
session.folder,
session.color,
session.last_used_at,
session.id,
],
).map_err(|e| format!("SSH update failed: {e}"))?;
Ok(())
}
}

View file

@ -24,6 +24,15 @@
"csp": null
}
},
"plugins": {
"updater": {
"endpoints": [
"https://github.com/DexterFromLab/BTerminal/releases/latest/download/latest.json"
],
"dialog": true,
"pubkey": ""
}
},
"bundle": {
"active": true,
"targets": ["deb", "appimage"],