Skip to main content

wire/
slash.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4// Copyright (c) 2026 2youg1 and the sprawling contributors
5
6//! The slash verbs a person types, one table for the CLI and the WebUI
7//! (`crates/wire/spec/Slash.lean` ยง8-96).
8//!
9//! Every fact about a verb is one arm of a `const fn` on [`Slash`], so
10//! adding a verb makes the compiler ask for its spelling, its argument,
11//! where it is offered and the sentence that explains it. The client
12//! reads the same table through the `SLASH` constant `cargo xtask
13//! wire-ts` generates into `client/src/wire.ts`.
14
15/// One verb a person can type after a `/`.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
17pub enum Slash {
18    Help,
19    Room,
20    New,
21    Stop,
22    Halt,
23    Release,
24    Model,
25    Effort,
26    Approve,
27    Deny,
28    Web,
29    Quit,
30    Serving,
31    Remote,
32    Acp,
33    Wire,
34}
35
36/// Where a verb is offered.
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum Offered {
39    Cli,
40    WebUi,
41    Both,
42}
43
44impl Offered {
45    /// The spelling the generated client table carries.
46    #[must_use]
47    pub const fn as_str(self) -> &'static str {
48        match self {
49            Self::Cli => "cli",
50            Self::WebUi => "webui",
51            Self::Both => "both",
52        }
53    }
54
55    /// Whether the terminal offers a verb offered here.
56    #[must_use]
57    pub const fn in_the_cli(self) -> bool {
58        match self {
59            Self::Cli | Self::Both => true,
60            Self::WebUi => false,
61        }
62    }
63}
64
65impl Slash {
66    /// Every verb, in the order `/help` lists them.
67    pub const ALL: [Slash; 16] = [
68        Self::Help,
69        Self::Room,
70        Self::New,
71        Self::Stop,
72        Self::Halt,
73        Self::Release,
74        Self::Model,
75        Self::Effort,
76        Self::Approve,
77        Self::Deny,
78        Self::Web,
79        Self::Quit,
80        Self::Serving,
81        Self::Remote,
82        Self::Acp,
83        Self::Wire,
84    ];
85
86    /// The verb as a person types it, slash included.
87    #[must_use]
88    pub const fn spelling(self) -> &'static str {
89        match self {
90            Self::Help => "/help",
91            Self::Room => "/room",
92            Self::New => "/new",
93            Self::Stop => "/stop",
94            Self::Halt => "/halt",
95            Self::Release => "/release",
96            Self::Model => "/model",
97            Self::Effort => "/effort",
98            Self::Approve => "/approve",
99            Self::Deny => "/deny",
100            Self::Web => "/web",
101            Self::Quit => "/quit",
102            Self::Serving => "/serving",
103            Self::Remote => "/remote",
104            Self::Acp => "/acp",
105            Self::Wire => "/wire",
106        }
107    }
108
109    /// What follows the spelling; empty when the verb takes nothing.
110    #[must_use]
111    pub const fn takes(self) -> &'static str {
112        match self {
113            Self::Help | Self::New | Self::Stop | Self::Web | Self::Quit | Self::Serving => "",
114            Self::Room => "<addr>",
115            Self::Halt | Self::Release => "[--all]",
116            Self::Model | Self::Approve | Self::Deny => "[<id>]",
117            Self::Effort => "[<level>]",
118            Self::Remote => "<verb>",
119            Self::Acp => "[<text>]",
120            Self::Wire => "<verb> [<json>]",
121        }
122    }
123
124    /// Where a person finds the verb.
125    #[must_use]
126    pub const fn offered(self) -> Offered {
127        match self {
128            Self::Help
129            | Self::Room
130            | Self::New
131            | Self::Stop
132            | Self::Halt
133            | Self::Release
134            | Self::Model
135            | Self::Effort
136            | Self::Quit
137            | Self::Acp => Offered::Both,
138            Self::Approve | Self::Deny | Self::Web | Self::Serving | Self::Remote | Self::Wire => {
139                Offered::Cli
140            }
141        }
142    }
143
144    /// The one sentence `/help` prints beside the verb.
145    #[must_use]
146    pub const fn says(self) -> &'static str {
147        match self {
148            Self::Help => "list these verbs",
149            Self::Room => "choose the room plain lines go to",
150            Self::New => "start a new session in this room",
151            Self::Stop => "stop the run working in this room",
152            Self::Halt => "hold this room's building, or with --all the whole city",
153            Self::Release => "let this room's building work again, or with --all the whole city",
154            Self::Model => "show the model, or choose one the city's endpoints offer",
155            Self::Effort => "show the thinking level, or choose one the model offers",
156            Self::Approve => "approve the waiting request, or the one with this id",
157            Self::Deny => "deny the waiting request, or the one with this id",
158            Self::Web => "open the WebUI and quiet this terminal; Esc comes back",
159            Self::Quit => "close the city",
160            Self::Serving => "where this city listens, and what is running in it",
161            Self::Remote => {
162                "open|pair|close|devices|revoke: the door a device reaches the city through"
163            }
164            Self::Acp => "find an ACP agent, or read a pasted launch line",
165            Self::Wire => "send any wire command or question, its body as JSON",
166        }
167    }
168
169    /// The verb a word names, with or without its leading `/`.
170    #[must_use]
171    pub fn parse(word: &str) -> Option<Slash> {
172        let bare = word.strip_prefix('/').unwrap_or(word);
173        Self::ALL
174            .into_iter()
175            .find(|verb| verb.spelling().strip_prefix('/') == Some(bare))
176    }
177}