sesh_cli/
lib.rs

1use std::{fmt::Display, str::FromStr};
2
3use clap::{Args, Subcommand};
4
5#[derive(Debug, clap::Parser)]
6#[clap(
7    name = "sesh",
8    version = "0.1.8",
9    author = "Will Hopkins <willothyh@gmail.com>"
10)]
11#[group(required = false, multiple = true)]
12/// A terminal session manager for unix systems. Run persistent, named tasks that you can
13/// detach from and attach to at any time - both on your local machine, and over SSH.
14pub struct Cli {
15    #[command(subcommand)]
16    pub command: Option<Command>,
17    #[command(flatten)]
18    pub args: CliArgs,
19}
20
21#[derive(Debug, Clone, Args)]
22pub struct CliArgs {
23    pub program: Option<String>,
24    pub args: Vec<String>,
25    #[arg(short, long)]
26    pub name: Option<String>,
27    #[arg(short, long)]
28    pub detached: bool,
29}
30
31#[derive(Debug, Subcommand)]
32pub enum Command {
33    #[command(alias = "r", verbatim_doc_comment)]
34    /// Resume the last used session [alias: r]
35    ///
36    /// Specify --create / -c to create a new session if one does not exist
37    Resume {
38        /// Create a new session if one does not exist
39        #[arg(short, long)]
40        create: bool,
41    },
42    /// Start a new session, optionally specifying a name [alias: s]
43    ///
44    /// If no program is specified, the default shell will be used.
45    /// If no name is specified, the name will be [program name]-[n-1] where n is the number of sessions
46    /// with that program name.
47    /// If --detached / -d is present, the session will not be attached to the client on creation
48    /// and will run in the background.
49    #[command(alias = "s", verbatim_doc_comment)]
50    Start {
51        #[arg(short, long)]
52        name: Option<String>,
53        program: Option<String>,
54        args: Vec<String>,
55        #[arg(short, long)]
56        detached: bool,
57    },
58    #[command(alias = "a", verbatim_doc_comment)]
59    /// Attach to a session [alias: a]
60    ///
61    /// Select a session by index or name.
62    /// If --create / -c is present, a new session will be created if one does not exist.
63    /// If the session was selected by name and the session was not present, the new session
64    /// created by --create will have the specified name.
65    Attach {
66        /// Id or name of session
67        session: SessionSelector,
68        /// Create a new session if one does not exist
69        #[arg(short, long)]
70        create: bool,
71    },
72    /// Fuzzy select a session to attach to [alias: f]
73    ///
74    /// Opens a fuzzy selection window provided by the dialoguer crate.
75    /// Type to fuzzy find files, or use the Up/Down arrows to navigate.
76    /// Press Enter to confirm your selection, or Escape to cancel.
77    #[command(alias = "f", verbatim_doc_comment)]
78    Select,
79    /// Detach from a session [alias: d]
80    ///
81    /// If no session is specified, detaches from the current session (if it exists).
82    /// Otherwise, detaches the specified session from its owning client.
83    #[command(alias = "d", verbatim_doc_comment)]
84    Detach {
85        /// Id or name of session
86        session: Option<SessionSelector>,
87    },
88    #[command(alias = "k", verbatim_doc_comment)]
89    /// Kill a session [alias: k]
90    ///
91    /// Kills a session and the process it owns.
92    /// Select a session by name or index.
93    Kill {
94        /// Id or name of session
95        session: SessionSelector,
96    },
97    /// List sessions [alias: ls]
98    ///
99    /// Prints a compact list of session names and indexes.
100    /// With the --info / -i option, prints a nicely formatted table with info about each session.
101    #[command(alias = "ls", verbatim_doc_comment)]
102    #[group(required = false, multiple = true)]
103    List {
104        /// Print detailed info about sessions
105        #[arg(short, long)]
106        info: bool,
107        /// Print session info as JSON, to be processed by another tool
108        #[arg(short, long)]
109        json: bool,
110    },
111    /// Shutdown the server (kill all sessions)
112    Shutdown,
113}
114
115#[derive(Debug, Clone)]
116pub enum SessionSelector {
117    Id(usize),
118    Name(String),
119}
120
121impl SessionSelector {
122    pub fn name(self) -> Option<String> {
123        match self {
124            SessionSelector::Id(_) => None,
125            SessionSelector::Name(name) => Some(name),
126        }
127    }
128}
129
130impl Display for SessionSelector {
131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132        match self {
133            SessionSelector::Id(id) => write!(f, "{}", id),
134            SessionSelector::Name(name) => write!(f, "{}", name),
135        }
136    }
137}
138
139impl FromStr for SessionSelector {
140    type Err = anyhow::Error;
141
142    fn from_str(s: &str) -> Result<Self, Self::Err> {
143        if let Ok(id) = s.parse::<usize>() {
144            Ok(SessionSelector::Id(id))
145        } else {
146            Ok(SessionSelector::Name(s.to_owned()))
147        }
148    }
149}