1use runtime::{compact_session, CompactionConfig, Session};
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub struct CommandManifestEntry {
5 pub name: String,
6 pub source: CommandSource,
7}
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum CommandSource {
11 Builtin,
12 InternalOnly,
13 FeatureGated,
14}
15
16#[derive(Debug, Clone, Default, PartialEq, Eq)]
17pub struct CommandRegistry {
18 entries: Vec<CommandManifestEntry>,
19}
20
21impl CommandRegistry {
22 #[must_use]
23 pub fn new(entries: Vec<CommandManifestEntry>) -> Self {
24 Self { entries }
25 }
26
27 #[must_use]
28 pub fn entries(&self) -> &[CommandManifestEntry] {
29 &self.entries
30 }
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub struct SlashCommandSpec {
35 pub name: &'static str,
36 pub summary: &'static str,
37 pub argument_hint: Option<&'static str>,
38 pub resume_supported: bool,
39}
40
41const SLASH_COMMAND_SPECS: &[SlashCommandSpec] = &[
42 SlashCommandSpec {
43 name: "help",
44 summary: "Show available slash commands",
45 argument_hint: None,
46 resume_supported: true,
47 },
48 SlashCommandSpec {
49 name: "status",
50 summary: "Show current session status",
51 argument_hint: None,
52 resume_supported: true,
53 },
54 SlashCommandSpec {
55 name: "compact",
56 summary: "Compact local session history",
57 argument_hint: None,
58 resume_supported: true,
59 },
60 SlashCommandSpec {
61 name: "compress",
62 summary: "Aggressively compress session history into a summary",
63 argument_hint: None,
64 resume_supported: true,
65 },
66 SlashCommandSpec {
67 name: "model",
68 summary: "Show or switch the active model",
69 argument_hint: Some("[model]"),
70 resume_supported: false,
71 },
72 SlashCommandSpec {
73 name: "permissions",
74 summary: "Show or switch the active permission mode",
75 argument_hint: Some("[read-only|workspace-write|danger-full-access]"),
76 resume_supported: false,
77 },
78 SlashCommandSpec {
79 name: "clear",
80 summary: "Start a fresh local session",
81 argument_hint: Some("[--confirm]"),
82 resume_supported: true,
83 },
84 SlashCommandSpec {
85 name: "cost",
86 summary: "Show cumulative token usage for this session",
87 argument_hint: None,
88 resume_supported: true,
89 },
90 SlashCommandSpec {
91 name: "resume",
92 summary: "Load a saved session into the REPL",
93 argument_hint: Some("<session-path>"),
94 resume_supported: false,
95 },
96 SlashCommandSpec {
97 name: "config",
98 summary: "Inspect Ternlang config files or merged sections",
99 argument_hint: Some("[env|hooks|model]"),
100 resume_supported: true,
101 },
102 SlashCommandSpec {
103 name: "memory",
104 summary: "Inspect loaded Ternlang instruction memory files",
105 argument_hint: None,
106 resume_supported: true,
107 },
108 SlashCommandSpec {
109 name: "init",
110 summary: "Create a starter ALBERT.md for this repo",
111 argument_hint: None,
112 resume_supported: true,
113 },
114 SlashCommandSpec {
115 name: "diff",
116 summary: "Show git diff for current workspace changes",
117 argument_hint: None,
118 resume_supported: true,
119 },
120 SlashCommandSpec {
121 name: "version",
122 summary: "Show CLI version and build information",
123 argument_hint: None,
124 resume_supported: true,
125 },
126 SlashCommandSpec {
127 name: "bughunter",
128 summary: "Inspect the codebase for likely bugs",
129 argument_hint: Some("[scope]"),
130 resume_supported: false,
131 },
132 SlashCommandSpec {
133 name: "commit",
134 summary: "Generate a commit message and create a git commit",
135 argument_hint: None,
136 resume_supported: false,
137 },
138 SlashCommandSpec {
139 name: "pr",
140 summary: "Draft or create a pull request from the conversation",
141 argument_hint: Some("[context]"),
142 resume_supported: false,
143 },
144 SlashCommandSpec {
145 name: "issue",
146 summary: "Draft or create a GitHub issue from the conversation",
147 argument_hint: Some("[context]"),
148 resume_supported: false,
149 },
150 SlashCommandSpec {
151 name: "ultraplan",
152 summary: "Run a deep planning prompt with multi-step reasoning",
153 argument_hint: Some("[task]"),
154 resume_supported: false,
155 },
156 SlashCommandSpec {
157 name: "teleport",
158 summary: "Jump to a file or symbol by searching the workspace",
159 argument_hint: Some("<symbol-or-path>"),
160 resume_supported: false,
161 },
162 SlashCommandSpec {
163 name: "debug-tool-call",
164 summary: "Replay the last tool call with debug details",
165 argument_hint: None,
166 resume_supported: false,
167 },
168 SlashCommandSpec {
169 name: "export",
170 summary: "Export the current conversation to a file",
171 argument_hint: Some("[file]"),
172 resume_supported: true,
173 },
174 SlashCommandSpec {
175 name: "session",
176 summary: "List or switch managed local sessions",
177 argument_hint: Some("[list|switch <session-id>]"),
178 resume_supported: false,
179 },
180 SlashCommandSpec {
181 name: "auth",
182 summary: "Configure LLM provider and API keys",
183 argument_hint: Some("[provider]"),
184 resume_supported: false,
185 },
186 SlashCommandSpec {
187 name: "plan",
188 summary: "Restate requirements and assess risks before implementation",
189 argument_hint: Some("[task]"),
190 resume_supported: false,
191 },
192 SlashCommandSpec {
193 name: "tdd",
194 summary: "Enforce test-driven development workflow",
195 argument_hint: Some("[interface]"),
196 resume_supported: false,
197 },
198 SlashCommandSpec {
199 name: "verify",
200 summary: "Run full verification: build, lint, test, and type-check",
201 argument_hint: None,
202 resume_supported: false,
203 },
204 SlashCommandSpec {
205 name: "code-review",
206 summary: "Full quality, security, and maintainability review",
207 argument_hint: Some("[files]"),
208 resume_supported: false,
209 },
210 SlashCommandSpec {
211 name: "build-fix",
212 summary: "Automatically detect and fix build errors",
213 argument_hint: None,
214 resume_supported: false,
215 },
216 SlashCommandSpec {
217 name: "aside",
218 summary: "Ask a quick side question without losing context",
219 argument_hint: Some("<question>"),
220 resume_supported: false,
221 },
222 SlashCommandSpec {
223 name: "learn",
224 summary: "Extract reusable patterns from the current session",
225 argument_hint: None,
226 resume_supported: false,
227 },
228 SlashCommandSpec {
229 name: "refactor",
230 summary: "Remove dead code and consolidate structure",
231 argument_hint: Some("[scope]"),
232 resume_supported: false,
233 },
234 SlashCommandSpec {
235 name: "checkpoint",
236 summary: "Mark a checkpoint in the current session",
237 argument_hint: Some("[label]"),
238 resume_supported: false,
239 },
240 SlashCommandSpec {
241 name: "docs",
242 summary: "Look up library or API documentation",
243 argument_hint: Some("<query>"),
244 resume_supported: false,
245 },
246 SlashCommandSpec {
247 name: "loop",
248 summary: "Engage autopilot loop to complete a mission",
249 argument_hint: Some("<mission>"),
250 resume_supported: false,
251 },
252];
253
254#[derive(Debug, Clone, PartialEq, Eq)]
255pub enum SlashCommand {
256 Help,
257 Status,
258 Compact,
259 Compress,
260 Bughunter {
261 scope: Option<String>,
262 },
263 Commit,
264 Pr {
265 context: Option<String>,
266 },
267 Issue {
268 context: Option<String>,
269 },
270 Ultraplan {
271 task: Option<String>,
272 },
273 Teleport {
274 target: Option<String>,
275 },
276 DebugToolCall,
277 Model {
278 model: Option<String>,
279 },
280 Permissions {
281 mode: Option<String>,
282 },
283 Clear {
284 confirm: bool,
285 },
286 Cost,
287 Resume {
288 session_path: Option<String>,
289 },
290 Config {
291 section: Option<String>,
292 },
293 Memory,
294 Init,
295 Diff,
296 Version,
297 Export {
298 path: Option<String>,
299 },
300 Session {
301 action: Option<String>,
302 target: Option<String>,
303 },
304 Auth {
305 provider: Option<String>,
306 },
307 Plan {
308 task: Option<String>,
309 },
310 Tdd {
311 interface: Option<String>,
312 },
313 Verify,
314 CodeReview {
315 files: Option<String>,
316 },
317 BuildFix,
318 Aside {
319 question: Option<String>,
320 },
321 Learn,
322 Refactor {
323 scope: Option<String>,
324 },
325 Checkpoint {
326 label: Option<String>,
327 },
328 Docs {
329 query: Option<String>,
330 },
331 Loop {
332 mission: Option<String>,
333 },
334 Unknown(String),
335}
336
337impl SlashCommand {
338 #[must_use]
339 pub fn parse(input: &str) -> Option<Self> {
340 let trimmed = input.trim();
341 if !trimmed.starts_with('/') {
342 return None;
343 }
344
345 let mut parts = trimmed.trim_start_matches('/').split_whitespace();
346 let command = parts.next().unwrap_or_default();
347 Some(match command {
348 "help" => Self::Help,
349 "status" => Self::Status,
350 "compact" => Self::Compact,
351 "compress" => Self::Compress,
352 "bughunter" => Self::Bughunter {
353 scope: remainder_after_command(trimmed, command),
354 },
355 "commit" => Self::Commit,
356 "pr" => Self::Pr {
357 context: remainder_after_command(trimmed, command),
358 },
359 "issue" => Self::Issue {
360 context: remainder_after_command(trimmed, command),
361 },
362 "ultraplan" => Self::Ultraplan {
363 task: remainder_after_command(trimmed, command),
364 },
365 "teleport" => Self::Teleport {
366 target: remainder_after_command(trimmed, command),
367 },
368 "debug-tool-call" => Self::DebugToolCall,
369 "model" => Self::Model {
370 model: parts.next().map(ToOwned::to_owned),
371 },
372 "permissions" => Self::Permissions {
373 mode: parts.next().map(ToOwned::to_owned),
374 },
375 "clear" => Self::Clear {
376 confirm: parts.next() == Some("--confirm"),
377 },
378 "cost" => Self::Cost,
379 "resume" => Self::Resume {
380 session_path: parts.next().map(ToOwned::to_owned),
381 },
382 "config" => Self::Config {
383 section: parts.next().map(ToOwned::to_owned),
384 },
385 "memory" => Self::Memory,
386 "init" => Self::Init,
387 "diff" => Self::Diff,
388 "version" => Self::Version,
389 "export" => Self::Export {
390 path: parts.next().map(ToOwned::to_owned),
391 },
392 "session" => Self::Session {
393 action: parts.next().map(ToOwned::to_owned),
394 target: parts.next().map(ToOwned::to_owned),
395 },
396 "auth" => Self::Auth {
397 provider: parts.next().map(ToOwned::to_owned),
398 },
399 "plan" => Self::Plan {
400 task: remainder_after_command(trimmed, command),
401 },
402 "tdd" => Self::Tdd {
403 interface: remainder_after_command(trimmed, command),
404 },
405 "verify" => Self::Verify,
406 "code-review" => Self::CodeReview {
407 files: remainder_after_command(trimmed, command),
408 },
409 "build-fix" => Self::BuildFix,
410 "aside" => Self::Aside {
411 question: remainder_after_command(trimmed, command),
412 },
413 "learn" => Self::Learn,
414 "refactor" => Self::Refactor {
415 scope: remainder_after_command(trimmed, command),
416 },
417 "checkpoint" => Self::Checkpoint {
418 label: remainder_after_command(trimmed, command),
419 },
420 "docs" => Self::Docs {
421 query: remainder_after_command(trimmed, command),
422 },
423 "loop" => Self::Loop {
424 mission: remainder_after_command(trimmed, command),
425 },
426 other => Self::Unknown(other.to_string()),
427 })
428 }
429}
430
431fn remainder_after_command(input: &str, command: &str) -> Option<String> {
432 input
433 .trim()
434 .strip_prefix(&format!("/{command}"))
435 .map(str::trim)
436 .filter(|value| !value.is_empty())
437 .map(ToOwned::to_owned)
438}
439
440#[must_use]
441pub fn slash_command_specs() -> &'static [SlashCommandSpec] {
442 SLASH_COMMAND_SPECS
443}
444
445#[must_use]
446pub fn resume_supported_slash_commands() -> Vec<&'static SlashCommandSpec> {
447 slash_command_specs()
448 .iter()
449 .filter(|spec| spec.resume_supported)
450 .collect()
451}
452
453#[must_use]
454pub fn render_slash_command_help() -> String {
455 use console::style;
456
457 let specs = slash_command_specs();
458 let mut output = String::new();
459
460 output.push_str(&format!("\n{}\n", style("SLASH COMMAND LIBRARY").bold().underlined()));
461 output.push_str(&format!(" {}\n", style("[resume] works with --resume SESSION.json").dim()));
462
463 let categories = vec![
464 ("SESSION & CONTEXT", vec!["status", "clear", "resume", "session", "export", "compact", "compress", "cost", "memory", "aside", "checkpoint", "learn"]),
465 ("DEVELOPMENT & REASONING", vec!["ultraplan", "plan", "loop", "tdd", "verify", "code-review", "build-fix", "refactor", "docs", "bughunter", "init", "teleport", "diff", "commit", "pr", "issue", "debug-tool-call"]),
466 ("CONFIGURATION & AUTH", vec!["model", "permissions", "auth", "config"]),
467 ("UTILITY", vec!["help", "version"]),
468 ];
469
470 for (cat_name, cat_cmds) in categories {
471 output.push_str(&format!("\n{}\n", style(cat_name).cyan().bold()));
472 for cmd_name in cat_cmds {
473 if let Some(spec) = specs.iter().find(|s| s.name == cmd_name) {
474 let name_display = match spec.argument_hint {
475 Some(hint) => format!("/{} {}", spec.name, hint),
476 None => format!("/{}", spec.name),
477 };
478 let resume = if spec.resume_supported {
479 style(" [resume]").dim().to_string()
480 } else {
481 "".to_string()
482 };
483 output.push_str(&format!(" {:<25} {}{}\n", style(name_display).green(), spec.summary, resume));
484 }
485 }
486 }
487
488 output
489}
490
491#[derive(Debug, Clone, PartialEq, Eq)]
492pub struct SlashCommandResult {
493 pub message: String,
494 pub session: Session,
495}
496
497#[must_use]
498pub fn handle_slash_command(
499 input: &str,
500 session: &Session,
501 compaction: CompactionConfig,
502) -> Option<SlashCommandResult> {
503 match SlashCommand::parse(input)? {
504 SlashCommand::Compact => {
505 let result = compact_session(session, compaction);
506 let message = if result.removed_message_count == 0 {
507 "Compaction skipped: session is below the compaction threshold.".to_string()
508 } else {
509 format!(
510 "Compacted {} messages into a resumable system summary.",
511 result.removed_message_count
512 )
513 };
514 Some(SlashCommandResult {
515 message,
516 session: result.compacted_session,
517 })
518 }
519 SlashCommand::Compress => {
520 let result = compact_session(session, CompactionConfig {
522 preserve_recent_messages: 2,
523 max_estimated_tokens: 1, });
525 let message = if result.removed_message_count == 0 {
526 "Compression skipped: session is empty or too short.".to_string()
527 } else {
528 format!(
529 "Aggressively compressed {} messages. Albert's memory is now lean and sharp.",
530 result.removed_message_count
531 )
532 };
533 Some(SlashCommandResult {
534 message,
535 session: result.compacted_session,
536 })
537 }
538 SlashCommand::Help => Some(SlashCommandResult {
539 message: render_slash_command_help(),
540 session: session.clone(),
541 }),
542 SlashCommand::Auth { .. }
543 | SlashCommand::Status
544 | SlashCommand::Bughunter { .. }
545 | SlashCommand::Commit
546 | SlashCommand::Pr { .. }
547 | SlashCommand::Issue { .. }
548 | SlashCommand::Ultraplan { .. }
549 | SlashCommand::Teleport { .. }
550 | SlashCommand::DebugToolCall
551 | SlashCommand::Model { .. }
552 | SlashCommand::Permissions { .. }
553 | SlashCommand::Clear { .. }
554 | SlashCommand::Cost
555 | SlashCommand::Resume { .. }
556 | SlashCommand::Config { .. }
557 | SlashCommand::Memory
558 | SlashCommand::Init
559 | SlashCommand::Diff
560 | SlashCommand::Version
561 | SlashCommand::Export { .. }
562 | SlashCommand::Session { .. }
563 | SlashCommand::Plan { .. }
564 | SlashCommand::Tdd { .. }
565 | SlashCommand::Verify
566 | SlashCommand::CodeReview { .. }
567 | SlashCommand::BuildFix
568 | SlashCommand::Aside { .. }
569 | SlashCommand::Learn
570 | SlashCommand::Refactor { .. }
571 | SlashCommand::Checkpoint { .. }
572 | SlashCommand::Docs { .. }
573 | SlashCommand::Loop { .. }
574 | SlashCommand::Unknown(_) => None,
575 }
576}
577
578
579#[cfg(test)]
580mod tests {
581 use super::{
582 handle_slash_command, render_slash_command_help, resume_supported_slash_commands,
583 slash_command_specs, SlashCommand,
584 };
585 use runtime::{CompactionConfig, ContentBlock, ConversationMessage, MessageRole, Session};
586
587 #[test]
588 fn parses_supported_slash_commands() {
589 assert_eq!(SlashCommand::parse("/help"), Some(SlashCommand::Help));
590 assert_eq!(SlashCommand::parse(" /status "), Some(SlashCommand::Status));
591 assert_eq!(
592 SlashCommand::parse("/bughunter runtime"),
593 Some(SlashCommand::Bughunter {
594 scope: Some("runtime".to_string())
595 })
596 );
597 assert_eq!(SlashCommand::parse("/commit"), Some(SlashCommand::Commit));
598 assert_eq!(
599 SlashCommand::parse("/pr ready for review"),
600 Some(SlashCommand::Pr {
601 context: Some("ready for review".to_string())
602 })
603 );
604 assert_eq!(
605 SlashCommand::parse("/issue flaky test"),
606 Some(SlashCommand::Issue {
607 context: Some("flaky test".to_string())
608 })
609 );
610 assert_eq!(
611 SlashCommand::parse("/ultraplan ship both features"),
612 Some(SlashCommand::Ultraplan {
613 task: Some("ship both features".to_string())
614 })
615 );
616 assert_eq!(
617 SlashCommand::parse("/teleport conversation.rs"),
618 Some(SlashCommand::Teleport {
619 target: Some("conversation.rs".to_string())
620 })
621 );
622 assert_eq!(
623 SlashCommand::parse("/debug-tool-call"),
624 Some(SlashCommand::DebugToolCall)
625 );
626 assert_eq!(
627 SlashCommand::parse("/model ternlang-opus"),
628 Some(SlashCommand::Model {
629 model: Some("ternlang-opus".to_string()),
630 })
631 );
632 assert_eq!(
633 SlashCommand::parse("/model"),
634 Some(SlashCommand::Model { model: None })
635 );
636 assert_eq!(
637 SlashCommand::parse("/permissions read-only"),
638 Some(SlashCommand::Permissions {
639 mode: Some("read-only".to_string()),
640 })
641 );
642 assert_eq!(
643 SlashCommand::parse("/clear"),
644 Some(SlashCommand::Clear { confirm: false })
645 );
646 assert_eq!(
647 SlashCommand::parse("/clear --confirm"),
648 Some(SlashCommand::Clear { confirm: true })
649 );
650 assert_eq!(SlashCommand::parse("/cost"), Some(SlashCommand::Cost));
651 assert_eq!(
652 SlashCommand::parse("/resume session.json"),
653 Some(SlashCommand::Resume {
654 session_path: Some("session.json".to_string()),
655 })
656 );
657 assert_eq!(
658 SlashCommand::parse("/config"),
659 Some(SlashCommand::Config { section: None })
660 );
661 assert_eq!(
662 SlashCommand::parse("/config env"),
663 Some(SlashCommand::Config {
664 section: Some("env".to_string())
665 })
666 );
667 assert_eq!(SlashCommand::parse("/memory"), Some(SlashCommand::Memory));
668 assert_eq!(SlashCommand::parse("/init"), Some(SlashCommand::Init));
669 assert_eq!(SlashCommand::parse("/diff"), Some(SlashCommand::Diff));
670 assert_eq!(SlashCommand::parse("/version"), Some(SlashCommand::Version));
671 assert_eq!(
672 SlashCommand::parse("/export notes.txt"),
673 Some(SlashCommand::Export {
674 path: Some("notes.txt".to_string())
675 })
676 );
677 assert_eq!(
678 SlashCommand::parse("/session switch abc123"),
679 Some(SlashCommand::Session {
680 action: Some("switch".to_string()),
681 target: Some("abc123".to_string())
682 })
683 );
684 }
685
686 #[test]
687 fn renders_help_from_shared_specs() {
688 let help = render_slash_command_help();
689 assert!(help.contains("works with --resume SESSION.json"));
690 assert!(help.contains("/help"));
691 assert!(help.contains("/status"));
692 assert!(help.contains("/compact"));
693 assert!(help.contains("/bughunter [scope]"));
694 assert!(help.contains("/commit"));
695 assert!(help.contains("/pr [context]"));
696 assert!(help.contains("/issue [context]"));
697 assert!(help.contains("/ultraplan [task]"));
698 assert!(help.contains("/teleport <symbol-or-path>"));
699 assert!(help.contains("/debug-tool-call"));
700 assert!(help.contains("/model [model]"));
701 assert!(help.contains("/permissions [read-only|workspace-write|danger-full-access]"));
702 assert!(help.contains("/clear [--confirm]"));
703 assert!(help.contains("/cost"));
704 assert!(help.contains("/resume <session-path>"));
705 assert!(help.contains("/config [env|hooks|model]"));
706 assert!(help.contains("/memory"));
707 assert!(help.contains("/init"));
708 assert!(help.contains("/diff"));
709 assert!(help.contains("/version"));
710 assert!(help.contains("/export [file]"));
711 assert!(help.contains("/session [list|switch <session-id>]"));
712 assert_eq!(slash_command_specs().len(), 22);
713 assert_eq!(resume_supported_slash_commands().len(), 11);
714 }
715
716 #[test]
717 fn compacts_sessions_via_slash_command() {
718 let session = Session {
719 version: 1,
720 messages: vec![
721 ConversationMessage::user_text("a ".repeat(200)),
722 ConversationMessage::assistant(vec![ContentBlock::Text {
723 text: "b ".repeat(200),
724 }]),
725 ConversationMessage::tool_result("1", "bash", "ok ".repeat(200), false),
726 ConversationMessage::assistant(vec![ContentBlock::Text {
727 text: "recent".to_string(),
728 }]),
729 ],
730 };
731
732 let result = handle_slash_command(
733 "/compact",
734 &session,
735 CompactionConfig {
736 preserve_recent_messages: 2,
737 max_estimated_tokens: 1,
738 },
739 )
740 .expect("slash command should be handled");
741
742 assert!(result.message.contains("Compacted 2 messages"));
743 assert_eq!(result.session.messages[0].role, MessageRole::System);
744 }
745
746 #[test]
747 fn help_command_is_non_mutating() {
748 let session = Session::new();
749 let result = handle_slash_command("/help", &session, CompactionConfig::default())
750 .expect("help command should be handled");
751 assert_eq!(result.session, session);
752 assert!(result.message.contains("Slash commands"));
753 }
754
755 #[test]
756 fn ignores_unknown_or_runtime_bound_slash_commands() {
757 let session = Session::new();
758 assert!(handle_slash_command("/unknown", &session, CompactionConfig::default()).is_none());
759 assert!(handle_slash_command("/status", &session, CompactionConfig::default()).is_none());
760 assert!(
761 handle_slash_command("/bughunter", &session, CompactionConfig::default()).is_none()
762 );
763 assert!(handle_slash_command("/commit", &session, CompactionConfig::default()).is_none());
764 assert!(handle_slash_command("/pr", &session, CompactionConfig::default()).is_none());
765 assert!(handle_slash_command("/issue", &session, CompactionConfig::default()).is_none());
766 assert!(
767 handle_slash_command("/ultraplan", &session, CompactionConfig::default()).is_none()
768 );
769 assert!(
770 handle_slash_command("/teleport foo", &session, CompactionConfig::default()).is_none()
771 );
772 assert!(
773 handle_slash_command("/debug-tool-call", &session, CompactionConfig::default())
774 .is_none()
775 );
776 assert!(
777 handle_slash_command("/model ternlang", &session, CompactionConfig::default()).is_none()
778 );
779 assert!(handle_slash_command(
780 "/permissions read-only",
781 &session,
782 CompactionConfig::default()
783 )
784 .is_none());
785 assert!(handle_slash_command("/clear", &session, CompactionConfig::default()).is_none());
786 assert!(
787 handle_slash_command("/clear --confirm", &session, CompactionConfig::default())
788 .is_none()
789 );
790 assert!(handle_slash_command("/cost", &session, CompactionConfig::default()).is_none());
791 assert!(handle_slash_command(
792 "/resume session.json",
793 &session,
794 CompactionConfig::default()
795 )
796 .is_none());
797 assert!(handle_slash_command("/config", &session, CompactionConfig::default()).is_none());
798 assert!(
799 handle_slash_command("/config env", &session, CompactionConfig::default()).is_none()
800 );
801 assert!(handle_slash_command("/diff", &session, CompactionConfig::default()).is_none());
802 assert!(handle_slash_command("/version", &session, CompactionConfig::default()).is_none());
803 assert!(
804 handle_slash_command("/export note.txt", &session, CompactionConfig::default())
805 .is_none()
806 );
807 assert!(
808 handle_slash_command("/session list", &session, CompactionConfig::default()).is_none()
809 );
810 }
811}