feat!: remove the deprecated --install-from flag and .install remote form
--install <git-url|name> is the single entry point for remote installs and updates; the unified .install dispatch likewise replaces .install remote. Flag completion for .install now applies to the unified form.
This commit is contained in:
@@ -34,7 +34,6 @@ Coming from [AIChat](https://github.com/sigoden/aichat)? Follow the [migration g
|
|||||||
homepage: https://github.com/example/oh-my-coyote # optional
|
homepage: https://github.com/example/oh-my-coyote # optional
|
||||||
```
|
```
|
||||||
* `coyote --install-builtins <category>` reinstalls Coyote's bundled assets for a category, overwriting any local changes (built-in assets are not bundles).
|
* `coyote --install-builtins <category>` reinstalls Coyote's bundled assets for a category, overwriting any local changes (built-in assets are not bundles).
|
||||||
* The old `--install-from <git-url>` flag still works but is deprecated; use `--install` instead.
|
|
||||||
* [REPL](https://github.com/Dark-Alex-17/coyote/wiki/REPL): Interactive Read-Eval-Print Loop for conversational interactions with LLMs and Coyote.
|
* [REPL](https://github.com/Dark-Alex-17/coyote/wiki/REPL): Interactive Read-Eval-Print Loop for conversational interactions with LLMs and Coyote.
|
||||||
* [Custom REPL Prompt](https://github.com/Dark-Alex-17/coyote/wiki/REPL-Prompt): Customize the REPL prompt to provide useful contextual information.
|
* [Custom REPL Prompt](https://github.com/Dark-Alex-17/coyote/wiki/REPL-Prompt): Customize the REPL prompt to provide useful contextual information.
|
||||||
* [Vault](https://github.com/Dark-Alex-17/coyote/wiki/Vault): Securely store and manage sensitive information such as API keys and credentials.
|
* [Vault](https://github.com/Dark-Alex-17/coyote/wiki/Vault): Securely store and manage sensitive information such as API keys and credentials.
|
||||||
|
|||||||
+12
-57
@@ -50,7 +50,7 @@ pub enum McpScopeArg {
|
|||||||
"model", "prompt", "role", "session", "agent", "rag", "rebuild_rag",
|
"model", "prompt", "role", "session", "agent", "rag", "rebuild_rag",
|
||||||
"macro_name", "execute", "code", "file", "no_stream", "no_memory",
|
"macro_name", "execute", "code", "file", "no_stream", "no_memory",
|
||||||
"init_memory", "dry_run", "info", "build_tools", "install",
|
"init_memory", "dry_run", "info", "build_tools", "install",
|
||||||
"install_from", "install_builtins", "sync_models", "list_models", "list_roles",
|
"install_builtins", "sync_models", "list_models", "list_roles",
|
||||||
"list_sessions", "list_agents", "list_rags", "list_macros",
|
"list_sessions", "list_agents", "list_rags", "list_macros",
|
||||||
"list_skills", "list_bundles", "skill", "tail_logs", "completions",
|
"list_skills", "list_bundles", "skill", "tail_logs", "completions",
|
||||||
"update", "update_bundle", "uninstall",
|
"update", "update_bundle", "uninstall",
|
||||||
@@ -61,11 +61,6 @@ pub enum McpScopeArg {
|
|||||||
.args(["mcp_add", "mcp_remove", "mcp_list", "mcp_get"])
|
.args(["mcp_add", "mcp_remove", "mcp_list", "mcp_get"])
|
||||||
.multiple(false)
|
.multiple(false)
|
||||||
),
|
),
|
||||||
group(
|
|
||||||
ArgGroup::new("remote-install")
|
|
||||||
.args(["install", "install_from"])
|
|
||||||
.multiple(false)
|
|
||||||
),
|
|
||||||
)]
|
)]
|
||||||
pub struct Cli {
|
pub struct Cli {
|
||||||
/// Input text
|
/// Input text
|
||||||
@@ -197,33 +192,21 @@ pub struct Cli {
|
|||||||
long,
|
long,
|
||||||
value_name = "CATEGORY",
|
value_name = "CATEGORY",
|
||||||
value_enum,
|
value_enum,
|
||||||
conflicts_with_all = ["install", "install_from"],
|
conflicts_with_all = ["install"],
|
||||||
help_heading = "Installation & Updates"
|
help_heading = "Installation & Updates"
|
||||||
)]
|
)]
|
||||||
pub install_builtins: Option<AssetCategory>,
|
pub install_builtins: Option<AssetCategory>,
|
||||||
/// Install assets from a remote git repository (URL may be suffixed with #<ref>)
|
|
||||||
#[arg(
|
|
||||||
long,
|
|
||||||
value_name = "GIT_URL",
|
|
||||||
hide = true,
|
|
||||||
help_heading = "Installation & Updates"
|
|
||||||
)]
|
|
||||||
pub install_from: Option<String>,
|
|
||||||
/// Restrict a remote install to a single asset category
|
/// Restrict a remote install to a single asset category
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
value_name = "CATEGORY",
|
value_name = "CATEGORY",
|
||||||
value_enum,
|
value_enum,
|
||||||
requires = "remote-install",
|
requires = "install",
|
||||||
help_heading = "Installation & Updates"
|
help_heading = "Installation & Updates"
|
||||||
)]
|
)]
|
||||||
pub filter: Option<InstallFilter>,
|
pub filter: Option<InstallFilter>,
|
||||||
/// Overwrite all conflicts without prompting (used with --install)
|
/// Overwrite all conflicts without prompting (used with --install)
|
||||||
#[arg(
|
#[arg(long, requires = "install", help_heading = "Installation & Updates")]
|
||||||
long,
|
|
||||||
requires = "remote-install",
|
|
||||||
help_heading = "Installation & Updates"
|
|
||||||
)]
|
|
||||||
pub install_force: bool,
|
pub install_force: bool,
|
||||||
/// Update an installed bundle from its recorded source (NAME may be suffixed with #<ref> to move a pin)
|
/// Update an installed bundle from its recorded source (NAME may be suffixed with #<ref> to move a pin)
|
||||||
#[arg(long, value_name = "NAME", help_heading = "Installation & Updates")]
|
#[arg(long, value_name = "NAME", help_heading = "Installation & Updates")]
|
||||||
@@ -581,65 +564,37 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_install_from_flag_still_works() {
|
fn parse_install_from_is_no_longer_a_flag() {
|
||||||
assert_eq!(
|
let cli = parse(&["--install-from", "https://github.com/x/y"]);
|
||||||
parse(&["--install-from", "https://github.com/x/y"])
|
assert!(cli.install.is_none());
|
||||||
.install_from
|
assert_eq!(cli.text, vec!["--install-from", "https://github.com/x/y"]);
|
||||||
.as_deref(),
|
|
||||||
Some("https://github.com/x/y")
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_install_conflicts_with_install_from() {
|
fn parse_install_builtins_conflicts_with_install() {
|
||||||
assert!(Cli::try_parse_from(["coyote", "--install", "x", "--install-from", "y"]).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_install_builtins_conflicts_with_remote_install_flags() {
|
|
||||||
assert!(
|
assert!(
|
||||||
Cli::try_parse_from(["coyote", "--install-builtins", "agents", "--install", "x"])
|
Cli::try_parse_from(["coyote", "--install-builtins", "agents", "--install", "x"])
|
||||||
.is_err()
|
.is_err()
|
||||||
);
|
);
|
||||||
assert!(
|
|
||||||
Cli::try_parse_from([
|
|
||||||
"coyote",
|
|
||||||
"--install-builtins",
|
|
||||||
"agents",
|
|
||||||
"--install-from",
|
|
||||||
"y"
|
|
||||||
])
|
|
||||||
.is_err()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_filter_requires_a_remote_install_flag() {
|
fn parse_filter_requires_install() {
|
||||||
assert!(Cli::try_parse_from(["coyote", "--filter", "agents"]).is_err());
|
assert!(Cli::try_parse_from(["coyote", "--filter", "agents"]).is_err());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse(&["--install", "https://github.com/x/y", "--filter", "agents"]).filter,
|
parse(&["--install", "https://github.com/x/y", "--filter", "agents"]).filter,
|
||||||
Some(InstallFilter::Agents)
|
Some(InstallFilter::Agents)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
|
||||||
parse(&[
|
|
||||||
"--install-from",
|
|
||||||
"https://github.com/x/y",
|
|
||||||
"--filter",
|
|
||||||
"agents"
|
|
||||||
])
|
|
||||||
.filter,
|
|
||||||
Some(InstallFilter::Agents)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_install_force_requires_a_remote_install_flag() {
|
fn parse_install_force_requires_install() {
|
||||||
assert!(Cli::try_parse_from(["coyote", "--install-force"]).is_err());
|
assert!(Cli::try_parse_from(["coyote", "--install-force"]).is_err());
|
||||||
assert!(parse(&["--install", "https://github.com/x/y", "--install-force"]).install_force);
|
assert!(parse(&["--install", "https://github.com/x/y", "--install-force"]).install_force);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn help_hides_install_from_and_shows_install_builtins() {
|
fn help_omits_install_from_and_shows_install_builtins() {
|
||||||
use clap::CommandFactory;
|
use clap::CommandFactory;
|
||||||
let help = Cli::command().render_long_help().to_string();
|
let help = Cli::command().render_long_help().to_string();
|
||||||
assert!(!help.contains("--install-from"), "help: {help}");
|
assert!(!help.contains("--install-from"), "help: {help}");
|
||||||
|
|||||||
@@ -69,22 +69,6 @@ pub fn install_remote(git_url: &str, filter: Option<InstallFilter>, force: bool)
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn install_remote_from_repl_args(args: &str) -> Result<()> {
|
|
||||||
let tokens = shell_words::split(args)
|
|
||||||
.with_context(|| format!("failed to parse '.install remote' args: {args}"))?;
|
|
||||||
|
|
||||||
let mut iter = tokens.into_iter();
|
|
||||||
let url = iter.next().with_context(|| {
|
|
||||||
format!(
|
|
||||||
"Usage: .install remote <git-url> [--filter <{}>] [--force]",
|
|
||||||
InstallFilter::NAMES.join("|")
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let (filter, force) = parse_repl_install_flags(".install remote", iter)?;
|
|
||||||
install_remote(&url, filter, force)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_repl_install_flags(
|
fn parse_repl_install_flags(
|
||||||
command: &str,
|
command: &str,
|
||||||
mut iter: impl Iterator<Item = String>,
|
mut iter: impl Iterator<Item = String>,
|
||||||
|
|||||||
+1
-2
@@ -33,8 +33,7 @@ pub use self::app_state::AppState;
|
|||||||
pub use self::bundles::list_installed_bundles;
|
pub use self::bundles::list_installed_bundles;
|
||||||
pub use self::input::Input;
|
pub use self::input::Input;
|
||||||
pub use self::install_remote::{
|
pub use self::install_remote::{
|
||||||
install_or_update, install_or_update_from_repl_args, install_remote,
|
install_or_update, install_or_update_from_repl_args, uninstall_bundle, update_bundle,
|
||||||
install_remote_from_repl_args, uninstall_bundle, update_bundle,
|
|
||||||
};
|
};
|
||||||
pub use self::macro_policy::{
|
pub use self::macro_policy::{
|
||||||
MacroAllowlistLevel, MacroPolicy, MacroSource, MacroState, RESERVED_MACRO_NAMES, ResolvedMacro,
|
MacroAllowlistLevel, MacroPolicy, MacroSource, MacroState, RESERVED_MACRO_NAMES, ResolvedMacro,
|
||||||
|
|||||||
@@ -3263,7 +3263,6 @@ impl RequestContext {
|
|||||||
".install" => {
|
".install" => {
|
||||||
let mut values: Vec<String> =
|
let mut values: Vec<String> =
|
||||||
AssetCategory::NAMES.iter().map(|s| s.to_string()).collect();
|
AssetCategory::NAMES.iter().map(|s| s.to_string()).collect();
|
||||||
values.push("remote".to_string());
|
|
||||||
values.extend(
|
values.extend(
|
||||||
BundleStore::load()
|
BundleStore::load()
|
||||||
.map(|store| {
|
.map(|store| {
|
||||||
@@ -3495,7 +3494,7 @@ impl RequestContext {
|
|||||||
values = complete_skills_with_descriptions(paths::list_skills());
|
values = complete_skills_with_descriptions(paths::list_skills());
|
||||||
} else if cmd == ".skill" && args.first() == Some(&"unload") && args.len() == 2 {
|
} else if cmd == ".skill" && args.first() == Some(&"unload") && args.len() == 2 {
|
||||||
values = complete_skills_with_descriptions(self.skill_registry.loaded_names());
|
values = complete_skills_with_descriptions(self.skill_registry.loaded_names());
|
||||||
} else if cmd == ".install" && args.first() == Some(&"remote") && args.len() >= 2 {
|
} else if cmd == ".install" && args.len() >= 2 {
|
||||||
let prev = args.get(args.len() - 2).copied().unwrap_or("");
|
let prev = args.get(args.len() - 2).copied().unwrap_or("");
|
||||||
if prev == "--filter" {
|
if prev == "--filter" {
|
||||||
values = super::map_completion_values(
|
values = super::map_completion_values(
|
||||||
@@ -4992,7 +4991,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[serial]
|
#[serial]
|
||||||
fn repl_complete_install_offers_categories_remote_and_bundles() {
|
fn repl_complete_install_offers_categories_and_bundles() {
|
||||||
let _guard = TestConfigDirGuard::new();
|
let _guard = TestConfigDirGuard::new();
|
||||||
let mut store = BundleStore::load().unwrap();
|
let mut store = BundleStore::load().unwrap();
|
||||||
store
|
store
|
||||||
@@ -5012,7 +5011,7 @@ mod tests {
|
|||||||
|
|
||||||
let values = ctx.repl_complete(".install", &[""], "");
|
let values = ctx.repl_complete(".install", &[""], "");
|
||||||
|
|
||||||
for expected in ["agents", "remote", "omc"] {
|
for expected in ["agents", "omc"] {
|
||||||
assert!(
|
assert!(
|
||||||
values.iter().any(|(name, _)| name == expected),
|
values.iter().any(|(name, _)| name == expected),
|
||||||
"missing '{expected}'; got: {values:?}"
|
"missing '{expected}'; got: {values:?}"
|
||||||
|
|||||||
@@ -138,13 +138,6 @@ async fn main() -> Result<()> {
|
|||||||
return config::install_or_update(value, cli.filter, cli.install_force);
|
return config::install_or_update(value, cli.filter, cli.install_force);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(url) = cli.install_from.as_deref() {
|
|
||||||
eprintln!(
|
|
||||||
"warning: --install-from is deprecated and will be removed in a future release; use --install <GIT_URL>"
|
|
||||||
);
|
|
||||||
return config::install_remote(url, cli.filter, cli.install_force);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(spec) = cli.update_bundle.as_deref() {
|
if let Some(spec) = cli.update_bundle.as_deref() {
|
||||||
return config::update_bundle(spec);
|
return config::update_bundle(spec);
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-12
@@ -872,15 +872,12 @@ pub async fn run_repl_command(
|
|||||||
}
|
}
|
||||||
".install" => match parse_repl_install(args) {
|
".install" => match parse_repl_install(args) {
|
||||||
ReplInstallDispatch::Builtins(category) => config::install_assets(category)?,
|
ReplInstallDispatch::Builtins(category) => config::install_assets(category)?,
|
||||||
ReplInstallDispatch::Remote(rest) => {
|
|
||||||
config::install_remote_from_repl_args(rest)?;
|
|
||||||
}
|
|
||||||
ReplInstallDispatch::Unified(value) => {
|
ReplInstallDispatch::Unified(value) => {
|
||||||
config::install_or_update_from_repl_args(value)?;
|
config::install_or_update_from_repl_args(value)?;
|
||||||
}
|
}
|
||||||
ReplInstallDispatch::Usage => println!(
|
ReplInstallDispatch::Usage => println!(
|
||||||
"Usage: .install <{}> | .install <git-url|installed-bundle> | \
|
"Usage: .install <{}> | .install <git-url|installed-bundle> \
|
||||||
.install remote <git-url> [--filter <cat>] [--force]",
|
[--filter <cat>] [--force]",
|
||||||
AssetCategory::NAMES.join("|")
|
AssetCategory::NAMES.join("|")
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -1577,7 +1574,6 @@ fn unknown_command() -> Result<()> {
|
|||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
enum ReplInstallDispatch<'a> {
|
enum ReplInstallDispatch<'a> {
|
||||||
Builtins(AssetCategory),
|
Builtins(AssetCategory),
|
||||||
Remote(&'a str),
|
|
||||||
Unified(&'a str),
|
Unified(&'a str),
|
||||||
Usage,
|
Usage,
|
||||||
}
|
}
|
||||||
@@ -1586,7 +1582,6 @@ fn parse_repl_install(args: Option<&str>) -> ReplInstallDispatch<'_> {
|
|||||||
let trimmed = args.map(str::trim).unwrap_or("");
|
let trimmed = args.map(str::trim).unwrap_or("");
|
||||||
let mut parts = trimmed.splitn(2, char::is_whitespace);
|
let mut parts = trimmed.splitn(2, char::is_whitespace);
|
||||||
match parts.next() {
|
match parts.next() {
|
||||||
Some("remote") => ReplInstallDispatch::Remote(parts.next().unwrap_or("").trim()),
|
|
||||||
Some(name) if !name.is_empty() => match AssetCategory::parse(name) {
|
Some(name) if !name.is_empty() => match AssetCategory::parse(name) {
|
||||||
Some(category) => ReplInstallDispatch::Builtins(category),
|
Some(category) => ReplInstallDispatch::Builtins(category),
|
||||||
None => ReplInstallDispatch::Unified(trimmed),
|
None => ReplInstallDispatch::Unified(trimmed),
|
||||||
@@ -1821,15 +1816,11 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_repl_install_keeps_category_and_remote_back_compat() {
|
fn parse_repl_install_routes_categories_to_builtins() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_repl_install(Some("agents")),
|
parse_repl_install(Some("agents")),
|
||||||
ReplInstallDispatch::Builtins(AssetCategory::Agents)
|
ReplInstallDispatch::Builtins(AssetCategory::Agents)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
|
||||||
parse_repl_install(Some("remote https://x --force")),
|
|
||||||
ReplInstallDispatch::Remote("https://x --force")
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user