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:
1 parent
fab9add5d5
commit
5b569b6835
108 files changed
+6785
-279
No files matched your search
+14
-5
@@ -46,6 +46,8 @@ impl Tool for Read {
|
||||
fn description(&self) -> String {
|
||||
"Read a file. Returns numbered lines (cat -n style). Use offset/limit for large files \
|
||||
(default: first 2000 lines). Images (png, jpg, gif, webp) are returned as images. \
|
||||
Documents are converted to text: PDF (pages=\"3-7\", scanned pages come as images), \
|
||||
Excel/ODS spreadsheets (sheet=\"Name\"), Word .docx and PowerPoint .pptx. \
|
||||
Always read a file before editing or overwriting it."
|
||||
.into()
|
||||
}
|
||||
@@ -55,7 +57,9 @@ impl Tool for Read {
|
||||
"properties": {
|
||||
"path": {"type": "string", "description": "File path, absolute or relative to the working directory"},
|
||||
"offset": {"type": "integer", "description": "1-based line to start from"},
|
||||
"limit": {"type": "integer", "description": "Number of lines to read"}
|
||||
"limit": {"type": "integer", "description": "Number of lines to read"},
|
||||
"pages": {"type": "string", "description": "PDF only: page or range, e.g. \"2-5\""},
|
||||
"sheet": {"type": "string", "description": "Spreadsheets only: sheet name"}
|
||||
},
|
||||
"required": ["path"]
|
||||
})
|
||||
@@ -67,10 +71,15 @@ impl Tool for Read {
|
||||
format!("Read {}", input["path"].as_str().unwrap_or("?"))
|
||||
}
|
||||
async fn run(&self, input: Value, ctx: &ToolCtx) -> Result<ToolOutput> {
|
||||
let path = ctx.resolve(arg_str(&input, "path")?)?;
|
||||
let path = ctx.resolve_read(arg_str(&input, "path")?)?;
|
||||
if path.is_dir() {
|
||||
bail!("{} is a directory – use the list tool", path.display());
|
||||
}
|
||||
if crate::docs::is_document(&path) {
|
||||
ctx.app.mark_read(&ctx.session_id, &path);
|
||||
let (text, images) = crate::docs::extract(&path, input["pages"].as_str(), input["sheet"].as_str()).await?;
|
||||
return Ok(ToolOutput { content: truncate(&text, MAX_OUTPUT * 2), images, ..Default::default() });
|
||||
}
|
||||
let bytes = tokio::fs::read(&path).await?;
|
||||
ctx.app.mark_read(&ctx.session_id, &path);
|
||||
if let Some(mt) = image_type(&path) {
|
||||
@@ -273,7 +282,7 @@ impl Tool for List {
|
||||
true
|
||||
}
|
||||
async fn run(&self, input: Value, ctx: &ToolCtx) -> Result<ToolOutput> {
|
||||
let dir = ctx.resolve(input["path"].as_str().unwrap_or("."))?;
|
||||
let dir = ctx.resolve_read(input["path"].as_str().unwrap_or("."))?;
|
||||
let depth = input["depth"].as_u64().unwrap_or(1).clamp(1, 5) as usize;
|
||||
let cwd = ctx.cwd.clone();
|
||||
let out = tokio::task::spawn_blocking(move || {
|
||||
@@ -328,7 +337,7 @@ impl Tool for Glob {
|
||||
}
|
||||
async fn run(&self, input: Value, ctx: &ToolCtx) -> Result<ToolOutput> {
|
||||
let pattern = arg_str(&input, "pattern")?.to_string();
|
||||
let root = ctx.resolve(input["path"].as_str().unwrap_or("."))?;
|
||||
let root = ctx.resolve_read(input["path"].as_str().unwrap_or("."))?;
|
||||
let out = tokio::task::spawn_blocking(move || -> Result<String> {
|
||||
let matcher = globset::GlobBuilder::new(&pattern).literal_separator(true).build()?.compile_matcher();
|
||||
let mut hits = Vec::new();
|
||||
@@ -394,7 +403,7 @@ impl Tool for Grep {
|
||||
let re = regex::RegexBuilder::new(arg_str(&input, "pattern")?)
|
||||
.case_insensitive(input["case_insensitive"].as_bool().unwrap_or(false))
|
||||
.build()?;
|
||||
let root = ctx.resolve(input["path"].as_str().unwrap_or("."))?;
|
||||
let root = ctx.resolve_read(input["path"].as_str().unwrap_or("."))?;
|
||||
let glob = input["glob"]
|
||||
.as_str()
|
||||
.map(|g| globset::Glob::new(g).map(|g| g.compile_matcher()))
|
||||
|
||||
Reference in new issue
Block a user