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