oxibrain_cli/cli.rs
1use clap::{Parser, Subcommand};
2use std::path::PathBuf;
3
4#[derive(Parser, Debug)]
5#[command(
6 name = "oxibrain",
7 version,
8 about = "A second brain for humans and agents"
9)]
10pub struct Cli {
11 #[arg(long, env = "OXIBRAIN_DIR", global = true)]
12 pub dir: Option<PathBuf>,
13 #[command(subcommand)]
14 pub command: Command,
15}
16
17#[derive(Subcommand, Debug)]
18pub enum Command {
19 /// Initialize a new brain store.
20 Init {
21 #[arg(long, default_value = "personal")]
22 space: String,
23 },
24 /// Ingest a file or stdin as an episode.
25 Ingest {
26 /// File path, or `-` for stdin.
27 path: PathBuf,
28 #[arg(long, default_value = "personal")]
29 space: String,
30 },
31 /// Sync a directory of markdown notes into a space. Idempotent: unchanged
32 /// files are skipped; new and modified files are ingested with
33 /// occurred_at = file mtime.
34 Sync {
35 /// Directory to scan recursively for .md files.
36 path: PathBuf,
37 #[arg(long, default_value = "personal")]
38 space: String,
39 },
40 /// Show store statistics.
41 Stats,
42 /// List all spaces with counts. Read-only; safe with a running daemon.
43 Spaces,
44 /// Health check.
45 Doctor,
46 /// Back up the store.
47 Backup {
48 #[arg(long)]
49 no_projection: bool,
50 #[arg(long)]
51 no_cache: bool,
52 /// Output directory (default: sibling of store dir).
53 #[arg(long)]
54 out: Option<PathBuf>,
55 },
56 /// Restore from a backup.
57 Restore { backup: PathBuf },
58 /// Ask a question (hybrid query).
59 Ask {
60 question: String,
61 #[arg(long, default_value = "personal")]
62 space: String,
63 },
64 /// Entity management (DESIGN §12.4: `entity show|merge|split|alias`).
65 Entity {
66 #[command(subcommand)]
67 command: EntityCmd,
68 },
69 Timeline {
70 entity_id: String,
71 #[arg(long, default_value = "personal")]
72 space: String,
73 },
74 /// Provenance for a statement.
75 Why {
76 statement_id: String,
77 #[arg(long, default_value = "personal")]
78 space: String,
79 /// Print what `rank` discarded for a query instead of provenance.
80 /// `statement_id` is then the query text (DESIGN §11.8).
81 #[arg(long)]
82 dropped: bool,
83 /// Confidence floor for --dropped (default 0). Raises it to see
84 /// BelowConfidenceFloor drops.
85 #[arg(long, default_value_t = 0.0)]
86 min_confidence: f32,
87 },
88 /// List contradicted statements.
89 Contradictions {
90 #[arg(long, default_value = "personal")]
91 space: String,
92 },
93 /// Render a page (brief) with followable links. `--kind entity` is the
94 /// default; `--kind space` shows counts + top entities; `--kind topic`
95 /// keyword-searches entity surfaces (`--topic` is the keyword).
96 Page {
97 /// Entity id (when --kind entity, default), or ignored for space/topic.
98 entity: Option<String>,
99 #[arg(long, default_value = "personal")]
100 space: String,
101 /// Target kind: entity (default), space, or topic.
102 #[arg(long, default_value = "entity")]
103 kind: String,
104 /// Keyword for --kind topic.
105 #[arg(long)]
106 topic: Option<String>,
107 },
108 /// Reproject the store.
109 Reproject,
110 /// Redact (the only true delete).
111 Redact {
112 target: String,
113 #[arg(long, default_value = "personal")]
114 space: String,
115 #[arg(long)]
116 dry_run: bool,
117 #[arg(long)]
118 reason: String,
119 },
120 /// Export to JSONL.
121 Export {
122 #[arg(long)]
123 out: Option<PathBuf>,
124 },
125 /// Import from JSONL.
126 Import { file: PathBuf },
127 /// Import from an oxios-memory SQLite database (DESIGN §16.3).
128 ImportOxios {
129 /// Path to the oxios-memory `memory.db` file.
130 db: PathBuf,
131 /// Target space (default: personal).
132 #[arg(long, default_value = "personal")]
133 space: String,
134 },
135 /// Token management (DESIGN §12.4: `token issue|list|revoke`).
136 Token {
137 #[command(subcommand)]
138 command: TokenCmd,
139 },
140 Serve {
141 /// Listen on a Unix-domain socket path instead of stdio.
142 #[arg(long)]
143 socket: Option<PathBuf>,
144 /// Listen on loopback HTTP (e.g. `127.0.0.1:8080`) instead of stdio.
145 #[arg(long)]
146 http: Option<String>,
147 /// Require token authentication on socket connections (DESIGN §11.2).
148 #[arg(long)]
149 require_token: bool,
150 /// Run as a background daemon: write a PID file and shut down
151 /// gracefully on SIGTERM/SIGINT (DESIGN §4.3, §15). External
152 /// supervision (launchd) handles backgrounding; this flag does not fork.
153 #[arg(long)]
154 daemon: bool,
155 /// Serve the desktop brain UI from this directory (GET requests).
156 /// Dev override — defaults to the embedded bundle (see ADR-008).
157 #[arg(long)]
158 ui_dir: Option<PathBuf>,
159 },
160 /// Predicate registry (DESIGN §12.4: `predicate add|list`).
161 Predicate {
162 #[command(subcommand)]
163 command: PredicateCmd,
164 },
165 /// Extract a single episode (calls the LLM, validates, projects).
166 Extract {
167 /// Episode ID to extract.
168 episode_id: String,
169 #[arg(long, default_value = "personal")]
170 space: String,
171 },
172 /// Re-extract all primary episodes with the configured extractor.
173 Reextract {
174 #[arg(long, default_value = "personal")]
175 space: String,
176 },
177 /// Model artifact management (§8.4: `model list|pull|verify|use`).
178 Model {
179 #[command(subcommand)]
180 command: ModelCmd,
181 },
182
183 /// Run the extraction evaluation suite (DESIGN §14.2).
184 Eval {
185 /// Suite: `fast` (fixture-replayed, no network) or `full` (live provider).
186 #[arg(long, default_value = "fast")]
187 suite: String,
188 },
189 /// Declare a statement from raw JSON (power-user path).
190 Declare {
191 /// Canonical declaration JSON.
192 json: String,
193 #[arg(long, default_value = "personal")]
194 space: String,
195 },
196 /// Source management.
197 Source {
198 #[command(subcommand)]
199 command: SourceCmd,
200 },
201}
202
203// ── Nested subcommand groups (DESIGN §12.4) ────────────────────────────────
204
205#[derive(Subcommand, Debug)]
206pub enum ModelCmd {
207 /// List installed models and their verification status.
208 List,
209 /// Download the default model set (or a named model).
210 Pull {
211 /// Model name or file. Omit to pull the whole default set.
212 name: Option<String>,
213 },
214 /// Re-hash installed models against the manifest.
215 Verify {
216 /// Model name. Omit to verify all.
217 name: Option<String>,
218 },
219 /// Resolve the active model for extraction (prints path + digest).
220 Use { name: String },
221}
222
223#[derive(Subcommand, Debug)]
224pub enum TokenCmd {
225 /// Issue a new token (returns the secret once).
226 Issue {
227 #[arg(long, default_value = "personal")]
228 space: String,
229 #[arg(long, help = "Comma-separated capabilities (Read,Ingest,Write,Sample)")]
230 caps: String,
231 #[arg(long)]
232 label: Option<String>,
233 },
234 /// List all tokens (secrets redacted).
235 List,
236 /// Revoke a token by id.
237 Revoke { id: String },
238}
239
240#[derive(Subcommand, Debug)]
241pub enum PredicateCmd {
242 /// List predicates in the core/v1 registry.
243 List,
244 /// Register a custom predicate from JSON.
245 Add {
246 /// Full PredicateDef JSON.
247 json: String,
248 #[arg(long, default_value = "personal")]
249 space: String,
250 },
251}
252
253#[derive(Subcommand, Debug)]
254pub enum EntityCmd {
255 /// Show entity beliefs.
256 Show {
257 id: String,
258 #[arg(long, default_value = "personal")]
259 space: String,
260 },
261 /// Merge two entities (loser → winner).
262 Merge {
263 /// Loser entity surface form.
264 loser: String,
265 /// Loser entity type.
266 loser_type: String,
267 /// Winner entity surface form.
268 winner: String,
269 /// Winner entity type.
270 winner_type: String,
271 #[arg(long, default_value = "personal")]
272 space: String,
273 },
274 /// Split: undo the most recent merge for an entity.
275 Split {
276 /// Entity surface form.
277 surface: String,
278 /// Entity type.
279 ty: String,
280 #[arg(long, default_value = "personal")]
281 space: String,
282 },
283 /// Add an alias to an entity.
284 Alias {
285 /// Entity surface form.
286 surface: String,
287 /// Entity type.
288 ty: String,
289 /// Alias surface form to add.
290 alias: String,
291 #[arg(long, default_value = "personal")]
292 space: String,
293 },
294 /// Retract a statement by ID.
295 Retract {
296 /// Statement ID to retract.
297 statement_id: String,
298 #[arg(long, default_value = "personal")]
299 space: String,
300 },
301}
302
303#[derive(Subcommand, Debug)]
304pub enum SourceCmd {
305 /// Set trust policy for a source.
306 Policy {
307 /// Source name (as registered).
308 name: String,
309 /// Trust tier: trusted | untrusted.
310 #[arg(long)]
311 trust: String,
312 /// Effective from (epoch ms). Defaults to now.
313 #[arg(long)]
314 effective_from: Option<i64>,
315 /// Effective to (epoch ms). Open-ended if omitted.
316 #[arg(long)]
317 effective_to: Option<i64>,
318 #[arg(long, default_value = "personal")]
319 space: String,
320 },
321}