Add security hardening, Ghidra, recreate skill, checkpoints, shells, web search and themes

- Sandbox (bubblewrap) for non-admin shell/git, MCP per-user rights, hard budgets and quotas,
  login rate limiting, audit log and backups
- Ghidra headless decompilation (ARM64 natives built from source), binary_info tool,
  clean room / deep workflow per session
- Recreate skill and commands for analysing and re-building old software
- File checkpoints with rewind/restore, background shells (bash_output, kill_shell)
- web_search tool (DuckDuckGo, SearXNG, Brave, Tavily)
- In-app dialogs and toasts instead of browser dialogs
- 15 base themes (trilium-themes palettes plus Nord, VS Code, GitHub, Catppuccin, One,
  Dracula, Rose Pine, Everforest, Kanagawa) with light/dark/auto switcher and license notes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-08 19:12:30 +01:00
1 parent fab9add5d5
commit 5b569b6835
108 files changed
+6785 -279

No files matched your search

+14 -5
View File
@@ -504,11 +504,14 @@ impl McpManager {
/// All MCP tools, split into tools given to the model directly and tools
/// that are only reachable through mcp_search / mcp_call.
pub fn tools(&self, cfg: &crate::config::Config) -> (Vec<Arc<dyn Tool>>, Vec<Arc<McpTool>>) {
pub fn tools(&self, cfg: &crate::config::Config, user: Option<&crate::store::User>) -> (Vec<Arc<dyn Tool>>, Vec<Arc<McpTool>>) {
let servers = self.servers.lock().unwrap();
let mut direct: Vec<Arc<dyn Tool>> = Vec::new();
let mut deferred = Vec::new();
for (sname, server) in servers.iter() {
if !cfg.mcp_allowed(sname, user) {
continue;
}
let tools = server.tools.lock().unwrap();
let load = cfg.mcp.get(sname).and_then(|m| m.load.clone()).unwrap_or_else(|| "auto".into());
let on_demand = match load.as_str() {
@@ -529,8 +532,8 @@ impl McpManager {
}
/// Short overview of on-demand servers for the system prompt.
pub fn deferred_summary(&self, cfg: &crate::config::Config) -> Vec<(String, usize, Vec<String>)> {
let (_, deferred) = self.tools(cfg);
pub fn deferred_summary(&self, cfg: &crate::config::Config, user: Option<&crate::store::User>) -> Vec<(String, usize, Vec<String>)> {
let (_, deferred) = self.tools(cfg, user);
let mut by_server: BTreeMap<String, Vec<String>> = BTreeMap::new();
for t in deferred {
by_server.entry(t.server.name.clone()).or_default().push(t.info.name.clone());
@@ -579,7 +582,7 @@ impl Tool for McpSearch {
let query = input["query"].as_str().unwrap_or("").to_lowercase();
let server = input["server"].as_str();
let words: Vec<&str> = query.split_whitespace().collect();
let (_, deferred) = ctx.app.mcp.tools(&ctx.app.config());
let (_, deferred) = ctx.app.mcp.tools(&ctx.app.config(), owner(ctx).as_ref());
let mut scored: Vec<(usize, &Arc<McpTool>)> = deferred
.iter()
.filter(|t| server.is_none_or(|s| t.server.name == s))
@@ -635,10 +638,16 @@ impl Tool for McpCall {
}
}
/// The session owner (MCP access is decided per user).
fn owner(ctx: &ToolCtx) -> Option<crate::store::User> {
let s = ctx.app.session(&ctx.session_id).ok()?;
ctx.app.store.user(s.owner_id.as_deref()?).ok().flatten()
}
/// Find the MCP tool an mcp_call refers to.
pub fn resolve_call_target(ctx: &ToolCtx, input: &Value) -> Result<Arc<McpTool>> {
let name = input["tool"].as_str().unwrap_or("");
let (_, deferred) = ctx.app.mcp.tools(&ctx.app.config());
let (_, deferred) = ctx.app.mcp.tools(&ctx.app.config(), owner(ctx).as_ref());
deferred
.into_iter()
.find(|t| t.full_name == name)