返回 CodeWhale
sessions.rs
根目录 / crates / tui / src / commands / groups / session / sessions.rs
1 //! `/sessions` command — picker UI or housekeeping sub-actions.
2
3 use super::CommandResult;
4
5 use codewhale_command_contract::facets::CommandSessionLifecycleContext;
6 use codewhale_command_contract::handler::{CommandContexts, CommandHandler};
7 use codewhale_command_contract::metadata::{
8 CommandInfo as ContractInfo, RegisterCommand as ContractRegisterCommand,
9 };
10
11 pub(in crate::commands) struct SessionsCmd;
12
13 // ---------------------------------------------------------------------------
14 // FEAT-023 Phase 4 (D3/D5/D6): portable contextual registration and handler.
15 // ---------------------------------------------------------------------------
16
17 pub(in crate::commands) const CONTRACT_INFO: ContractInfo = ContractInfo {
18 name: "sessions",
19 aliases: &[],
20 usage: "/sessions [show|open <id>|archive <id>|unarchive <id>|prune <days>]",
21 description_key: "cmd_sessions_description",
22 };
23
24 impl ContractRegisterCommand<CommandResult> for SessionsCmd {
25 fn info() -> &'static ContractInfo {
26 &CONTRACT_INFO
27 }
28 fn handler() -> CommandHandler<CommandResult> {
29 CommandHandler::Contextual {
30 capabilities:
31 codewhale_command_contract::handler::CommandCapabilities::SESSION_LIFECYCLE,
32 handler: sessions_contextual,
33 }
34 }
35 }
36
37 pub(in crate::commands) fn sessions_contextual(
38 contexts: CommandContexts<'_>,
39 arg: Option<&str>,
40 ) -> CommandResult {
41 let mut parts = contexts.into_parts();
42 let Some(lifecycle) = parts.lifecycle.as_deref_mut() else {
43 return CommandResult::error(
44 "Command capability unavailable: session_lifecycle".to_string(),
45 );
46 };
47 sessions_portable(lifecycle, arg)
48 }
49
50 pub(in crate::commands) fn sessions_portable(
51 lifecycle: &mut dyn CommandSessionLifecycleContext,
52 arg: Option<&str>,
53 ) -> CommandResult {
54 let trimmed = arg.unwrap_or("").trim();
55 if trimmed.is_empty() {
56 lifecycle.open_picker(None);
57 return CommandResult::ok();
58 }
59
60 let mut parts = trimmed.split_whitespace();
61 let action = parts.next().unwrap_or("").to_ascii_lowercase();
62 match action.as_str() {
63 "prune" => {
64 let days_str = match parts.next() {
65 Some(s) => s,
66 None => {
67 return CommandResult::error(
68 "usage: /sessions prune <days> (e.g. `/sessions prune 30` to drop sessions older than 30 days)"
69 .to_string(),
70 );
71 }
72 };
73 let days: u64 = match days_str.parse() {
74 Ok(n) if n > 0 => n,
75 _ => {
76 return CommandResult::error(format!(
77 "expected a positive integer number of days, got `{days_str}`"
78 ));
79 }
80 };
81 match lifecycle.prune_sessions(days) {
82 Ok(0) => CommandResult::message(format!("no sessions older than {days}d to prune")),
83 Ok(n) => CommandResult::message(format!(
84 "pruned {n} session{} older than {days}d",
85 if n == 1 { "" } else { "s" }
86 )),
87 Err(error) => CommandResult::error(error),
88 }
89 }
90 "show" | "list" | "picker" => {
91 lifecycle.open_picker(None);
92 CommandResult::ok()
93 }
94 "open" => {
95 let Some(session_id) = parts.next().map(str::trim).filter(|id| !id.is_empty()) else {
96 return CommandResult::error("usage: /sessions open <session-id>".to_string());
97 };
98 lifecycle.open_picker(Some(session_id.to_string()));
99 CommandResult::ok()
100 }
101 "archive" | "unarchive" | "restore" => {
102 let archived = action == "archive";
103 let verb = if archived { "archive" } else { "unarchive" };
104 let Some(session_id) = parts.next().map(str::trim).filter(|id| !id.is_empty()) else {
105 return CommandResult::error(format!("usage: /sessions {verb} <session-id>"));
106 };
107 match lifecycle.set_archived(session_id, archived) {
108 Ok(receipt) => CommandResult::message(format!(
109 "{} session {} ({})",
110 if archived { "Archived" } else { "Restored" },
111 receipt.truncated_id,
112 receipt.title
113 )),
114 Err(error) => CommandResult::error(error),
115 }
116 }
117 _ => CommandResult::error(format!(
118 "unknown subcommand `{action}`. usage: /sessions [show|open <id>|archive <id>|unarchive <id>|prune <days>]"
119 )),
120 }
121 }
122
122 lines RUST