@team-agent/installer 0.5.82 → 0.5.84

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Cargo.lock CHANGED
@@ -581,7 +581,7 @@ dependencies = [
581
581
 
582
582
  [[package]]
583
583
  name = "team-agent"
584
- version = "0.5.82"
584
+ version = "0.5.84"
585
585
  dependencies = [
586
586
  "anyhow",
587
587
  "chrono",
package/Cargo.toml CHANGED
@@ -9,7 +9,7 @@ members = ["crates/team-agent", "crates/win-conpty-phase0", "crates/conpty-trans
9
9
 
10
10
  [workspace.package]
11
11
  edition = "2021"
12
- version = "0.5.82"
12
+ version = "0.5.84"
13
13
  license = "AGPL-3.0"
14
14
  rust-version = "1.95"
15
15
 
@@ -218,7 +218,7 @@ fn dispatch(command: &str, args: &[String], cwd: &Path) -> Result<ExitCode, CliE
218
218
  // resolve short/qualified/hash-qualified names to a canonical
219
219
  // (workspace, team_key) tuple and delegates to the E6 named-leader
220
220
  // delivery path — no separate route authority.
221
- "leaders" => cmd_leaders(&leaders_args(args, cwd)).map(emit_result),
221
+ "leaders" => cmd_leaders(&leaders_args(args, cwd)?).map(emit_result),
222
222
  "models" => cmd_models(&models_args(args)?).map(emit_result),
223
223
  "validate" => cmd_validate(&validate_args(args, cwd)).map(emit_result),
224
224
  "install-skill" => cmd_install_skill(&install_skill_args(args)?).map(emit_result),
@@ -324,7 +324,15 @@ pub(crate) fn default_help() -> String {
324
324
  append_help_section(
325
325
  &mut out,
326
326
  "Core",
327
- &["quick-start", "send", "status", "collect", "results", "models"],
327
+ &[
328
+ "quick-start",
329
+ "send",
330
+ "status",
331
+ "collect",
332
+ "results",
333
+ "models",
334
+ "leaders",
335
+ ],
328
336
  );
329
337
  append_help_section(
330
338
  &mut out,
@@ -410,7 +418,8 @@ fn command_help(command: Option<&str>) -> String {
410
418
  .to_string(),
411
419
  Some("allow-peer-talk") => "usage: team-agent allow-peer-talk A B [--workspace WORKSPACE] [--json]".to_string(),
412
420
  Some("status") => "usage: team-agent status [AGENT] [--workspace WORKSPACE] [--team TEAM] [--summary|--json] [--detail]\n\n输出七字段:name/provider/runtime_status/activity/health/session_name/tmux_command;人读与 --json 使用同一投影。缺少可靠定位或 nodeprobe 证据时显示 unknown;tmux_command 可复制到对应目标。--summary/--detail 仅保留兼容性,不增加诊断字段。".to_string(),
413
- Some("models") => "usage: team-agent models --provider pi [--search TEXT] [--json]\n\nPrints models.v1 exact role_model entries; each entry includes current=true|false. Catalog readiness is reported as auth=ok|not_ready with auth_basis=catalog_visibility.".to_string(),
421
+ Some("models") => "usage: team-agent models [--provider pi|cursor_agent] [QUERY|--search TEXT] [--json]\n\nLists exact provider model ids with case-insensitive multi-word search across provider, vendor, id, and display name. Cursor uses the local `agent --list-models` catalog.".to_string(),
422
+ Some("leaders") => "usage: team-agent leaders [QUERY|--search TEXT] [--all|--stale] [--json] | --prune [--dry-run] [--json]\n\nLists LIVE leaders by default. Use --all to include retained STALE entries, --stale to inspect only STALE entries, QUERY or --search TEXT to match workspace/team/name fields, and --prune to remove only entries proven terminal by canonical state. --dry-run is valid only with --prune.".to_string(),
414
423
  Some("stop") => compat_hidden_help("stop", "usage: team-agent stop [--workspace WORKSPACE] [--team TEAM] [--keep-logs] [--json]"),
415
424
  Some("shutdown") => "usage: team-agent shutdown [--workspace WORKSPACE] [--team TEAM] [--keep-logs] [--json]".to_string(),
416
425
  Some("restart") => "usage: team-agent restart [WORKSPACE] [--team TEAM] [--allow-fresh] [--session-converge-deadline SECONDS] [--json] [--detail]".to_string(),
@@ -998,6 +1007,9 @@ fn parse_args(args: &[String]) -> ParsedArgs {
998
1007
  other if other.starts_with("--provider=") => {
999
1008
  parsed.provider = Some(other.trim_start_matches("--provider=").to_string());
1000
1009
  }
1010
+ other if other.starts_with("--search=") => {
1011
+ parsed.search = Some(other.trim_start_matches("--search=").to_string());
1012
+ }
1001
1013
  other if other.starts_with("--socket=") => {
1002
1014
  parsed.socket = Some(other.trim_start_matches("--socket=").to_string());
1003
1015
  }
@@ -1097,17 +1109,19 @@ fn quick_start_args(args: &[String], cwd: &Path) -> Result<QuickStartArgs, CliEr
1097
1109
 
1098
1110
  fn models_args(args: &[String]) -> Result<ModelsArgs, CliError> {
1099
1111
  let parsed = parse_args(args);
1100
- let provider = parsed
1101
- .provider
1102
- .ok_or_else(|| CliError::Usage("models requires --provider pi".to_string()))?;
1103
- if provider != "pi" {
1104
- return Err(CliError::Usage(format!(
1105
- "models supports only --provider pi, got {provider:?}"
1106
- )));
1112
+ let provider = parsed.provider.unwrap_or_else(|| "pi".to_string());
1113
+ if parsed.search.is_some() && !parsed.positionals.is_empty() {
1114
+ return Err(CliError::Usage(
1115
+ "models accepts either QUERY or --search TEXT, not both".to_string(),
1116
+ ));
1107
1117
  }
1118
+ let search = parsed
1119
+ .search
1120
+ .or_else(|| (!parsed.positionals.is_empty()).then(|| parsed.positionals.join(" ")))
1121
+ .filter(|query| !query.trim().is_empty());
1108
1122
  Ok(ModelsArgs {
1109
1123
  provider,
1110
- search: parsed.search,
1124
+ search,
1111
1125
  json: parsed.json,
1112
1126
  })
1113
1127
  }
@@ -1683,9 +1697,109 @@ fn sessions_args(args: &[String], cwd: &Path) -> SessionsArgs {
1683
1697
  }
1684
1698
  }
1685
1699
 
1686
- fn leaders_args(args: &[String], _cwd: &Path) -> LeadersArgs {
1687
- let parsed = parse_args(args);
1688
- LeadersArgs { json: parsed.json }
1700
+ fn leaders_args(args: &[String], _cwd: &Path) -> Result<LeadersArgs, CliError> {
1701
+ let mut view = None;
1702
+ let mut query = None;
1703
+ let mut prune = false;
1704
+ let mut dry_run = false;
1705
+ let mut json = false;
1706
+ let mut i = 0usize;
1707
+ while i < args.len() {
1708
+ let arg = &args[i];
1709
+ match arg.as_str() {
1710
+ "--json" => json = true,
1711
+ "--all" => {
1712
+ if view.replace(LeadersView::All).is_some() {
1713
+ return Err(CliError::Usage(
1714
+ "leaders accepts only one of --all or --stale".to_string(),
1715
+ ));
1716
+ }
1717
+ }
1718
+ "--stale" => {
1719
+ if view.replace(LeadersView::Stale).is_some() {
1720
+ return Err(CliError::Usage(
1721
+ "leaders accepts only one of --all or --stale".to_string(),
1722
+ ));
1723
+ }
1724
+ }
1725
+ "--prune" => {
1726
+ if prune {
1727
+ return Err(CliError::Usage(
1728
+ "leaders accepts --prune at most once".to_string(),
1729
+ ));
1730
+ }
1731
+ prune = true;
1732
+ }
1733
+ "--dry-run" => dry_run = true,
1734
+ "--search" => {
1735
+ let Some(value) = args.get(i.saturating_add(1)) else {
1736
+ return Err(CliError::Usage(
1737
+ "leaders --search requires TEXT".to_string(),
1738
+ ));
1739
+ };
1740
+ if value.starts_with('-') {
1741
+ return Err(CliError::Usage(
1742
+ "leaders --search requires TEXT".to_string(),
1743
+ ));
1744
+ }
1745
+ i = i.saturating_add(1);
1746
+ if query.replace(value.clone()).is_some() {
1747
+ return Err(CliError::Usage(
1748
+ "leaders accepts only one QUERY or --search TEXT".to_string(),
1749
+ ));
1750
+ }
1751
+ }
1752
+ value if value.starts_with("--search=") => {
1753
+ let value = value.trim_start_matches("--search=").to_string();
1754
+ if query.replace(value).is_some() {
1755
+ return Err(CliError::Usage(
1756
+ "leaders accepts only one QUERY or --search TEXT".to_string(),
1757
+ ));
1758
+ }
1759
+ }
1760
+ value if value.starts_with('-') => {
1761
+ return Err(CliError::Usage(format!(
1762
+ "unknown leaders argument: {value}"
1763
+ )))
1764
+ }
1765
+ value => {
1766
+ if query.replace(value.to_string()).is_some() {
1767
+ return Err(CliError::Usage(
1768
+ "leaders accepts only one QUERY or --search TEXT".to_string(),
1769
+ ));
1770
+ }
1771
+ }
1772
+ }
1773
+ i = i.saturating_add(1);
1774
+ }
1775
+ if query
1776
+ .as_deref()
1777
+ .is_some_and(|value| value.trim().is_empty())
1778
+ {
1779
+ return Err(CliError::Usage("leaders QUERY cannot be blank".to_string()));
1780
+ }
1781
+ if prune && view.is_some() {
1782
+ return Err(CliError::Usage(
1783
+ "leaders --prune cannot be combined with --all or --stale".to_string(),
1784
+ ));
1785
+ }
1786
+ if prune && query.is_some() {
1787
+ return Err(CliError::Usage(
1788
+ "leaders --prune cannot be combined with QUERY or --search".to_string(),
1789
+ ));
1790
+ }
1791
+ if dry_run && !prune {
1792
+ return Err(CliError::Usage(
1793
+ "leaders --dry-run requires --prune".to_string(),
1794
+ ));
1795
+ }
1796
+ Ok(LeadersArgs {
1797
+ view: view.unwrap_or(LeadersView::Live),
1798
+ query,
1799
+ prune,
1800
+ dry_run,
1801
+ json,
1802
+ })
1689
1803
  }
1690
1804
 
1691
1805
  fn validate_args(args: &[String], cwd: &Path) -> ValidateArgs {
@@ -1935,6 +2049,37 @@ mod tests {
1935
2049
  .collect()
1936
2050
  }
1937
2051
 
2052
+ #[test]
2053
+ fn leaders_parser_supports_views_and_search() {
2054
+ let parsed = leaders_args(
2055
+ &cli_argv(&["--all", "--search", "Wiki", "--json"]),
2056
+ Path::new("."),
2057
+ )
2058
+ .unwrap();
2059
+ assert_eq!(parsed.view, LeadersView::All);
2060
+ assert_eq!(parsed.query.as_deref(), Some("Wiki"));
2061
+ assert!(!parsed.prune);
2062
+ assert!(!parsed.dry_run);
2063
+ assert!(parsed.json);
2064
+ }
2065
+
2066
+ #[test]
2067
+ fn leaders_parser_rejects_invalid_search_and_prune_combinations() {
2068
+ for args in [
2069
+ cli_argv(&["one", "two"]),
2070
+ cli_argv(&["one", "--search", "two"]),
2071
+ cli_argv(&["--search", " "]),
2072
+ cli_argv(&["--dry-run"]),
2073
+ cli_argv(&["--prune", "--stale"]),
2074
+ cli_argv(&["--prune", "--search", "x"]),
2075
+ ] {
2076
+ assert!(
2077
+ leaders_args(&args, Path::new(".")).is_err(),
2078
+ "args={args:?}"
2079
+ );
2080
+ }
2081
+ }
2082
+
1938
2083
  #[test]
1939
2084
  fn command_specs_have_unique_names_and_valid_aliases() {
1940
2085
  let mut names = std::collections::BTreeSet::new();
@@ -2031,6 +2176,16 @@ mod tests {
2031
2176
  }
2032
2177
  }
2033
2178
 
2179
+ #[test]
2180
+ fn leaders_help_publishes_selectors_and_prune() {
2181
+ let help = command_help(Some("leaders"));
2182
+ assert!(help.contains("--all"));
2183
+ assert!(help.contains("--stale"));
2184
+ assert!(help.contains("--search TEXT"));
2185
+ assert!(help.contains("--prune"));
2186
+ assert!(help.contains("--dry-run"));
2187
+ }
2188
+
2034
2189
  #[test]
2035
2190
  fn compat_hidden_help_has_sunset_action() {
2036
2191
  for command in ["stop", "restart-agent", "start", "init"] {
@@ -2235,6 +2390,30 @@ mod tests {
2235
2390
  assert!(!help.contains("错误细分走 status --summary"));
2236
2391
  }
2237
2392
 
2393
+ #[test]
2394
+ fn models_defaults_provider_and_accepts_one_query_form() {
2395
+ let defaulted = models_args(&cli_argv(&["gpt", "5.6", "luna"])).unwrap();
2396
+ assert_eq!(defaulted.provider, "pi");
2397
+ assert_eq!(defaulted.search.as_deref(), Some("gpt 5.6 luna"));
2398
+
2399
+ let cursor = models_args(&cli_argv(&[
2400
+ "--provider",
2401
+ "cursor_agent",
2402
+ "--search",
2403
+ "GPT Luna",
2404
+ ]))
2405
+ .unwrap();
2406
+ assert_eq!(cursor.provider, "cursor_agent");
2407
+ assert_eq!(cursor.search.as_deref(), Some("GPT Luna"));
2408
+
2409
+ let both = models_args(&cli_argv(&["luna", "--search", "gpt"])).unwrap_err();
2410
+ assert!(
2411
+ matches!(both, CliError::Usage(message) if message.contains("either QUERY or --search TEXT"))
2412
+ );
2413
+ let blank = models_args(&cli_argv(&["--search", " "])).unwrap();
2414
+ assert_eq!(blank.search, None);
2415
+ }
2416
+
2238
2417
  #[test]
2239
2418
  fn send_pane_positionals_are_message_not_target() {
2240
2419
  let cwd = tmp_workspace();