sqlite_graphrag/cli.rs
1//! CLI argument structs and command surface (clap-based).
2//!
3//! Defines `Cli` and all subcommand enums; contains no business logic.
4
5use crate::commands::*;
6use crate::i18n::{current, Language};
7use clap::{Parser, Subcommand};
8
9/// Returns the maximum simultaneous invocations allowed by the CPU heuristic.
10fn max_concurrency_ceiling() -> usize {
11 std::thread::available_parallelism()
12 .map(|n| n.get() * 2)
13 .unwrap_or(8)
14}
15
16/// Graph export format.
17#[derive(Copy, Clone, Debug, PartialEq, Eq, clap::ValueEnum)]
18pub enum GraphExportFormat {
19 /// JSON variant.
20 Json,
21 /// DOT variant.
22 Dot,
23 /// Mermaid variant.
24 Mermaid,
25 /// Stream one JSON object per entity, then one per edge, then a summary line.
26 Ndjson,
27}
28
29// Backend choice enums live in `backend_choice` (Wave C1).
30pub use crate::backend_choice::{EmbeddingBackendChoice, LlmBackendChoice};
31
32#[derive(Parser)]
33#[command(name = "sqlite-graphrag")]
34#[command(version)]
35#[command(about = "Local GraphRAG memory for LLMs in a single SQLite file")]
36#[command(arg_required_else_help = true)]
37#[command(after_help = "DATABASE PATH (GAP-SG-32):\n \
38 `--db` is a PER-SUBCOMMAND flag, so it must come AFTER the subcommand:\n \
39 sqlite-graphrag remember --db ./graphrag.sqlite --name mem --type note ...\n \
40 Placing it before the subcommand (e.g. `sqlite-graphrag --db x.sqlite remember`) is rejected.\n \
41 Prefer `--db` on every invocation (one-shot agents). Optional XDG defaults:\n \
42 `sqlite-graphrag config set db.path ./graphrag.sqlite`\n \
43 Product environment variables are not read at runtime; use flags + `config set/get`.")]
44/// CLI.
45pub struct Cli {
46 /// Maximum number of simultaneous CLI invocations allowed (default: 4).
47 ///
48 /// Caps the counting semaphore used for CLI concurrency slots. The value must
49 /// stay within [1, 2×nCPUs]. Values above the ceiling are rejected with exit 2.
50 #[arg(long, global = true, value_name = "N")]
51 pub max_concurrency: Option<usize>,
52
53 /// Wait up to SECONDS for a free concurrency slot before giving up (exit 75).
54 ///
55 /// Useful in retrying agent pipelines: the process polls every 500 ms until a
56 /// slot opens or the timeout expires. Default: 300s (5 minutes).
57 #[arg(long, global = true, value_name = "SECONDS")]
58 pub wait_lock: Option<u64>,
59
60 /// Skip the available-memory check before loading the model.
61 ///
62 /// Exclusive use in automated tests where real allocation does not occur.
63 #[arg(long, global = true, hide = true, default_value_t = false)]
64 pub skip_memory_guard: bool,
65
66 /// v1.0.83 (ADR-0041): strict env-clear mode for compliance environments.
67 ///
68 /// When enabled, the LLM subprocess receives ONLY `PATH` — no
69 /// `ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_BASE_URL`, `OPENAI_BASE_URL`
70 /// or other custom-provider credentials are forwarded. Defaults to
71 /// the standard v1.0.83 whitelist that preserves custom-provider
72 /// credentials (ADR-0041). Prefer the flag; optional XDG
73 /// `spawn.strict_env_clear=1` via `config set`.
74 #[arg(
75 long,
76 global = true,
77 hide = true,
78 default_value_t = false,
79 value_parser = clap::builder::BoolishValueParser::new(),
80 )]
81 pub strict_env_clear: bool,
82
83 /// v1.0.84 (ADR-0042 / GAP-002): resolve and print the LLM backend that
84 /// WOULD be invoked for embedding (binary path + model + flavour),
85 /// then exit 0 without executing the subprocess. Useful for CI
86 /// audit and sanity-check of `--llm-backend` before long sessions.
87 ///
88 /// Prefer the flag; optional XDG `llm.dry_run_backend=1` via `config set`.
89 #[arg(
90 long,
91 global = true,
92 hide = true,
93 default_value_t = false,
94 value_parser = clap::builder::BoolishValueParser::new(),
95 )]
96 pub dry_run_backend: bool,
97
98 /// Language for human-facing stderr messages. Accepts `en` or `pt`.
99 ///
100 /// Without the flag, detection uses XDG `i18n.lang` then OS locale
101 /// (`LC_ALL`/`LC_MESSAGES`/`LANG`). JSON stdout stays deterministic and
102 /// identical across languages; only human-facing strings are affected.
103 #[arg(long, global = true, value_enum, value_name = "LANG")]
104 pub lang: Option<crate::i18n::Language>,
105
106 /// Time zone for `*_iso` fields in JSON output (for example `America/Sao_Paulo`).
107 ///
108 /// Accepts any IANA time zone name. Without the flag, it falls back to
109 /// XDG `display.tz`; if unset, UTC is used. Integer epoch fields
110 /// are not affected.
111 #[arg(long, global = true, value_name = "IANA")]
112 pub tz: Option<chrono_tz::Tz>,
113
114 /// Directory holding `config.toml`. Overrides the OS config directory.
115 ///
116 /// Precedence (G-T-XDG-04): this flag > OS default. It deliberately does
117 /// NOT consult a `config set` key, because the config file itself lives in
118 /// this directory and reading it to find itself would be circular.
119 /// Hidden: it exists for hermetic test isolation and sandboxed hosts.
120 #[arg(long, global = true, hide = true, value_name = "DIR")]
121 pub config_dir: Option<std::path::PathBuf>,
122
123 /// Directory for lock files, model files and other cache artifacts.
124 ///
125 /// Precedence (G-T-XDG-04): this flag > XDG `cache.dir` > OS default.
126 /// Hidden for the same reason as `--config-dir`.
127 #[arg(long, global = true, hide = true, value_name = "DIR")]
128 pub cache_dir: Option<std::path::PathBuf>,
129
130 /// Increase logging verbosity (-v=info, -vv=debug, -vvv=trace).
131 ///
132 /// Overrides XDG `log.level` when present. Logs are emitted
133 /// to stderr; JSON stdout is unaffected.
134 #[arg(short = 'v', long, global = true, action = clap::ArgAction::Count)]
135 pub verbose: u8,
136
137 /// Suppress non-error tracing on stderr (sets log level to `error`).
138 ///
139 /// Prefer this in pipelines that capture stdout JSON (`> out.json`).
140 /// Never combine stdout and stderr into the same file (`&>` / `2>&1`) —
141 /// that contaminates the JSON envelope (v1.1.05 Bug 2). Conflicts with
142 /// `-v` / `--verbose` only in spirit: quiet wins when both are present.
143 #[arg(short = 'q', long, global = true, default_value_t = false)]
144 pub quiet: bool,
145
146 /// v1.0.75 (G21 solution): extraction backend selector. Accepts
147 /// `llm` (default), `embedding` (legacy), `none`, or `both` (composite).
148 /// The `llm` backend invokes claude code / codex CLI headless to extract
149 /// entities and relationships; `embedding` is a permanent stub since
150 /// v1.0.79 (legacy fastembed pipeline removed) that returns a clear
151 /// migration error.
152 #[arg(long, global = true, value_name = "KIND", default_value = "llm")]
153 pub extraction_backend: Option<String>,
154
155 /// Embedding dimensionality override (default 1024 since v1.2.0).
156 ///
157 /// Precedence: this flag > XDG `embedding.dim` >
158 /// the `dim` recorded in the database `schema_meta` > 1024. Existing
159 /// databases keep their recorded dimensionality automatically; use
160 /// this flag only to migrate a corpus to a new dimensionality
161 /// (followed by `enrich --operation re-embed`). Range: [8, 4096].
162 #[arg(long, global = true, value_name = "N", value_parser = clap::value_parser!(u64).range(8..=4096))]
163 pub embedding_dim: Option<u64>,
164
165 /// v1.0.82 (GAP-003) / v1.0.84 (ADR-0042): LLM backend for embedding.
166 /// Accepts `auto` (detects via PATH, codex-first), `codex` (forces
167 /// `codex exec`), `claude` (forces `claude -p`; since v1.0.84 does NOT fall back to
168 /// codex — emits `AppError::Validation` if `claude` is absent),
169 /// `opencode` (forces `opencode run`), or `none`
170 /// (skips embedding; useful for tests). Prefer the flag; optional XDG
171 /// XDG `llm.backend` via `config set`.
172 #[arg(long, global = true, value_enum, default_value_t = LlmBackendChoice::Auto)]
173 pub llm_backend: LlmBackendChoice,
174
175 /// v1.0.82 (GAP-003): model to invoke on the chosen backend.
176 /// Prefer the flag; optional XDG `llm.model`. The default depends
177 /// on the backend (codex: `gpt-5.5`; claude: `claude-sonnet-4-6`).
178 #[arg(long, global = true, value_name = "MODEL")]
179 pub llm_model: Option<String>,
180
181 /// v1.0.82 (GAP-003): path to the `claude` binary (overrides
182 /// PATH detection). Prefer the flag; optional XDG `llm.claude_binary`.
183 #[arg(long, global = true, value_name = "PATH")]
184 pub claude_binary: Option<std::path::PathBuf>,
185
186 /// v1.0.89 (GAP-1): path to the `codex` binary (overrides
187 /// PATH detection). Prefer the flag; optional XDG `llm.codex_binary`.
188 #[arg(long, global = true, value_name = "PATH")]
189 pub codex_binary: Option<std::path::PathBuf>,
190
191 /// v1.0.90 (GAP-OPENCODE-001): path to the `opencode` binary (overrides
192 /// PATH detection). Prefer the flag; optional XDG `llm.opencode_binary`.
193 #[arg(long, global = true, value_name = "PATH")]
194 pub opencode_binary: Option<std::path::PathBuf>,
195
196 /// v1.0.82 (GAP-005): chain of LLM backends tried in order
197 /// when the primary fails. Default `codex,claude,none`. Prefer the
198 /// flag; optional XDG `llm.fallback`.
199 #[arg(long, global = true, default_value = "codex,claude,none")]
200 pub llm_fallback: String,
201
202 /// v1.0.82 (GAP-005): persists with a NULL embedding when all
203 /// backends in the chain fail. The memory stays in `pending_embeddings`
204 /// for reprocessing via `embedding retry`. Prefer the flag; optional XDG
205 /// XDG `llm.skip_embedding_on_failure`.
206 #[arg(
207 long,
208 global = true,
209 default_value_t = false,
210 value_parser = clap::builder::BoolishValueParser::new(),
211 )]
212 pub skip_embedding_on_failure: bool,
213
214 /// v1.0.82 (GAP-004): host-wide limit of concurrent LLM
215 /// subprocesses. Default derived from `ncpus`. Prefer the flag; optional XDG
216 /// XDG `llm.max_host_concurrency`.
217 #[arg(long, global = true, value_name = "N")]
218 pub llm_max_host_concurrency: Option<u32>,
219
220 /// v1.0.82 (GAP-004): seconds to wait for a free LLM slot
221 /// before failing with exit 75. Default 30s. Prefer the flag; optional XDG
222 /// XDG `llm.slot_wait_secs`.
223 #[arg(long, global = true, value_name = "SECONDS")]
224 pub llm_slot_wait_secs: Option<u64>,
225
226 /// v1.0.82 (GAP-004): if set, fails immediately (exit 75)
227 /// when no LLM slot is free. Prefer the flag; optional XDG
228 /// XDG `llm.slot_no_wait`.
229 #[arg(
230 long,
231 global = true,
232 default_value_t = false,
233 value_parser = clap::builder::BoolishValueParser::new(),
234 )]
235 pub llm_slot_no_wait: bool,
236
237 /// v1.0.93: embedding backend selector. `auto` tries OpenRouter API if key
238 /// available, falls back to LLM subprocess. `openrouter` requires API key.
239 /// `llm` forces subprocess. Prefer the flag; optional XDG `embedding.backend`.
240 #[arg(long, global = true, value_enum, default_value_t = EmbeddingBackendChoice::Auto)]
241 pub embedding_backend: EmbeddingBackendChoice,
242
243 /// v1.0.93: embedding model for the OpenRouter API. Required when
244 /// `--embedding-backend openrouter`. Prefer the flag; optional XDG `embedding.model`.
245 #[arg(long, global = true, value_name = "MODEL")]
246 pub embedding_model: Option<String>,
247
248 /// v1.0.93: OpenRouter API key (prefer env var or config.toml over CLI flag
249 /// to avoid shell history exposure). Prefer `config set-key openrouter`.
250 #[arg(
251 long,
252 global = true,
253 value_name = "KEY",
254 hide = true,
255 hide_env_values = true
256 )]
257 pub openrouter_api_key: Option<String>,
258
259 /// Subcommand to execute.
260 #[command(subcommand)]
261 pub command: Option<Commands>,
262}
263
264#[cfg(test)]
265#[path = "cli_json_only_format_tests.rs"]
266mod json_only_format_tests;
267
268impl Cli {
269 /// Validates concurrency flags and returns a localised descriptive error if invalid.
270 ///
271 /// Requires that `crate::i18n::init()` has already been called (happens before this
272 /// function in the `main` flow). In English it emits EN messages; in Portuguese it emits PT.
273 pub fn validate_flags(&self) -> Result<(), String> {
274 if let Some(n) = self.max_concurrency {
275 if n == 0 {
276 return Err(match current() {
277 Language::English => "--max-concurrency must be >= 1".to_string(),
278 Language::Portuguese => "--max-concurrency deve ser >= 1".to_string(),
279 });
280 }
281 let teto = max_concurrency_ceiling();
282 if n > teto {
283 return Err(match current() {
284 Language::English => format!(
285 "--max-concurrency {n} exceeds the ceiling of {teto} (2×nCPUs) on this system"
286 ),
287 Language::Portuguese => format!(
288 "--max-concurrency {n} excede o teto de {teto} (2×nCPUs) neste sistema"
289 ),
290 });
291 }
292 }
293 Ok(())
294 }
295}
296
297impl Commands {
298 /// Returns true for subcommands that load the ONNX model locally.
299 pub fn is_embedding_heavy(&self) -> bool {
300 matches!(
301 self,
302 Self::Init(_)
303 | Self::Remember(_)
304 | Self::RememberBatch(_)
305 | Self::Recall(_)
306 | Self::HybridSearch(_)
307 | Self::DeepResearch(_)
308 )
309 }
310
311 /// Return whether this command occupies a CLI concurrency slot.
312 pub fn uses_cli_slot(&self) -> bool {
313 true
314 }
315
316 /// Read-only / no-embedding subcommands that MUST run without an embedding
317 /// API key. `init` warms a best-effort smoke test internally and degrades to
318 /// `ok_no_embedding` when the backend is unreachable; the `enrich` queue
319 /// inspectors (`--status` / `--list-dead` / `--requeue-dead` /
320 /// `--prune-dead-orphans`) never embed and never call the LLM. The eager
321 /// OpenRouter key preflight in `main` must skip its hard-fail for these.
322 pub fn tolerates_missing_embedding_key(&self) -> bool {
323 match self {
324 Self::Init(_) => true,
325 Self::Enrich(args) => {
326 args.status
327 || args.list_dead
328 || args.requeue_dead
329 || args.list_skipped
330 || args.requeue_skipped
331 || args.prune_dead_orphans
332 || args.prune_dead_entity_orphans
333 || args.print_schema
334 }
335 _ => false,
336 }
337 }
338}
339
340/// GAP-E2E-010 (v1.0.89): `codex-models` accepts `--json` as a no-op so
341/// agents that append `--json` to every subcommand never see clap errors.
342/// The handler in `main.rs` always emits JSON on stdout; this flag is
343/// accepted and ignored for parity with the rest of the CLI surface.
344///
345/// GAP-SG-139: also accepts `--db` as a no-op for agent uniformity (host surface).
346#[derive(Debug, clap::Args)]
347pub struct CodexModelsArgs {
348 /// No-op; JSON is always emitted on stdout by `codex-models`.
349 #[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
350 pub json: bool,
351 /// GAP-SG-139: accepted as a no-op for agent uniformity (no graph I/O).
352 #[command(flatten)]
353 pub db_noop: crate::cli_db_noop::DbNoopArgs,
354}
355
356/// Commands.
357#[derive(Subcommand)]
358pub enum Commands {
359 /// Initialize database and download embedding model
360 #[command(after_long_help = "EXAMPLES:\n \
361 # Initialize in current directory (default behavior)\n \
362 sqlite-graphrag init\n\n \
363 # Initialize at a specific path\n \
364 sqlite-graphrag init --db /path/to/graphrag.sqlite\n\n \
365 # Persist default db path via XDG config (no product env)\n \
366 sqlite-graphrag config set db.path /data/graphrag.sqlite\n \
367 sqlite-graphrag init\n\n\
368 NOTES:\n \
369 - `init` is OPTIONAL: any subsequent CRUD command auto-initializes graphrag.sqlite if missing.\n \
370 - As a side effect, `init` warms a smoke-test embedding via the LLM-only one-shot pipeline.")]
371 Init(init::InitArgs),
372 /// Save a memory with optional entity graph
373 #[command(after_long_help = "EXAMPLES:\n \
374 # Inline body\n \
375 sqlite-graphrag remember --name onboarding --type user --description \"intro\" --body \"hello\"\n\n \
376 # Body from file\n \
377 sqlite-graphrag remember --name doc1 --type document --description \"...\" --body-file ./README.md\n\n \
378 # Body from stdin (pipe)\n \
379 cat README.md | sqlite-graphrag remember --name doc1 --type document --description \"...\" --body-stdin\n\n \
380 # Enable automatic URL extraction (URL-regex only since v1.0.79)\n \
381 sqlite-graphrag remember --name rich --type note --description \"...\" --body \"...\" --enable-ner")]
382 Remember(remember::RememberArgs),
383 /// Batch-create memories from NDJSON stdin (one invocation, one slot)
384 #[command(after_long_help = "EXAMPLES:\n \
385 # Batch create from NDJSON\n \
386 cat memories.ndjson | sqlite-graphrag remember-batch --force-merge --json\n\n \
387 # Atomic batch\n \
388 cat memories.ndjson | sqlite-graphrag remember-batch --transaction --json")]
389 RememberBatch(remember_batch::RememberBatchArgs),
390 /// Bulk-ingest every file under a directory as separate memories (NDJSON output)
391 Ingest(Box<ingest::IngestArgs>),
392 /// Search memories semantically
393 #[command(after_long_help = "EXAMPLES:\n \
394 # Top 10 semantic matches (default)\n \
395 sqlite-graphrag recall \"agent memory\"\n\n \
396 # Top 3 only\n \
397 sqlite-graphrag recall \"agent memory\" -k 3\n\n \
398 # Search across all namespaces\n \
399 sqlite-graphrag recall \"agent memory\" --all-namespaces\n\n \
400 # Disable graph traversal (vector-only)\n \
401 sqlite-graphrag recall \"agent memory\" --no-graph")]
402 Recall(recall::RecallArgs),
403 /// Read a memory by exact name
404 Read(read::ReadArgs),
405 /// List memories with filters
406 List(list::ListArgs),
407 /// Soft-delete a memory
408 Forget(forget::ForgetArgs),
409 /// Permanently delete soft-deleted memories
410 Purge(purge::PurgeArgs),
411 /// Rename a memory preserving history
412 Rename(rename::RenameArgs),
413 /// Split an oversized memory body into N child memories (v1.1.03, GAP-V8)
414 SplitBody(split_body::SplitBodyArgs),
415 /// Edit a memory's body or description
416 Edit(edit::EditArgs),
417 /// List all versions of a memory
418 History(history::HistoryArgs),
419 /// Restore a memory to a previous version
420 Restore(restore::RestoreArgs),
421 /// Search using hybrid vector + full-text search
422 #[command(after_long_help = "EXAMPLES:\n \
423 # Hybrid search combining KNN + FTS5 BM25 with RRF\n \
424 sqlite-graphrag hybrid-search \"agent memory architecture\"\n\n \
425 # Custom weights for vector vs full-text components\n \
426 sqlite-graphrag hybrid-search \"agent\" --weight-vec 0.7 --weight-fts 0.3")]
427 HybridSearch(hybrid_search::HybridSearchArgs),
428 /// Show database health
429 Health(health::HealthArgs),
430 /// Apply pending schema migrations
431 Migrate(migrate::MigrateArgs),
432 /// Resolve namespace precedence for the current invocation
433 NamespaceDetect(namespace_detect::NamespaceDetectArgs),
434 /// Run PRAGMA optimize on the database
435 Optimize(optimize::OptimizeArgs),
436 /// Show database statistics
437 Stats(stats::StatsArgs),
438 /// Create a checkpointed copy safe for file sync
439 SyncSafeCopy(sync_safe_copy::SyncSafeCopyArgs),
440 /// Back up the database using the SQLite Online Backup API
441 Backup(backup::BackupArgs),
442 /// Run VACUUM after checkpointing the WAL
443 Vacuum(vacuum::VacuumArgs),
444 /// Create an explicit relationship between two entities
445 Link(link::LinkArgs),
446 /// Remove a specific relationship between two entities
447 Unlink(unlink::UnlinkArgs),
448 /// Deep parallel multi-hop GraphRAG research
449 #[command(name = "deep-research")]
450 DeepResearch(deep_research::DeepResearchArgs),
451 /// List memories connected via the entity graph
452 Related(related::RelatedArgs),
453 /// Export a graph snapshot in json, dot or mermaid
454 Graph(graph_export::GraphArgs),
455 /// Export memories as NDJSON (one JSON line per memory, plus a summary line)
456 Export(export::ExportArgs),
457 /// FTS5 full-text search index management (rebuild or check)
458 Fts(fts::FtsArgs),
459 /// Vector index maintenance (orphan detection, purge, stats) — G39
460 Vec(vec::VecArgs),
461 /// List codex OAuth models accepted by ChatGPT Pro (G33).
462 ///
463 /// GAP-E2E-010 (v1.0.89): accepts `--json` as a no-op (JSON is always
464 /// emitted on stdout) so the flag never breaks agent pipelines that
465 /// append `--json` to every invocation.
466 #[command(name = "codex-models")]
467 CodexModels(CodexModelsArgs),
468 /// Bulk-delete all relationships of a given type (e.g. mentions)
469 PruneRelations(prune_relations::PruneRelationsArgs),
470 /// Remove NER bindings (memory_entities rows) for an entity or all entities
471 #[command(name = "prune-ner")]
472 PruneNer(prune_ner::PruneNerArgs),
473 /// Inspect and manage cross-process LLM slot semaphore (GAP-004, v1.0.82)
474 Slots(slots::SlotsArgs),
475 /// Inspect and manage the `remember` checkpoint queue (GAP-001, v1.0.82)
476 Pending(pending::PendingArgs),
477 /// Health and per-entry inspection of the pending-embeddings queue (GAP-005, v1.0.82)
478 Embedding(embedding::EmbeddingArgs),
479 /// Batch operations over the pending-embeddings queue (GAP-005, v1.0.82)
480 #[command(name = "pending-embeddings")]
481 PendingEmbeddings(pending_embeddings::PendingEmbeddingsArgs),
482 /// Remove entities that have no memories and no relationships
483 CleanupOrphans(cleanup_orphans::CleanupOrphansArgs),
484 /// List entities linked to a specific memory
485 MemoryEntities(memory_entities::MemoryEntitiesArgs),
486 /// Manage cached resources (embedding models, etc.)
487 Cache(cache::CacheArgs),
488 /// Delete an entity and all its relationships from the graph
489 #[command(name = "delete-entity")]
490 DeleteEntity(delete_entity::DeleteEntityArgs),
491 /// Reclassify one entity or a batch of entities to a new type
492 Reclassify(reclassify::ReclassifyArgs),
493 /// Rename an entity preserving all relationships and memory bindings
494 #[command(name = "rename-entity")]
495 RenameEntity(rename_entity::RenameEntityArgs),
496 /// Merge multiple source entities into a single target entity
497 #[command(name = "merge-entities")]
498 MergeEntities(merge_entities::MergeEntitiesArgs),
499 /// Enrich graph memories and entities using an LLM provider
500 Enrich(Box<enrich::EnrichArgs>),
501 /// Reclassify relationship types across the graph using rules or LLM judgment
502 #[command(name = "reclassify-relation")]
503 ReclassifyRelation(reclassify_relation::ReclassifyRelationArgs),
504 /// Normalize entity names (deduplicate, kebab-case, merge near-duplicates)
505 #[command(name = "normalize-entities")]
506 NormalizeEntities(normalize_entities::NormalizeEntitiesArgs),
507 /// Generate shell completions for Bash, Zsh, Fish, PowerShell, or Elvish
508 Completions(completions::CompletionsArgs),
509 /// `debug-schema` subcommand.
510 #[command(name = "debug-schema", hide = true)]
511 DebugSchema(debug_schema::DebugSchemaArgs),
512 /// Manage API keys and diagnose provider configuration (v1.0.93)
513 Config(config_cmd::ConfigArgs),
514}
515// FIX-1 (v1.0.89): manual `Debug` impl so test panic messages that print
516// `{:?}` on a captured `Commands` variant compile without requiring every
517// contained subcommand arg struct to derive `Debug`. The Debug output is
518// only used in test assertions for diagnostic messages; we emit the variant
519// name only — arg payload is intentionally omitted.
520impl std::fmt::Debug for Commands {
521 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
522 let name = match self {
523 Self::Init(_) => "Init",
524 Self::Health(_) => "Health",
525 Self::Stats(_) => "Stats",
526 Self::List(_) => "List",
527 Self::Read(_) => "Read",
528 Self::Edit(_) => "Edit",
529 Self::Rename(_) => "Rename",
530 Self::SplitBody(_) => "SplitBody",
531 Self::Restore(_) => "Restore",
532 Self::History(_) => "History",
533 Self::Forget(_) => "Forget",
534 Self::Purge(_) => "Purge",
535 Self::Remember(_) => "Remember",
536 Self::RememberBatch(_) => "RememberBatch",
537 Self::Recall(_) => "Recall",
538 Self::HybridSearch(_) => "HybridSearch",
539 Self::Enrich(_) => "Enrich",
540 Self::Ingest(_) => "Ingest",
541 Self::Optimize(_) => "Optimize",
542 Self::Migrate(_) => "Migrate",
543 Self::SyncSafeCopy(_) => "SyncSafeCopy",
544 Self::Backup(_) => "Backup",
545 Self::Vacuum(_) => "Vacuum",
546 Self::Link(_) => "Link",
547 Self::Unlink(_) => "Unlink",
548 Self::DeepResearch(_) => "DeepResearch",
549 Self::Related(_) => "Related",
550 Self::Graph(_) => "Graph",
551 Self::Export(_) => "Export",
552 Self::Fts(_) => "Fts",
553 Self::Vec(_) => "Vec",
554 Self::CodexModels(_) => "CodexModels",
555 Self::PruneRelations(_) => "PruneRelations",
556 Self::PruneNer(_) => "PruneNer",
557 Self::Slots(_) => "Slots",
558 Self::Pending(_) => "Pending",
559 Self::Embedding(_) => "Embedding",
560 Self::PendingEmbeddings(_) => "PendingEmbeddings",
561 Self::CleanupOrphans(_) => "CleanupOrphans",
562 Self::MemoryEntities(_) => "MemoryEntities",
563 Self::Cache(_) => "Cache",
564 Self::DeleteEntity(_) => "DeleteEntity",
565 Self::Reclassify(_) => "Reclassify",
566 Self::RenameEntity(_) => "RenameEntity",
567 Self::ReclassifyRelation(_) => "ReclassifyRelation",
568 Self::NormalizeEntities(_) => "NormalizeEntities",
569 Self::MergeEntities(_) => "MergeEntities",
570 Self::NamespaceDetect(_) => "NamespaceDetect",
571 Self::Completions(_) => "Completions",
572 Self::DebugSchema(_) => "DebugSchema",
573 Self::Config(_) => "Config",
574 };
575 f.write_str(name)
576 }
577}
578
579/// Memory type.
580#[derive(Copy, Clone, Debug, Default, clap::ValueEnum)]
581pub enum MemoryType {
582 /// User variant.
583 User,
584 /// Feedback variant.
585 Feedback,
586 /// Project variant.
587 Project,
588 /// Reference variant.
589 Reference,
590 /// Decision variant.
591 Decision,
592 /// Incident variant.
593 Incident,
594 /// Skill variant.
595 Skill,
596 /// Document variant.
597 #[default]
598 Document,
599 /// Note variant.
600 Note,
601}
602
603#[cfg(test)]
604#[path = "cli_heavy_concurrency_tests.rs"]
605mod heavy_concurrency_tests;
606
607impl MemoryType {
608 /// Return the canonical string representation.
609 pub fn as_str(&self) -> &'static str {
610 match self {
611 Self::User => "user",
612 Self::Feedback => "feedback",
613 Self::Project => "project",
614 Self::Reference => "reference",
615 Self::Decision => "decision",
616 Self::Incident => "incident",
617 Self::Skill => "skill",
618 Self::Document => "document",
619 Self::Note => "note",
620 }
621 }
622}
623
624/// GAP-SG-31/33/34/35/30: parse-time contracts for the Fase G clap fixes.
625#[cfg(test)]
626#[path = "cli_fase_g_parsing_tests.rs"]
627mod fase_g_parsing_tests;