Skip to main content

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}