返回 CodeWhale
legacy.rs
根目录 / crates / tui / src / commands / groups / plugins / legacy.rs
1 //! Legacy executable plugin-tool inventory (`[tools].plugin_dir`).
2 //!
3 //! These are scripts, not declarative bundles: they are discovered by
4 //! scanning a directory, they carry their own approval requirement, and
5 //! they never share bundle trust state. `/plugin tools` reports them
6 //! read-only — nothing here installs, trusts, or executes anything.
7
8 use std::fmt::Write as _;
9 use std::path::{Path, PathBuf};
10
11 use crate::commands::CommandResult;
12 use crate::localization::{MessageId, tr};
13 use crate::tools::plugin::{PluginMetadata, scan_plugin_dir};
14 use crate::tools::spec::ApprovalRequirement;
15 use crate::tui::app::App;
16
17 use super::action_error;
18
19 pub(super) fn legacy_tools(app: &App, name: Option<&str>) -> CommandResult {
20 let Some(plugin_dir) = plugin_dir_for(app) else {
21 return action_error(
22 app,
23 "Could not resolve the legacy executable plugin-tool directory",
24 );
25 };
26 if !plugin_dir.exists() {
27 return CommandResult::message(
28 tr(app.ui_locale, MessageId::CmdPluginNoneFound)
29 .replace("{dir}", &plugin_dir.display().to_string()),
30 );
31 }
32 let discovered = scan_plugin_dir(&plugin_dir);
33 match name {
34 Some(name) => show_legacy_tool_detail(app, name, &discovered),
35 None => list_legacy_tools(app, &plugin_dir, &discovered),
36 }
37 }
38
39 fn list_legacy_tools(
40 app: &App,
41 plugin_dir: &Path,
42 discovered: &[(PathBuf, PluginMetadata)],
43 ) -> CommandResult {
44 if discovered.is_empty() {
45 return CommandResult::message(
46 tr(app.ui_locale, MessageId::CmdPluginNoneFound)
47 .replace("{dir}", &plugin_dir.display().to_string()),
48 );
49 }
50 let mut output = tr(app.ui_locale, MessageId::CmdPluginLegacyListHeader)
51 .replace("{count}", &discovered.len().to_string())
52 .replace("{dir}", &plugin_dir.display().to_string());
53 output.push('\n');
54 for (path, metadata) in discovered {
55 let _ = writeln!(
56 output,
57 "• {} — {}\n {}",
58 metadata.name,
59 metadata.description,
60 path.display()
61 );
62 }
63 CommandResult::message(output)
64 }
65
66 fn show_legacy_tool_detail(
67 app: &App,
68 name: &str,
69 discovered: &[(PathBuf, PluginMetadata)],
70 ) -> CommandResult {
71 let Some((path, metadata)) = discovered
72 .iter()
73 .find(|(_, metadata)| metadata.name == name)
74 else {
75 return CommandResult::error(
76 tr(app.ui_locale, MessageId::CmdPluginNotFound).replace("{name}", name),
77 );
78 };
79 let schema = serde_json::to_string_pretty(&metadata.input_schema).unwrap_or_default();
80 let mut output = format!("{}\n{:=<40}\n", metadata.name, "");
81 let _ = writeln!(
82 output,
83 "{}",
84 tr(app.ui_locale, MessageId::CmdPluginDetailDescription)
85 .replace("{description}", &metadata.description)
86 );
87 let _ = writeln!(
88 output,
89 "{}",
90 tr(app.ui_locale, MessageId::CmdPluginDetailSchema).replace("{schema}", &schema)
91 );
92 let _ = writeln!(
93 output,
94 "{}",
95 tr(app.ui_locale, MessageId::CmdPluginDetailApproval)
96 .replace("{approval}", approval_label(metadata.approval))
97 );
98 let _ = writeln!(
99 output,
100 "{}",
101 tr(app.ui_locale, MessageId::CmdPluginDetailPath)
102 .replace("{path}", &path.display().to_string())
103 );
104 CommandResult::message(output)
105 }
106
107 pub(super) fn scan_legacy_tools(app: &App) -> Option<(PathBuf, Vec<(PathBuf, PluginMetadata)>)> {
108 let dir = plugin_dir_for(app)?;
109 dir.exists().then(|| {
110 let tools = scan_plugin_dir(&dir);
111 (dir, tools)
112 })
113 }
114
115 fn approval_label(approval: ApprovalRequirement) -> &'static str {
116 match approval {
117 ApprovalRequirement::Auto => "auto",
118 ApprovalRequirement::Suggest => "suggest",
119 ApprovalRequirement::Required => "required",
120 }
121 }
122
123 fn plugin_dir_for(app: &App) -> Option<PathBuf> {
124 app.legacy_plugin_tools_dir
125 .clone()
126 .or_else(default_codewhale_tools_dir)
127 }
128
129 fn default_codewhale_tools_dir() -> Option<PathBuf> {
130 codewhale_config::codewhale_home()
131 .ok()
132 .map(|home| home.join("tools"))
133 }
134
134 lines RUST