fix: harden the bundle lifecycle per code review
The path-escape guard that uninstall applies to recorded paths now also covers update's obsolete-file deletion through a shared check, so a tampered store cannot turn either delete site into an arbitrary file removal. Updates gain a working non-interactive path: --yes now applies to --update-bundle (locally modified files, obsolete files, and modified mcp entries are all kept; everything else refreshes), owned mcp entries whose recorded hash still matches the local entry take the remote side without prompting, and the non-TTY conflict bails name the flag that actually works per surface. An update records its new commit and version only after files and mcp entries land, so an aborted update cannot claim content it never wrote. The store gains a version field and rejects stores from newer builds, the corrupt-store error no longer advises the removal that would forfeit ownership tracking, and duplicate records tracking one source abort a rename instead of overwriting a record. Reinstalling from a source URL reclassifies owned unmodified files as silent refreshes just like updates. git runs with GIT_TERMINAL_PROMPT=0 and a null stdin so private or mistyped URLs fail instead of hanging. File comparison fills buffers fully before comparing, deleting an obsolete file prunes emptied directories, mcp.json backfill uses the fsynced atomic writer, --list-bundles no longer triggers builtin backfill, bundle-name completion logs store errors instead of swallowing them and offers --yes, and REPL .uninstall rejects unknown flags.
This commit is contained in:
+20
-14
@@ -1197,20 +1197,10 @@ pub async fn run_repl_command(
|
||||
println!("Usage: .delete <role|session|rag|macro|skill|agent-data>")
|
||||
}
|
||||
},
|
||||
".uninstall" => {
|
||||
let mut assume_yes = false;
|
||||
let mut names = Vec::new();
|
||||
for token in args.unwrap_or("").split_whitespace() {
|
||||
match token {
|
||||
"--yes" | "-y" => assume_yes = true,
|
||||
other => names.push(other),
|
||||
}
|
||||
}
|
||||
match names.as_slice() {
|
||||
[name] => config::uninstall_bundle(name, assume_yes)?,
|
||||
_ => println!("Usage: .uninstall <bundle-name> [--yes]"),
|
||||
}
|
||||
}
|
||||
".uninstall" => match parse_repl_uninstall(args) {
|
||||
Some((name, assume_yes)) => config::uninstall_bundle(&name, assume_yes)?,
|
||||
None => println!("Usage: .uninstall <bundle-name> [--yes]"),
|
||||
},
|
||||
".list" => match args {
|
||||
Some(args) => {
|
||||
ctx.list_assets(args.trim())?;
|
||||
@@ -1602,6 +1592,22 @@ fn parse_repl_install(args: Option<&str>) -> ReplInstallDispatch<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_repl_uninstall(args: Option<&str>) -> Option<(String, bool)> {
|
||||
let mut assume_yes = false;
|
||||
let mut names = Vec::new();
|
||||
for token in args.unwrap_or("").split_whitespace() {
|
||||
match token {
|
||||
"--yes" | "-y" => assume_yes = true,
|
||||
other if other.starts_with('-') => return None,
|
||||
other => names.push(other),
|
||||
}
|
||||
}
|
||||
match names.as_slice() {
|
||||
[name] => Some((name.to_string(), assume_yes)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn builtin_command_names() -> Vec<&'static str> {
|
||||
let mut names: Vec<&'static str> = REPL_COMMANDS
|
||||
.iter()
|
||||
|
||||
Reference in New Issue
Block a user