diff --git a/CHANGELOG.md b/CHANGELOG.md index 7986006..6a19760 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- **`bssh ai install omp`** configures [omp](https://github.com/can1357/oh-my-pi) (oh-my-pi) in `~/.omp/agent/mcp.json`, and `bssh ai doctor` lists it. omp doesn't read other clients' user configs such as `~/.claude.json` unless they're opted in with `enabledProviders`, so `bssh ai install claude-code` alone didn't reach it. + ## [2.13.8] - 2026-10-27 ### Fixed diff --git a/crates/cli/src/cli/commands/ai/doctor.rs b/crates/cli/src/cli/commands/ai/doctor.rs index be8d301..c4b880d 100644 --- a/crates/cli/src/cli/commands/ai/doctor.rs +++ b/crates/cli/src/cli/commands/ai/doctor.rs @@ -170,7 +170,7 @@ fn render_text(report: &DoctorReport, policy_error: Option) -> String { if clients.is_empty() { let _ = writeln!( s, - " skip no agent client found (Claude Code, Codex, Cursor, VS Code, Claude Desktop)" + " skip no agent client found (Claude Code, Codex, Cursor, VS Code, Claude Desktop, omp)" ); } for c in clients { diff --git a/crates/cli/src/cli/parser_ai.rs b/crates/cli/src/cli/parser_ai.rs index 18921f3..3fd7d0d 100644 --- a/crates/cli/src/cli/parser_ai.rs +++ b/crates/cli/src/cli/parser_ai.rs @@ -20,7 +20,7 @@ pub enum AiCommands { Log(AiArgsLog), /// Replay the recorded output of an agent run Replay(AiArgsReplay), - /// Configure an agent client (claude-code, codex, cursor, vscode, claude-desktop) to use bssh + /// Configure an agent client (claude-code, codex, cursor, vscode, claude-desktop, omp) to use bssh Install(AiArgsInstall), /// Prepare hosts for agents: create the low-privilege bssh-agent user (needs root once) Setup(AiArgsSetup), @@ -137,7 +137,7 @@ pub struct AiArgsReplay { #[derive(Args, Debug)] pub struct AiArgsInstall { - /// claude-code, codex, cursor, vscode or claude-desktop + /// claude-code, codex, cursor, vscode, claude-desktop or omp pub client: String, #[arg(long, default_value = "default")] pub policy: String, diff --git a/crates/cli/src/services/ai/install.rs b/crates/cli/src/services/ai/install.rs index e7d170d..9b45e40 100644 --- a/crates/cli/src/services/ai/install.rs +++ b/crates/cli/src/services/ai/install.rs @@ -11,6 +11,8 @@ //! - Codex: `[mcp_servers.]` with `command` and `args` in `~/.codex/config.toml`. //! - Cursor: `~/.cursor/mcp.json`, `mcpServers`. VS Code: user `mcp.json`, `servers` with `type`. //! - Claude Desktop: `claude_desktop_config.json`, `mcpServers`. +//! - omp (oh-my-pi): `~/.omp/agent/mcp.json`, `mcpServers` with `type`. omp ignores other +//! clients' user configs (`~/.claude.json`, …) unless opted in, so it needs its own entry. use anyhow::{anyhow, bail, Context, Result}; use serde_json::{json, Map, Value}; @@ -26,15 +28,17 @@ pub enum Client { Cursor, Vscode, ClaudeDesktop, + Omp, } impl Client { - pub const ALL: [Client; 5] = [ + pub const ALL: [Client; 6] = [ Client::ClaudeCode, Client::Codex, Client::Cursor, Client::Vscode, Client::ClaudeDesktop, + Client::Omp, ]; pub fn parse(s: &str) -> Result { @@ -44,8 +48,9 @@ impl Client { "cursor" => Client::Cursor, "vscode" | "vs-code" | "code" => Client::Vscode, "claude-desktop" | "claudedesktop" => Client::ClaudeDesktop, + "omp" | "oh-my-pi" => Client::Omp, other => bail!( - "unknown client '{other}' (use claude-code, codex, cursor, vscode or claude-desktop)" + "unknown client '{other}' (use claude-code, codex, cursor, vscode, claude-desktop or omp)" ), }) } @@ -57,6 +62,7 @@ impl Client { Client::Cursor => "cursor", Client::Vscode => "vscode", Client::ClaudeDesktop => "claude-desktop", + Client::Omp => "omp", } } @@ -101,6 +107,7 @@ pub fn config_path(client: Client, home: &Path) -> PathBuf { home.join(".config/Claude/claude_desktop_config.json") } } + Client::Omp => home.join(".omp").join("agent").join("mcp.json"), } } @@ -177,7 +184,7 @@ fn mcp_args(policy: &str) -> Vec { fn json_entry(client: Client, bin: &Path, policy: &str) -> Value { let mut o = Map::new(); - if matches!(client, Client::ClaudeCode | Client::Vscode) { + if matches!(client, Client::ClaudeCode | Client::Vscode | Client::Omp) { o.insert("type".into(), json!("stdio")); } o.insert("command".into(), json!(bin.to_string_lossy())); @@ -791,14 +798,22 @@ mod tests { let h = tempfile::tempdir().unwrap(); let ai = h.path().join("ai"); let bin = PathBuf::from("/b/bssh"); - for c in [Client::Vscode, Client::Cursor, Client::ClaudeDesktop] { + for c in [ + Client::Vscode, + Client::Cursor, + Client::ClaudeDesktop, + Client::Omp, + ] { run_install(&opts(c, h.path(), &ai, &bin, &[])).unwrap(); let v: Value = serde_json::from_str(&std::fs::read_to_string(config_path(c, h.path())).unwrap()) .unwrap(); let e = &v[c.servers_key()]["bssh"]; assert_eq!(e["command"], "/b/bssh", "{c:?}"); - assert_eq!(e.get("type").is_some(), c == Client::Vscode); + assert_eq!( + e.get("type").is_some(), + matches!(c, Client::Vscode | Client::Omp) + ); } }