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    /// 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        /// Point at the built `apps/brain-ui/dist/`.
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}
188
189// ── Nested subcommand groups (DESIGN §12.4) ────────────────────────────────
190
191#[derive(Subcommand, Debug)]
192pub enum ModelCmd {
193    /// List installed models and their verification status.
194    List,
195    /// Download the default model set (or a named model).
196    Pull {
197        /// Model name or file. Omit to pull the whole default set.
198        name: Option<String>,
199    },
200    /// Re-hash installed models against the manifest.
201    Verify {
202        /// Model name. Omit to verify all.
203        name: Option<String>,
204    },
205    /// Resolve the active model for extraction (prints path + digest).
206    Use { name: String },
207}
208
209#[derive(Subcommand, Debug)]
210pub enum TokenCmd {
211    /// Issue a new token (returns the secret once).
212    Issue {
213        #[arg(long, default_value = "personal")]
214        space: String,
215        #[arg(long, help = "Comma-separated capabilities (Read,Ingest,Write,Sample)")]
216        caps: String,
217        #[arg(long)]
218        label: Option<String>,
219    },
220    /// List all tokens (secrets redacted).
221    List,
222    /// Revoke a token by id.
223    Revoke { id: String },
224}
225
226#[derive(Subcommand, Debug)]
227pub enum PredicateCmd {
228    /// List predicates in the core/v1 registry.
229    List,
230}
231
232#[derive(Subcommand, Debug)]
233pub enum EntityCmd {
234    /// Show entity beliefs.
235    Show {
236        id: String,
237        #[arg(long, default_value = "personal")]
238        space: String,
239    },
240}