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}