Skip to main content

semora_cli/
args.rs

1//! Command-line arguments.
2
3use std::path::PathBuf;
4
5use clap::{Args, Parser, Subcommand};
6
7#[derive(Debug, Parser)]
8#[command(
9    name = "semora",
10    version,
11    about = "Publish Markdown and HTML to Semora and hear back from the page",
12    after_help = "Configuration: SEMORA_URL, SEMORA_API_KEY, SEMORA_DEFAULT_TAGS, SEMORA_WAIT_TIMEOUT \
13                  (environment or a .env file in this folder or a parent), or `semora login`. \
14                  A `.semora` file marks a project folder (see `semora project init`).\n\
15                  Exit codes: 0 ok, 1 error, 2 timeout, 3 auth error, 4 not found."
16)]
17pub struct Cli {
18    /// Server URL (default: SEMORA_URL, config.toml, or the stored login)
19    #[arg(long, global = true, value_name = "URL")]
20    pub server: Option<String>,
21    /// API key (default: SEMORA_API_KEY, then the stored login)
22    #[arg(long, global = true, value_name = "KEY", hide_env_values = true)]
23    pub api_key: Option<String>,
24    /// Print JSON instead of text
25    #[arg(long, global = true)]
26    pub json: bool,
27    #[command(subcommand)]
28    pub cmd: Cmd,
29}
30
31#[derive(Debug, Subcommand)]
32pub enum Cmd {
33    /// Sign in through the browser and store an API key for this machine
34    Login {
35        /// Print the URL instead of opening a browser; paste the code shown after approval
36        #[arg(long)]
37        no_browser: bool,
38        /// Name for the new key (default: cli@HOSTNAME)
39        #[arg(long)]
40        name: Option<String>,
41    },
42    /// Revoke and forget the stored key
43    Logout,
44    /// Show the server, user and key in use
45    Whoami,
46    /// Publish a file or folder; replaces the remembered document if there is one
47    Push(PushArgs),
48    /// Re-push whenever files change (debounced 500 ms)
49    Watch(WatchArgs),
50    /// Block until feedback arrives from the page
51    Wait(WaitArgs),
52    /// Show a message in the open page
53    Say {
54        /// Document id or URL
55        doc: String,
56        /// Text to show
57        text: String,
58        /// Event id this message answers
59        #[arg(long)]
60        reply_to: Option<i64>,
61    },
62    /// List or search the documents you can open
63    Ls {
64        /// Full-text search
65        #[arg(short = 'q', long = "query")]
66        query: Option<String>,
67        /// Only documents with this tag
68        #[arg(long)]
69        tag: Option<String>,
70        /// md, html or bundle
71        #[arg(long)]
72        kind: Option<String>,
73        /// Only documents in this project (its id)
74        #[arg(long)]
75        project: Option<String>,
76        #[arg(long, default_value_t = 50)]
77        limit: u32,
78    },
79    /// Metadata, files and revisions of a document
80    Show {
81        /// Document id or URL
82        doc: String,
83    },
84    /// Download raw content
85    Get {
86        /// Document id or URL
87        doc: String,
88        /// File inside a bundle (default: the entry page)
89        path: Option<String>,
90        /// Revision (default: latest)
91        #[arg(long)]
92        rev: Option<i64>,
93        /// Write to a file instead of stdout
94        #[arg(short = 'o', long = "output")]
95        output: Option<PathBuf>,
96    },
97    /// Edit tags: `+add -remove` (a bare name adds)
98    Tag {
99        /// Document id or URL
100        doc: String,
101        #[arg(required = true, num_args = 1.., allow_hyphen_values = true)]
102        changes: Vec<String>,
103    },
104    /// Delete a document
105    Rm {
106        /// Document id or URL
107        doc: String,
108    },
109    /// Open a document in the browser
110    Open {
111        /// Document id or URL
112        doc: String,
113    },
114    /// Manage your API keys
115    #[command(subcommand)]
116    Keys(KeysCmd),
117    /// Make a folder a project, list your projects, and arrange a project's tree
118    #[command(subcommand)]
119    Project(ProjectCmd),
120    /// Print a short usage guide to paste into an agent's instructions
121    Skill,
122}
123
124#[derive(Debug, Args)]
125pub struct PushArgs {
126    /// File (.md or .html) or folder (becomes a bundle)
127    pub path: PathBuf,
128    /// Replace this document instead of the remembered one
129    #[arg(long)]
130    pub id: Option<String>,
131    /// Always create a new document
132    #[arg(long, conflicts_with = "id")]
133    pub new: bool,
134    #[arg(long)]
135    pub title: Option<String>,
136    /// Comma-separated tags (repeatable)
137    #[arg(long = "tag", value_name = "TAGS")]
138    pub tags: Vec<String>,
139    /// Revision message
140    #[arg(short = 'm', long = "message")]
141    pub message: Option<String>,
142    /// Entry page of a bundle
143    #[arg(long)]
144    pub entry: Option<String>,
145    /// key=value stored on the document (repeatable)
146    #[arg(long = "meta", value_name = "KEY=VALUE")]
147    pub meta: Vec<String>,
148    /// After pushing, wait for feedback (same as `semora wait`)
149    #[arg(long)]
150    pub wait: bool,
151    #[command(flatten)]
152    pub wait_opts: WaitOpts,
153}
154
155#[derive(Debug, Args, Clone, Default)]
156pub struct WaitOpts {
157    /// Seconds to wait (default: SEMORA_WAIT_TIMEOUT or 900)
158    #[arg(long)]
159    pub timeout: Option<u64>,
160    /// Only these kinds: comment,drawing,page_event,message
161    #[arg(long)]
162    pub kinds: Option<String>,
163    /// Keep streaming events as JSON lines instead of exiting
164    #[arg(long)]
165    pub follow: bool,
166    /// Seconds to keep collecting after the first event
167    #[arg(long, default_value_t = 3.0)]
168    pub collect: f64,
169    /// Folder for downloaded screenshots (default: a temp folder)
170    #[arg(long)]
171    pub save_dir: Option<PathBuf>,
172}
173
174#[derive(Debug, Args)]
175pub struct WaitArgs {
176    /// Document id, page URL or websocket URL
177    pub doc: String,
178    #[command(flatten)]
179    pub opts: WaitOpts,
180}
181
182#[derive(Debug, Args)]
183pub struct WatchArgs {
184    /// File or folder to watch
185    pub path: PathBuf,
186    /// Document to replace (default: the remembered one)
187    #[arg(long)]
188    pub id: Option<String>,
189    #[arg(long)]
190    pub title: Option<String>,
191    #[arg(long = "tag", value_name = "TAGS")]
192    pub tags: Vec<String>,
193    #[arg(long)]
194    pub entry: Option<String>,
195}
196
197#[derive(Debug, Subcommand)]
198pub enum KeysCmd {
199    /// List your keys
200    Ls,
201    /// Create a key and print it once
202    Create {
203        name: String,
204        /// Comma-separated scopes (default: all)
205        #[arg(long)]
206        scopes: Option<String>,
207        /// Expire after this many days
208        #[arg(long)]
209        expires_in_days: Option<u32>,
210    },
211    /// Revoke a key by id
212    Revoke { id: i64 },
213}
214
215#[derive(Debug, Subcommand)]
216pub enum ProjectCmd {
217    /// Make a folder a project: create it on the server and write .semora
218    Init {
219        /// The project folder (default: the current one)
220        dir: Option<PathBuf>,
221        /// The project id (default: made from the folder's name)
222        #[arg(long)]
223        name: Option<String>,
224        /// Publish into a project someone else owns and you collaborate on
225        #[arg(long)]
226        join: bool,
227    },
228    /// List your projects
229    Ls,
230    /// Arrange how documents show in the project's tree (display only; links never change)
231    Map(MapArgs),
232}
233
234#[derive(Debug, Args)]
235pub struct MapArgs {
236    /// Project id (default: the .semora file in this folder or a parent)
237    #[arg(long, global = true)]
238    pub project: Option<String>,
239    #[command(subcommand)]
240    pub cmd: MapCmd,
241}
242
243#[derive(Debug, Subcommand)]
244pub enum MapCmd {
245    /// List the rules in order
246    Ls,
247    /// Add a rule: documents matching PATTERN show inside FOLDER ("/" is the project root)
248    Add {
249        /// For example 'docs/**' (quote it, or the shell expands it)
250        pattern: String,
251        /// For example misc/ or / (use . if your shell rewrites a leading slash)
252        folder: String,
253        /// Put the rule at this number (1 is first); default: last
254        #[arg(long, value_name = "N")]
255        at: Option<usize>,
256    },
257    /// Remove rule number N (see `map ls`)
258    Rm {
259        /// The rule's number, from `map ls`
260        n: usize,
261    },
262}