返回 CodeWhale
mod.rs
根目录 / crates / tui / src / commands / groups / config / mod.rs
1 //! Config command area: settings, modes, themes, trust, and status surfaces.
2
3 // This group dir intentionally has a `config.rs` child module with the same
4 // name. The module_inception allow is a permanent structure rationale, not
5 // migration scaffolding; see docs/architecture/command-dispatch.md.
6 #[allow(clippy::module_inception)]
7 pub mod config;
8 mod import_claude;
9 mod permissions;
10 mod status;
11
12 use crate::commands::CommandResult;
13 use crate::commands::traits::{Command, CommandGroup, CommandInfo, FunctionCommand};
14 use crate::tui::app::App;
15 use codewhale_localization::MessageId;
16
17 pub struct ConfigCommands;
18
19 impl CommandGroup for ConfigCommands {
20 fn commands(&self) -> &'static [Box<dyn Command>] {
21 cached_command_list!(vec![
22 Box::new(FunctionCommand::new(&CONFIG_INFO, run_config)),
23 Box::new(FunctionCommand::new(&IMPORT_CLAUDE_INFO, run_import_claude)),
24 Box::new(FunctionCommand::new(&PERMISSIONS_INFO, run_permissions)),
25 Box::new(FunctionCommand::new(&LOGIN_INFO, run_login)),
26 Box::new(FunctionCommand::new(&AUTH_INFO, run_auth)),
27 Box::new(FunctionCommand::new(&RAIL_INFO, run_rail)),
28 Box::new(FunctionCommand::new(&PET_INFO, run_pet)),
29 Box::new(FunctionCommand::new(&SETTINGS_INFO, run_settings)),
30 Box::new(FunctionCommand::new(&STATUS_INFO, run_status)),
31 Box::new(FunctionCommand::new(&STATUSLINE_INFO, run_statusline)),
32 Box::new(FunctionCommand::new(&MODE_INFO, run_mode)),
33 Box::new(FunctionCommand::new(&FULLSCREEN_INFO, run_fullscreen)),
34 Box::new(FunctionCommand::new(&INLINE_INFO, run_inline)),
35 Box::new(FunctionCommand::new(&THEME_INFO, run_theme)),
36 Box::new(FunctionCommand::new(&VERBOSE_INFO, run_verbose)),
37 Box::new(FunctionCommand::new(&TRUST_INFO, run_trust)),
38 Box::new(FunctionCommand::new(&LOGOUT_INFO, run_logout)),
39 ])
40 }
41 }
42
43 static CONFIG_INFO: CommandInfo = CommandInfo {
44 name: "config",
45 aliases: &[],
46 usage: "/config [ask-rules|status|<key> [value]]",
47 description_id: MessageId::CmdConfigDescription,
48 };
49 static IMPORT_CLAUDE_INFO: CommandInfo = CommandInfo {
50 name: "import-claude",
51 aliases: &["import_claude"],
52 usage: "/import-claude [--apply]",
53 description_id: MessageId::CmdImportClaudeDescription,
54 };
55 static PERMISSIONS_INFO: CommandInfo = CommandInfo {
56 name: "permissions",
57 aliases: &["permission-rules", "permission_rules"],
58 usage: "/permissions [list|remove <rule-number> [--confirm <token>]]",
59 description_id: MessageId::CmdPermissionsDescription,
60 };
61 static LOGIN_INFO: CommandInfo = CommandInfo {
62 name: "login",
63 aliases: &[],
64 usage: "/login [status|account|key]",
65 description_id: MessageId::CmdLoginDescription,
66 };
67 static AUTH_INFO: CommandInfo = CommandInfo {
68 name: "auth",
69 aliases: &[],
70 usage: "/auth xai-device|chatgpt|chatgpt-revoke",
71 description_id: MessageId::CmdAuthDescription,
72 };
73 static RAIL_INFO: CommandInfo = CommandInfo {
74 name: "workbar",
75 // /rail and /sidebar are the names users already know; both now drive
76 // the one workbar.
77 aliases: &["rail", "sidebar"],
78 usage: "/workbar [bottom|top|left|right|off|tasks|agents|context|pinned] [--save]",
79 description_id: MessageId::CmdSidebarDescription,
80 };
81 static PET_INFO: CommandInfo = CommandInfo {
82 name: "pet",
83 aliases: &[],
84 usage: "/pet [on|off|status|appearance|window|source|export|sound on|off]",
85 description_id: MessageId::CmdPetDescription,
86 };
87 static SETTINGS_INFO: CommandInfo = CommandInfo {
88 name: "settings",
89 aliases: &[],
90 usage: "/settings [text]",
91 description_id: MessageId::CmdSettingsDescription,
92 };
93 static STATUS_INFO: CommandInfo = CommandInfo {
94 name: "status",
95 aliases: &[],
96 usage: "/status",
97 description_id: MessageId::CmdStatusDescription,
98 };
99 static STATUSLINE_INFO: CommandInfo = CommandInfo {
100 name: "statusline",
101 aliases: &[],
102 usage: "/statusline",
103 description_id: MessageId::CmdStatuslineDescription,
104 };
105 static MODE_INFO: CommandInfo = CommandInfo {
106 name: "mode",
107 aliases: &["jihua", "zidong"],
108 usage: "/mode [act|plan|operate|1|2|3]",
109 description_id: MessageId::CmdModeDescription,
110 };
111 static FULLSCREEN_INFO: CommandInfo = CommandInfo {
112 name: "fullscreen",
113 aliases: &[],
114 usage: "/fullscreen",
115 description_id: MessageId::CmdFullscreenDescription,
116 };
117 static INLINE_INFO: CommandInfo = CommandInfo {
118 name: "inline",
119 aliases: &[],
120 usage: "/inline",
121 description_id: MessageId::CmdInlineDescription,
122 };
123 static THEME_INFO: CommandInfo = CommandInfo {
124 name: "theme",
125 aliases: &[],
126 usage: "/theme [name|underwater|custom:<name>|schema|path]",
127 description_id: MessageId::CmdThemeDescription,
128 };
129 static VERBOSE_INFO: CommandInfo = CommandInfo {
130 name: "verbose",
131 aliases: &[],
132 usage: "/verbose [on|off]",
133 description_id: MessageId::CmdVerboseDescription,
134 };
135 static TRUST_INFO: CommandInfo = CommandInfo {
136 name: "trust",
137 aliases: &["xinren"],
138 usage: "/trust [on|off|add <path>|remove <path>|list]",
139 description_id: MessageId::CmdTrustDescription,
140 };
141 static LOGOUT_INFO: CommandInfo = CommandInfo {
142 name: "logout",
143 aliases: &[],
144 usage: "/logout",
145 description_id: MessageId::CmdLogoutDescription,
146 };
147 fn run_registered(app: &mut App, name: &str, arg: Option<&str>) -> CommandResult {
148 dispatch(app, name, arg).expect("registered config command should dispatch")
149 }
150
151 fn run_config(app: &mut App, arg: Option<&str>) -> CommandResult {
152 run_registered(app, "config", arg)
153 }
154 fn run_import_claude(app: &mut App, arg: Option<&str>) -> CommandResult {
155 import_claude::import_claude_command(app, arg)
156 }
157 fn run_permissions(app: &mut App, arg: Option<&str>) -> CommandResult {
158 run_registered(app, "permissions", arg)
159 }
160 fn run_login(app: &mut App, arg: Option<&str>) -> CommandResult {
161 run_registered(app, "login", arg)
162 }
163 fn run_auth(app: &mut App, arg: Option<&str>) -> CommandResult {
164 run_registered(app, "auth", arg)
165 }
166 fn run_rail(app: &mut App, arg: Option<&str>) -> CommandResult {
167 run_registered(app, "workbar", arg)
168 }
169 fn run_pet(app: &mut App, arg: Option<&str>) -> CommandResult {
170 run_registered(app, "pet", arg)
171 }
172 fn run_settings(app: &mut App, arg: Option<&str>) -> CommandResult {
173 run_registered(app, "settings", arg)
174 }
175 fn run_status(app: &mut App, arg: Option<&str>) -> CommandResult {
176 run_registered(app, "status", arg)
177 }
178 fn run_statusline(app: &mut App, arg: Option<&str>) -> CommandResult {
179 run_registered(app, "statusline", arg)
180 }
181 fn run_mode(app: &mut App, arg: Option<&str>) -> CommandResult {
182 run_registered(app, "mode", arg)
183 }
184 fn run_fullscreen(app: &mut App, arg: Option<&str>) -> CommandResult {
185 run_registered(app, "fullscreen", arg)
186 }
187 fn run_inline(app: &mut App, arg: Option<&str>) -> CommandResult {
188 run_registered(app, "inline", arg)
189 }
190 fn run_theme(app: &mut App, arg: Option<&str>) -> CommandResult {
191 run_registered(app, "theme", arg)
192 }
193 fn run_verbose(app: &mut App, arg: Option<&str>) -> CommandResult {
194 run_registered(app, "verbose", arg)
195 }
196 fn run_trust(app: &mut App, arg: Option<&str>) -> CommandResult {
197 run_registered(app, "trust", arg)
198 }
199 fn run_logout(app: &mut App, arg: Option<&str>) -> CommandResult {
200 run_registered(app, "logout", arg)
201 }
202 pub(in crate::commands) fn dispatch(
203 app: &mut App,
204 command: &str,
205 arg: Option<&str>,
206 ) -> Option<CommandResult> {
207 let result = match command {
208 "config" => config::config_command(app, arg),
209 "permissions" | "permission-rules" | "permission_rules" => {
210 permissions::permissions_command(app, arg)
211 }
212 "login" => config::login(app, arg),
213 "auth" => match arg.map(str::trim) {
214 Some("xai-device") | Some("xai_device") => {
215 CommandResult::action(crate::tui::app::AppAction::StartXaiDeviceLogin)
216 }
217 Some("chatgpt") | Some("openai-codex") | Some("openai_codex") => {
218 CommandResult::action(crate::tui::app::AppAction::StartChatgptPkceLogin)
219 }
220 Some("chatgpt-revoke") | Some("chatgpt_revoke") => {
221 CommandResult::action(crate::tui::app::AppAction::StartChatgptRevoke)
222 }
223 _ => CommandResult::error("Usage: /auth xai-device|chatgpt|chatgpt-revoke"),
224 },
225 "workbar" | "rail" | "sidebar" => config::sidebar(app, arg),
226 "pet" => config::pet(app, arg),
227 "settings" => config::settings_command(app, arg),
228 "status" => status::status(app),
229 "statusline" => config::status_line(app),
230 "mode" => config::mode(app, arg),
231 "fullscreen" => config::screen(app, crate::tui::app::ScreenMode::Fullscreen, arg),
232 "inline" => config::screen(app, crate::tui::app::ScreenMode::Inline, arg),
233 "jihua" => config::mode(app, Some("plan")),
234 "zidong" => config::mode(app, Some("yolo")),
235 "theme" => config::theme(app, arg),
236 "verbose" => config::verbose(app, arg),
237 "trust" | "xinren" => config::trust(app, arg),
238 "logout" => config::logout(app),
239 _ => return None,
240 };
241 Some(result)
242 }
243
244 /// `/workflow settings` and `/config workflow`: the effective `[workflow]`
245 /// and `[goal]` tables with what each value does, read from the refreshed
246 /// session table after a config.toml reload (no model turn). The workflow
247 /// tool reads the same table, so the two surfaces cannot disagree. This
248 /// surface explains, it does not edit.
249 pub(in crate::commands) fn workflow_settings(app: &App) -> CommandResult {
250 let refreshed = crate::tools::workflow::session_workflow_config(&app.workspace);
251 let cfg = refreshed.as_ref().unwrap_or(&app.workflow_config);
252 let on = |value: bool| if value { "on" } else { "off" };
253 let lines = [
254 "[workflow] — config.toml".to_string(),
255 format!(
256 "automatic = {} · the agent may start a workflow itself for broad or staged work; off means only /workflow starts one",
257 on(cfg.automatic)
258 ),
259 format!(
260 "auto_start_read_only = {} · read-only plans start without an approval card",
261 on(cfg.auto_start_read_only)
262 ),
263 format!(
264 "require_approval_for_writes = {} · plans that write, use shell/network, or elevate show an approval card first",
265 on(cfg.require_approval_for_writes)
266 ),
267 format!(
268 "max_children = {} · max_concurrent = {} · max_depth = {} · hard ceilings for one run",
269 cfg.max_children, cfg.max_concurrent, cfg.max_depth
270 ),
271 format!(
272 "default_token_budget = {} · shared admission cap for a run and its children (0 = none)",
273 cfg.default_token_budget
274 ),
275 String::new(),
276 "[goal] — config.toml".to_string(),
277 format!(
278 "max_continuations = {} · automatic continuation passes before a goal pauses; 0 = unlimited (completion, blocked, or you stop it)",
279 app.goal_max_continuations
280 ),
281 format!(
282 "enforce_token_budget = {} · true = a goal's token budget is a hard stop; false = advisory telemetry",
283 on(app.goal_enforce_token_budget)
284 ),
285 ];
286 CommandResult::message(lines.join("\n"))
287 }
288
288 lines RUST