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