返回 CodeWhale
setup.rs
根目录 / crates / tui / src / commands / groups / core / setup.rs
1 //! `/setup` command.
2
3 use crate::commands::traits::{CommandInfo, RegisterCommand};
4 use crate::config::ApiProvider;
5 use crate::localization::MessageId;
6 use crate::tui::app::{App, AppAction};
7
8 use super::CommandResult;
9 use codewhale_config::SetupStep;
10
11 pub(in crate::commands) const COMMAND_INFO: CommandInfo = CommandInfo {
12 name: "setup",
13 aliases: &[],
14 usage: "/setup",
15 description_id: MessageId::CmdSetupDescription,
16 };
17
18 pub(in crate::commands) struct SetupCmd;
19
20 impl RegisterCommand for SetupCmd {
21 fn info() -> &'static CommandInfo {
22 &COMMAND_INFO
23 }
24
25 fn execute(_app: &mut App, arg: Option<&str>) -> CommandResult {
26 let target = arg.map(str::trim).filter(|arg| !arg.is_empty());
27 if let Some(arg) = target {
28 let mut parts = arg.split_whitespace();
29 if matches!(parts.next(), Some("provider" | "providers"))
30 && let Some(raw_provider) = parts.next()
31 {
32 if parts.next().is_some() {
33 return CommandResult::error(
34 "Usage: /setup provider [provider-name]".to_string(),
35 );
36 }
37 let Some(provider) = ApiProvider::parse(raw_provider) else {
38 return CommandResult::error(format!(
39 "Unknown provider '{raw_provider}'. Expected: {}.",
40 ApiProvider::names_hint()
41 ));
42 };
43 return CommandResult::action(AppAction::OpenProviderSetup {
44 provider: Some(provider),
45 });
46 }
47 }
48
49 match target {
50 None | Some("open" | "wizard" | "checkpoint") => {
51 CommandResult::action(AppAction::OpenSetupWizard)
52 }
53 Some("provider" | "providers" | "model" | "models" | "route") => {
54 CommandResult::action(AppAction::OpenSetupWizardAt {
55 step: SetupStep::ProviderModel,
56 })
57 }
58 Some("runtime" | "posture" | "trust" | "sandbox") => {
59 CommandResult::action(AppAction::OpenSetupWizardAt {
60 step: SetupStep::TrustSandbox,
61 })
62 }
63 Some("constitution" | "law") => CommandResult::action(AppAction::OpenSetupWizardAt {
64 step: SetupStep::Constitution,
65 }),
66 Some("status" | "report" | "verification" | "verify") => {
67 CommandResult::action(AppAction::OpenSetupWizardAt {
68 step: SetupStep::Verification,
69 })
70 }
71 Some("operate" | "fleet" | "operate-fleet" | "operate_fleet") => {
72 CommandResult::action(AppAction::OpenSetupWizardAt {
73 step: SetupStep::OperateFleet,
74 })
75 }
76 Some("hotbar" | "hotkeys" | "shortcuts" | "keys") => {
77 CommandResult::action(AppAction::OpenSetupWizardAt {
78 step: SetupStep::Hotbar,
79 })
80 }
81 Some("tools" | "tool" | "mcp" | "tools-mcp" | "tools_mcp" | "skills" | "plugins") => {
82 CommandResult::action(AppAction::OpenSetupWizardAt {
83 step: SetupStep::ToolsMcp,
84 })
85 }
86 Some(
87 "remote" | "remote-runtime" | "remote_runtime" | "cloud" | "bridge" | "mobile"
88 | "phone",
89 ) => CommandResult::action(AppAction::OpenSetupWizardAt {
90 step: SetupStep::RemoteRuntime,
91 }),
92 Some("persistence" | "persist" | "storage") => {
93 CommandResult::action(AppAction::OpenSetupWizardAt {
94 step: SetupStep::Persistence,
95 })
96 }
97 Some(other) => CommandResult::error(format!(
98 "Unknown /setup target '{other}'. Try `/setup` to open the setup wizard."
99 )),
100 }
101 }
102 }
103
104 #[cfg(test)]
105 mod tests {
106 use super::*;
107 use crate::config::Config;
108 use crate::tui::app::TuiOptions;
109 use std::path::PathBuf;
110
111 fn test_app() -> App {
112 let options = TuiOptions {
113 ..crate::test_support::test_tui_options(PathBuf::from("."))
114 };
115 App::new(options, &Config::default())
116 }
117
118 #[test]
119 fn setup_command_opens_wizard() {
120 let mut app = test_app();
121
122 let result = SetupCmd::execute(&mut app, None);
123
124 assert_eq!(result.action, Some(AppAction::OpenSetupWizard));
125 assert!(result.message.is_none());
126 }
127
128 #[test]
129 fn setup_checkpoint_alias_opens_wizard() {
130 let mut app = test_app();
131
132 let result = SetupCmd::execute(&mut app, Some("checkpoint"));
133
134 assert_eq!(result.action, Some(AppAction::OpenSetupWizard));
135 assert!(result.message.is_none());
136 }
137
138 #[test]
139 fn setup_report_opens_verification_step() {
140 let mut app = test_app();
141
142 let result = SetupCmd::execute(&mut app, Some("report"));
143
144 assert_eq!(
145 result.action,
146 Some(AppAction::OpenSetupWizardAt {
147 step: SetupStep::Verification
148 })
149 );
150 assert!(result.message.is_none());
151 }
152
153 #[test]
154 fn setup_named_steps_open_matching_wizard_cards() {
155 let cases = [
156 ("provider", SetupStep::ProviderModel),
157 ("model", SetupStep::ProviderModel),
158 ("runtime", SetupStep::TrustSandbox),
159 ("posture", SetupStep::TrustSandbox),
160 ("constitution", SetupStep::Constitution),
161 ("hotbar", SetupStep::Hotbar),
162 ("shortcuts", SetupStep::Hotbar),
163 ("tools", SetupStep::ToolsMcp),
164 ("tool", SetupStep::ToolsMcp),
165 ("tools-mcp", SetupStep::ToolsMcp),
166 ("tools_mcp", SetupStep::ToolsMcp),
167 ("mcp", SetupStep::ToolsMcp),
168 ("skills", SetupStep::ToolsMcp),
169 ("plugins", SetupStep::ToolsMcp),
170 ("remote", SetupStep::RemoteRuntime),
171 ("cloud", SetupStep::RemoteRuntime),
172 ("persistence", SetupStep::Persistence),
173 ("persist", SetupStep::Persistence),
174 ("storage", SetupStep::Persistence),
175 ];
176
177 for (arg, step) in cases {
178 let mut app = test_app();
179 let result = SetupCmd::execute(&mut app, Some(arg));
180 assert_eq!(
181 result.action,
182 Some(AppAction::OpenSetupWizardAt { step }),
183 "{arg}"
184 );
185 assert!(result.message.is_none(), "{arg}");
186 }
187 }
188
189 #[test]
190 fn setup_fleet_opens_operate_readiness_step() {
191 let mut app = test_app();
192
193 let result = SetupCmd::execute(&mut app, Some("fleet"));
194
195 assert_eq!(
196 result.action,
197 Some(AppAction::OpenSetupWizardAt {
198 step: SetupStep::OperateFleet
199 })
200 );
201 assert!(result.message.is_none());
202 }
203
204 #[test]
205 fn setup_provider_named_opens_provider_setup_catalog() {
206 let mut app = test_app();
207
208 let result = SetupCmd::execute(&mut app, Some("provider anthropic"));
209
210 assert_eq!(
211 result.action,
212 Some(AppAction::OpenProviderSetup {
213 provider: Some(ApiProvider::Anthropic)
214 })
215 );
216 assert!(result.message.is_none());
217 }
218
219 #[test]
220 fn setup_provider_named_rejects_unknown_provider() {
221 let mut app = test_app();
222
223 let result = SetupCmd::execute(&mut app, Some("provider imaginary"));
224
225 assert!(result.action.is_none());
226 assert!(
227 result
228 .message
229 .as_deref()
230 .is_some_and(|message| message.contains("Unknown provider 'imaginary'"))
231 );
232 }
233 }
234
234 lines RUST