fix: address code review findings on the bundle lifecycle

The user-origin marker on replaced mcp.json entries is now sticky:
re-records and cross-bundle transfers only upgrade replaced to
transferred when the prior record proves bundle origin, so updating a
bundle can no longer make uninstall delete a key the user had before the
bundle replaced it. Canonical source URLs lowercase only the host, since
self-hosted forges treat repository paths as case-sensitive and
collapsing distinct repos misdirects updates and uninstalls. git clone
invocations pass '--' before the URL so a crafted source cannot be
parsed as a git flag. Lifecycle flags (--install, --install-builtins,
--update-bundle, --uninstall) and their companions now conflict
explicitly instead of first-match dispatch silently dropping actions.
--install-from returns as a hidden tombstone that errors with the
replacement instead of feeding the flag to the LLM as prompt text.
--list-bundles dispatches before config load so a pure read no longer
boots MCP servers. write_file_atomic fsyncs before the rename so a crash
cannot persist a truncated store. REPL: .uninstall accepts --yes,
.install rejects trailing tokens after a category, and .install remote
gets a migration hint. Plus polish: host validation rejects '#' and '?',
renamed_to no longer serializes null, derived names get a debug assert
against the validator, completions share DEFAULT_GIT_HOST, README
mentions skills.
This commit is contained in:
2026-08-24 11:15:13 -06:00
parent 4324d551d6
commit 80b082423c
8 changed files with 239 additions and 44 deletions
+8 -4
View File
@@ -107,7 +107,6 @@ async fn main() -> Result<()> {
|| cli.list_rags
|| cli.list_macros
|| cli.list_skills
|| cli.list_bundles
|| cli.list_sessions;
let vault_flags = cli.add_secret.is_some()
|| cli.get_secret.is_some()
@@ -128,6 +127,10 @@ async fn main() -> Result<()> {
return sandbox::launch(name.clone(), cli.fresh);
}
if cli.install_from.is_some() {
bail!("--install-from was removed; use --install <GIT_URL|OWNER/REPO> instead");
}
install_builtins()?;
if let Some(category) = cli.install_builtins {
@@ -151,6 +154,10 @@ async fn main() -> Result<()> {
return config::uninstall_bundle(name, cli.yes);
}
if cli.list_bundles {
return config::list_installed_bundles();
}
if let Some(client_arg) = &cli.authenticate {
let cfg = Config::load_with_interpolation(true).await?;
let app_config = AppConfig::from_config(cfg)?;
@@ -323,9 +330,6 @@ async fn run(
println!("{skills}");
return Ok(());
}
if cli.list_bundles {
return config::list_installed_bundles();
}
let skills = cli.skills();
if skills.len() == 1 {
let name = &skills[0];