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
//! CLI definitions via clap. Commands: `serve`, `publish`, `list`, `delete`, `token`, `mcp`, `audit`.
use clap::{Parser, Subcommand};
/// Dual-layer markdown share service.
///
/// Serves two views from one document: a styled human view and a full raw
/// agent view. POST markdown in, get a URL out.
#[derive(Parser, Debug)]
#[command(name = "twofold", version, about)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
/// Start the HTTP server.
///
/// Reads configuration from environment variables:
/// TWOFOLD_TOKEN (required) Bearer token for publish auth
/// TWOFOLD_BIND (optional) Bind address (default: 127.0.0.1:3000)
/// TWOFOLD_DB_PATH (optional) SQLite path (default: ./twofold.db)
/// TWOFOLD_BASE_URL (optional) Base URL (default: http://localhost:3000)
/// TWOFOLD_MAX_SIZE (optional) Max body bytes (default: 1048576)
/// TWOFOLD_REAPER_INTERVAL (optional) Reaper interval seconds (default: 60)
/// TWOFOLD_DEFAULT_THEME (optional) Default theme (default: clean)
/// TWOFOLD_WEBHOOK_URL (optional) Webhook endpoint URL
/// TWOFOLD_WEBHOOK_SECRET (optional) HMAC signing secret for webhooks
Serve,
/// Publish a markdown document to a twofold server.
///
/// Reads the file at PATH (or stdin if PATH is `-`) and POSTs it to the
/// server. Prints the resulting URL to stdout on success. Exits 1 on failure.
Publish(PublishArgs),
/// List published documents on a twofold server.
List(ListArgs),
/// Delete a document by slug.
Delete(DeleteArgs),
/// Manage API tokens.
Token(TokenArgs),
/// View recent audit log entries.
Audit(AuditArgs),
/// Manage pre-provisioned OAuth clients (for closed registration mode).
Client(ClientArgs),
/// Start the MCP server (stdio JSON-RPC).
///
/// Reads:
/// TWOFOLD_MCP_SERVER Server URL (default: http://localhost:3000)
/// TWOFOLD_MCP_TOKEN Bearer token (falls back to TWOFOLD_TOKEN)
Mcp,
}
/// Arguments for the `audit` subcommand.
#[derive(clap::Args, Debug)]
pub struct AuditArgs {
/// Server base URL.
#[arg(long, default_value = "http://localhost:3000")]
pub server: String,
/// Bearer token for authentication.
/// Defaults to the TWOFOLD_TOKEN environment variable.
#[arg(long)]
pub token: Option<String>,
/// Maximum number of entries to show.
#[arg(long, default_value = "20")]
pub limit: u32,
}
/// Arguments for the `publish` subcommand.
#[derive(clap::Args, Debug)]
pub struct PublishArgs {
/// Path to the markdown file to publish, or `-` to read from stdin.
pub path: String,
/// Server base URL.
#[arg(long, default_value = "http://localhost:3000")]
pub server: String,
/// Bearer token for authentication.
/// Defaults to the TWOFOLD_TOKEN environment variable.
#[arg(long)]
pub token: Option<String>,
/// Document title (prepended as frontmatter).
#[arg(long)]
pub title: Option<String>,
/// Custom slug (prepended as frontmatter).
#[arg(long)]
pub slug: Option<String>,
/// Theme (clean, dark, paper, minimal).
#[arg(long)]
pub theme: Option<String>,
/// Expiry duration (e.g., 7d, 24h, 30m, 2w).
#[arg(long)]
pub expiry: Option<String>,
/// Password to protect the document.
#[arg(long)]
pub password: Option<String>,
}
/// Arguments for the `list` subcommand.
#[derive(clap::Args, Debug)]
pub struct ListArgs {
/// Server base URL.
#[arg(long, default_value = "http://localhost:3000")]
pub server: String,
/// Bearer token for authentication.
/// Defaults to the TWOFOLD_TOKEN environment variable.
#[arg(long)]
pub token: Option<String>,
/// Maximum number of documents to show.
#[arg(long, default_value = "20")]
pub limit: u32,
}
/// Arguments for the `delete` subcommand.
#[derive(clap::Args, Debug)]
pub struct DeleteArgs {
/// Slug of the document to delete.
pub slug: String,
/// Server base URL.
#[arg(long, default_value = "http://localhost:3000")]
pub server: String,
/// Bearer token for authentication.
/// Defaults to the TWOFOLD_TOKEN environment variable.
#[arg(long)]
pub token: Option<String>,
}
/// Arguments for the `client` subcommand.
#[derive(clap::Args, Debug)]
pub struct ClientArgs {
#[command(subcommand)]
pub action: ClientAction,
}
#[derive(Subcommand, Debug)]
pub enum ClientAction {
/// Create a pre-provisioned confidential OAuth client.
///
/// Generates a client_id and client_secret. The secret is shown ONCE at creation time.
/// Use with TWOFOLD_REGISTRATION_MODE=closed to restrict access to pre-configured clients.
Create {
/// Human-readable name for the client.
#[arg(long)]
name: String,
/// Redirect URI for the client (e.g. https://claude.ai/api/mcp/auth_callback).
#[arg(long)]
redirect_uri: String,
/// Path to the SQLite database.
/// Defaults to TWOFOLD_DB_PATH or ./twofold.db.
#[arg(long)]
db: Option<String>,
},
/// List all pre-provisioned clients.
List {
/// Path to the SQLite database.
#[arg(long)]
db: Option<String>,
},
/// Revoke a pre-provisioned client and all its tokens.
Revoke {
/// The client_id to revoke.
client_id: String,
/// Path to the SQLite database.
#[arg(long)]
db: Option<String>,
},
}
/// Arguments for the `token` subcommand.
#[derive(clap::Args, Debug)]
pub struct TokenArgs {
#[command(subcommand)]
pub action: TokenAction,
}
#[derive(Subcommand, Debug)]
pub enum TokenAction {
/// Create a new API token.
Create {
/// Human-readable name for the token.
#[arg(long)]
name: String,
/// Path to the SQLite database.
/// Defaults to TWOFOLD_DB_PATH or ./twofold.db.
#[arg(long)]
db: Option<String>,
},
/// List all tokens.
List {
/// Path to the SQLite database.
#[arg(long)]
db: Option<String>,
},
/// Revoke a token by name.
Revoke {
/// Name of the token to revoke.
#[arg(long)]
name: String,
/// Path to the SQLite database.
#[arg(long)]
db: Option<String>,
},
}