1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
// ABOUTME: Main entry point for micropub CLI
// ABOUTME: Parses commands and dispatches to appropriate handlers
use anyhow::Context;
use clap::{Parser, Subcommand};
use micropub::Result;
#[derive(Parser)]
#[command(name = "micropub")]
#[command(about = "Ultra-compliant Micropub CLI", long_about = None)]
struct Cli {
/// Enable verbose output
#[arg(short, long, global = true)]
verbose: bool,
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand)]
enum Commands {
/// Authenticate with a Micropub site
Auth {
/// Domain to authenticate with
domain: String,
/// OAuth scope (default: "create update delete media")
#[arg(long)]
scope: Option<String>,
},
/// Draft management commands
#[command(subcommand)]
Draft(DraftCommands),
/// Publish a draft
Publish {
/// Path to draft file
draft: String,
},
/// Publish a backdated post
Backdate {
/// Path to draft file
draft: String,
/// Date to publish (ISO 8601 format)
#[arg(long)]
date: String,
},
/// Update an existing post
Update {
/// URL of post to update
url: String,
},
/// Delete a post
Delete {
/// URL of post to delete
url: String,
},
/// Undelete a post
Undelete {
/// URL of post to undelete
url: String,
},
/// Debug connection to a profile
Debug {
/// Profile name to debug
profile: String,
},
/// Show current authenticated user
Whoami,
/// List published posts
Posts {
/// Number of posts to show (default: 10)
#[arg(short, long, default_value = "10")]
limit: usize,
/// Offset for pagination (default: 0)
#[arg(short, long, default_value = "0")]
offset: usize,
},
/// List uploaded media files
Media {
/// Number of media items to show (default: 20)
#[arg(short, long, default_value = "20")]
limit: usize,
/// Offset for pagination (default: 0)
#[arg(short, long, default_value = "0")]
offset: usize,
},
/// Launch interactive TUI (Terminal User Interface)
Tui,
// MCP server disabled until SDK macros are fixed
// /// Start MCP server (Model Context Protocol)
// Mcp,
}
#[derive(Subcommand)]
enum DraftCommands {
/// Create a new draft
New,
/// Edit an existing draft
Edit {
/// Draft ID to edit
draft_id: String,
},
/// List all drafts
List {
/// Filter by category
#[arg(long)]
category: Option<String>,
/// Number of drafts to show per page (default: 10)
#[arg(short, long, default_value = "10")]
limit: usize,
/// Offset for pagination (default: 0)
#[arg(short, long, default_value = "0")]
offset: usize,
},
/// Show a draft's content
Show {
/// Draft ID to show
draft_id: String,
},
/// Search drafts by content or metadata
Search {
/// Search query
query: String,
},
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
// If no command provided, show help
if cli.command.is_none() {
let config = micropub::config::Config::load()?;
println!("Welcome to Micropub CLI!\n");
if !config.default_profile.is_empty() {
println!("You are authenticated as: {}", config.default_profile);
println!("\nQuick commands:");
println!(" micropub tui Launch interactive TUI");
println!(" micropub draft new Create a new draft");
println!(" micropub posts List published posts");
println!(" micropub whoami Show current profile");
} else {
println!("To get started, authenticate with your site:");
println!(" micropub auth <your-domain.com>");
}
println!("\nFor more help, run:");
println!(" micropub --help");
return Ok(());
}
match cli.command.unwrap() {
Commands::Auth { domain, scope } => {
micropub::auth::cmd_auth(&domain, scope.as_deref()).await?;
Ok(())
}
Commands::Draft(cmd) => match cmd {
DraftCommands::New => {
micropub::draft::cmd_new()?;
Ok(())
}
DraftCommands::Edit { draft_id } => {
micropub::draft::cmd_edit(&draft_id)?;
Ok(())
}
DraftCommands::List {
category,
limit,
offset,
} => {
micropub::draft::cmd_list(category.as_deref(), limit, offset)?;
Ok(())
}
DraftCommands::Show { draft_id } => {
micropub::draft::cmd_show(&draft_id)?;
Ok(())
}
DraftCommands::Search { query } => {
micropub::draft::cmd_search(&query)?;
Ok(())
}
},
Commands::Publish { draft } => {
micropub::publish::cmd_publish(&draft, None).await?;
Ok(())
}
Commands::Backdate { draft, date } => {
use chrono::DateTime;
let parsed_date = DateTime::parse_from_rfc3339(&date)
.context("Invalid date format. Use ISO 8601 (e.g., 2024-01-15T10:30:00Z)")?
.with_timezone(&chrono::Utc);
micropub::publish::cmd_publish(&draft, Some(parsed_date)).await?;
Ok(())
}
Commands::Update { url } => {
micropub::operations::cmd_update(&url).await?;
Ok(())
}
Commands::Delete { url } => {
micropub::operations::cmd_delete(&url).await?;
Ok(())
}
Commands::Undelete { url } => {
micropub::operations::cmd_undelete(&url).await?;
Ok(())
}
Commands::Debug { profile } => {
println!("Debug command: {}", profile);
Ok(())
}
Commands::Whoami => {
micropub::operations::cmd_whoami().await?;
Ok(())
}
Commands::Posts { limit, offset } => {
micropub::operations::cmd_list_posts(limit, offset).await?;
Ok(())
}
Commands::Media { limit, offset } => {
micropub::operations::cmd_list_media(limit, offset).await?;
Ok(())
}
Commands::Tui => {
micropub::tui::run().await?;
Ok(())
} // Commands::Mcp => {
// micropub::mcp::run_server().await?;
// Ok(())
// }
}
}