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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(name = "unifier")]
#[command(
about = "Filesystem postbox: programs communicate by writing values to files in a Unix tree",
version
)]
pub struct Cli {
/// Override state root (default: $UNIFIER_HOME or ~/.local/unifier)
#[arg(long, global = true, env = "UNIFIER_HOME")]
pub home: Option<std::path::PathBuf>,
/// Scope operations to chroots/<name>/ (only that subtree is visible)
#[arg(long, global = true, env = "UNIFIER_CHROOT")]
pub chroot: Option<String>,
/// Force direct filesystem access even when a hot daemon is running
#[arg(long, global = true)]
pub no_daemon: bool,
/// Prefix keys with this namespace (overrides a bound process namespace)
#[arg(long, global = true, env = "UNIFIER_NAMESPACE")]
pub namespace: Option<String>,
#[command(subcommand)]
pub cmd: Commands,
}
#[derive(Subcommand)]
pub enum Commands {
/// Write a persistent key value under keys/
Put {
/// Key path (e.g. myapp/config/theme)
key: String,
/// Value to store
value: String,
},
/// Read a key value
Get { key: String },
/// Delete a key
Del { key: String },
/// Drop a message into a recipient mailbox
Send {
/// Sender agent name (default: unifier)
#[arg(long)]
from: Option<String>,
recipient: String,
message: String,
},
/// Schedule a cron message (directory name: min_hour_dom_mon_dow, use * for any)
Cron { schedule: String, message: String },
/// Collect pending messages for a mailbox recipient
Poll {
recipient: String,
/// Remove messages after printing
#[arg(long)]
ack: bool,
},
/// Collect cron messages whose schedule matches the current time
PollCron {
/// Remove messages after printing
#[arg(long)]
ack: bool,
},
/// List message files in a subtree (e.g. cron/0_0_*_*_*)
List { path: String },
/// Remove a message by UUID or relative path under the state root
Ack { id_or_path: String },
/// Print the effective state root path
Root,
/// Post a named JSON event for agent pickup
Event {
/// JSON payload (must include a name field or be a JSON object)
payload: String,
/// Seconds until the event is deleted (default 86400 / 24h). 0 = never expire.
/// Overrides payload `ttl` / `expires_at` when set.
#[arg(long)]
ttl: Option<u64>,
},
/// Send a structured JSON message to an agent mailbox
Message {
#[arg(long)]
from: String,
recipient: String,
payload: String,
},
/// ACID tick staging (read previous tick, write current, commit at end)
#[command(subcommand)]
Tick(TickCommands),
/// Hot in-memory daemon (persists state in RAM, flushes to disk on demand)
#[command(subcommand)]
Daemon(DaemonCommands),
/// Manage isolated chroot subtrees
#[command(subcommand)]
Chroot(ChrootCommands),
/// Bind a key prefix to the calling process (and descendants)
#[command(subcommand)]
Namespace(NamespaceCommands),
/// Managed SQLite databases under sqlite/
#[command(subcommand)]
Sql(SqlCommands),
/// RDF-style triples in the reserved `triples` database
#[command(subcommand)]
Triple(TripleCommands),
/// Span-tree logging for Jan cron scripts and agent pipelines
#[command(subcommand)]
Log(LogCommands),
/// Pipe a temp file into the daemon HTTP server and print its URL
Serve {
/// Published name (default: random UUID). Path segment only: [A-Za-z0-9._-]
name: Option<String>,
/// Read body from this file instead of stdin
#[arg(long)]
file: Option<std::path::PathBuf>,
/// Content-Type header (default: text/html; charset=utf-8)
#[arg(long = "content-type", short = 't')]
content_type: Option<String>,
/// Auto-delete after this many seconds
#[arg(long)]
ttl: Option<u64>,
/// Wrap body in a Unifier HTML chrome (for Jan report export)
#[arg(long)]
wrap: bool,
/// Page title used with --wrap
#[arg(long, default_value = "Unifier report")]
title: String,
},
/// Manage temp files on the daemon web server
#[command(subcommand)]
Web(WebCommands),
}
#[derive(Subcommand)]
pub enum TickCommands {
/// Begin a new tick (reads frozen snapshot of previous committed state)
Start {
#[arg(default_value = "default")]
label: String,
},
/// Commit current tick to disk with versioning
End,
/// Show committed tick, active tick, queue depth, and locks
Status,
/// Set the active tick's phase name (broadcast on events.sock)
Phase { phase: String },
/// Lock a key for the duration of the active tick
Lock { key: String },
/// Release a tick lock
Unlock { key: String },
}
#[derive(Subcommand)]
pub enum DaemonCommands {
/// Start the hot daemon in the background
Start,
/// Run the daemon in the foreground (internal / debugging)
Run,
/// Stop the daemon (flushes dirty state first)
Stop,
/// Show whether the daemon is running
Status,
/// Flush in-memory dirty state to disk
Flush,
/// Print mailbox/event wakeup notices from the event socket (for Jan cron)
Watch,
/// Kill orphan daemons whose `--home` directory no longer exists
Gc {
/// Print victims without signaling them
#[arg(long)]
dry_run: bool,
},
}
#[derive(Subcommand)]
pub enum ChrootCommands {
/// Create chroots/<name>/ with keys/, mailbox/, and cron/
Init { name: String },
/// List chroot names under the global store
List,
}
#[derive(Subcommand)]
pub enum NamespaceCommands {
/// Bind a key prefix until this process exits or a new namespace is set
Set { name: String },
/// Print the effective namespace for this process
#[command(visible_alias = "show")]
Get,
/// Remove this process's namespace binding
Clear,
}
#[derive(Subcommand)]
pub enum SqlCommands {
/// List managed database names under sqlite/
List,
/// Create an empty managed database (triples also gets its schema)
Create {
/// Database name (stored as sqlite/<name>.sqlite)
name: String,
},
/// List tables (and columns) in a database
Tables {
database: String,
/// Include column names and declared types
#[arg(long)]
schema: bool,
},
/// Run SQL against a managed database (creates the file if missing)
Exec {
database: String,
/// SQL statement(s)
sql: String,
},
}
#[derive(Subcommand)]
pub enum TripleCommands {
/// Insert a subject/predicate/object triple (duplicates ignored)
Add {
subject: String,
predicate: String,
object: String,
},
/// Query triples with optional filters
Query {
#[arg(long, short = 's')]
subject: Option<String>,
#[arg(long, short = 'p')]
predicate: Option<String>,
#[arg(long, short = 'o')]
object: Option<String>,
},
}
#[derive(Subcommand)]
pub enum LogCommands {
/// Start a new span and print its UUID
Start {
/// Human-readable span name
name: String,
/// Parent span UUID (nest this span under an existing one)
#[arg(long)]
parent: Option<String>,
/// Initial key=value fields (repeatable)
#[arg(long = "field", short = 'f')]
fields: Vec<String>,
},
/// End a span (record ended_at)
End {
/// Span UUID
id: String,
},
/// Append a log event to a span
Event {
/// Span UUID
span: String,
/// Log message
message: String,
/// key=value fields to attach (repeatable)
#[arg(long = "field", short = 'f')]
fields: Vec<String>,
},
/// Set key=value fields on an existing span
Field {
/// Span UUID
span: String,
/// key=value pairs (repeatable)
#[arg(required = true)]
fields: Vec<String>,
},
/// Print the span tree (all spans, or rooted at a given span)
Tree {
/// Root span UUID (optional; show all roots if omitted)
#[arg(long)]
span: Option<String>,
},
/// List all span UUIDs with name and status
List,
}
#[derive(Subcommand)]
pub enum WebCommands {
/// List published temp files (name, type, bytes, url)
List,
/// Print the URL for a published name
Url { name: String },
/// Print the HTTP URL for a persistent key (`/keys/<key>`)
KeyUrl { key: String },
/// Remove a published temp file
Rm { name: String },
/// Print the daemon HTTP base URL
Status,
}