sqlite_graphrag/cli/commands.rs
1//! The subcommand surface: every `Commands` variant and its classification.
2//!
3//! Holds the `Commands` enum, the predicates `main` uses to route a variant,
4//! and the manual `Debug` impl.
5
6use crate::commands::*;
7use clap::Subcommand;
8
9/// Every subcommand the CLI dispatches, in the order `--help` renders them.
10#[derive(Subcommand)]
11pub enum Commands {
12 /// Initialize the database and write the schema (no model download, no subprocess)
13 #[command(after_long_help = "EXAMPLES:\n \
14 # Initialize in current directory (default behavior)\n \
15 sqlite-graphrag init\n\n \
16 # Initialize at a specific path\n \
17 sqlite-graphrag init --db /path/to/graphrag.sqlite\n\n \
18 # Persist default db path via XDG config (no product env)\n \
19 sqlite-graphrag config set db.path /data/graphrag.sqlite\n \
20 sqlite-graphrag init\n\n\
21 NOTES:\n \
22 - `init` is OPTIONAL: any subsequent CRUD command auto-initializes graphrag.sqlite if missing.\n \
23 - As a side effect, `init` warms a smoke-test embedding via the LLM-only one-shot pipeline.")]
24 Init(init::InitArgs),
25 /// Save a memory with optional entity graph
26 #[command(after_long_help = "EXAMPLES:\n \
27 # Inline body\n \
28 sqlite-graphrag remember --name onboarding --type user --description \"intro\" --body \"hello\"\n\n \
29 # Body from file\n \
30 sqlite-graphrag remember --name doc1 --type document --description \"...\" --body-file ./README.md\n\n \
31 # Body from stdin (pipe)\n \
32 cat README.md | sqlite-graphrag remember --name doc1 --type document --description \"...\" --body-stdin\n\n \
33 # Enable automatic URL extraction (URL-regex only since v1.0.79)\n \
34 sqlite-graphrag remember --name rich --type note --description \"...\" --body \"...\" --enable-ner\n\n \
35 # Positional name, the same form edit/read/forget/history accept\n \
36 sqlite-graphrag remember onboarding --type user --description \"intro\" --body \"hello\"\n\n\
37 NOTES:\n \
38 - The name may be given positionally OR via --name, never both.\n \
39 - Pick exactly one body source: --body, --body-file, --body-stdin or --graph-stdin.\n \
40 - --graph-file combines with any of the first three.\n\n\
41 ENTITY TYPES (for graph entities, NOT the memory --type):\n \
42 The entity vocabulary is OPEN since v1.2.8: any label is stored as\n \
43 you write it, and none is ever rewritten into another.\n \
44 RECOMMENDED labels: concept, tool, person, file, project, decision,\n \
45 incident, organization, location, date, dashboard, issue_tracker,\n \
46 memory. A label outside them is accepted and reported in the\n \
47 response `warnings` array, never substituted.\n \
48 Pass --strict-entity-types to refuse anything outside the thirteen.\n \
49 Shape is still checked: a label cannot be empty, digits only,\n \
50 contain a line break, or exceed 64 characters.\n \
51 The memory --type is a DIFFERENT and CLOSED vocabulary (user,\n \
52 feedback, project, reference, decision, incident, skill, document,\n \
53 note); the three names in both mean different things.\n \
54 Inspect what your database actually uses: sqlite-graphrag graph entity-types\n \
55 Wire contract: sqlite-graphrag schema --name graph-input")]
56 Remember(remember::RememberArgs),
57 /// Batch-create memories from NDJSON stdin (one invocation, one slot)
58 #[command(after_long_help = "EXAMPLES:\n \
59 # Batch create from NDJSON\n \
60 cat memories.ndjson | sqlite-graphrag remember-batch --force-merge --json\n\n \
61 # Atomic batch\n \
62 cat memories.ndjson | sqlite-graphrag remember-batch --transaction --json")]
63 RememberBatch(remember_batch::RememberBatchArgs),
64 /// Bulk-ingest every file under a directory as separate memories (NDJSON output)
65 Ingest(Box<ingest::IngestArgs>),
66 /// Search memories semantically
67 #[command(after_long_help = "EXAMPLES:\n \
68 # Top 10 semantic matches (default)\n \
69 sqlite-graphrag recall \"agent memory\"\n\n \
70 # Top 3 only\n \
71 sqlite-graphrag recall \"agent memory\" -k 3\n\n \
72 # Search across all namespaces\n \
73 sqlite-graphrag recall \"agent memory\" --all-namespaces\n\n \
74 # Disable graph traversal (vector-only)\n \
75 sqlite-graphrag recall \"agent memory\" --no-graph")]
76 Recall(recall::RecallArgs),
77 /// Read a memory by exact name
78 Read(read::ReadArgs),
79 /// List memories with filters
80 List(list::ListArgs),
81 /// Soft-delete a memory
82 Forget(forget::ForgetArgs),
83 /// Permanently delete soft-deleted memories
84 Purge(purge::PurgeArgs),
85 /// Rename a memory preserving history
86 Rename(rename::RenameArgs),
87 /// Split an oversized memory body into N child memories (v1.1.03, GAP-V8)
88 SplitBody(split_body::SplitBodyArgs),
89 /// Edit a memory's body or description
90 Edit(edit::EditArgs),
91 /// List all versions of a memory
92 History(history::HistoryArgs),
93 /// Restore a memory to a previous version
94 Restore(restore::RestoreArgs),
95 /// Search using hybrid vector + full-text search
96 #[command(after_long_help = "EXAMPLES:\n \
97 # Hybrid search combining KNN + FTS5 BM25 with RRF\n \
98 sqlite-graphrag hybrid-search \"agent memory architecture\"\n\n \
99 # Custom weights for vector vs full-text components\n \
100 sqlite-graphrag hybrid-search \"agent\" --weight-vec 0.7 --weight-fts 0.3")]
101 HybridSearch(hybrid_search::HybridSearchArgs),
102 /// Show database health
103 Health(health::HealthArgs),
104 /// Apply pending schema migrations
105 Migrate(migrate::MigrateArgs),
106 /// Resolve namespace precedence for the current invocation
107 NamespaceDetect(namespace_detect::NamespaceDetectArgs),
108 /// Run PRAGMA optimize on the database
109 Optimize(optimize::OptimizeArgs),
110 /// Show database statistics
111 Stats(stats::StatsArgs),
112 /// Create a checkpointed copy safe for file sync
113 SyncSafeCopy(sync_safe_copy::SyncSafeCopyArgs),
114 /// Back up the database using the SQLite Online Backup API
115 Backup(backup::BackupArgs),
116 /// Run VACUUM after checkpointing the WAL
117 Vacuum(vacuum::VacuumArgs),
118 /// Create an explicit relationship between two entities
119 Link(link::LinkArgs),
120 /// Remove a specific relationship between two entities
121 Unlink(unlink::UnlinkArgs),
122 /// Deep parallel multi-hop GraphRAG research
123 #[command(name = "deep-research")]
124 DeepResearch(deep_research::DeepResearchArgs),
125 /// List memories connected via the entity graph
126 Related(related::RelatedArgs),
127 /// Export a graph snapshot in json, dot or mermaid
128 Graph(graph_export::GraphArgs),
129 /// Export memories as NDJSON (one JSON line per memory, plus a summary line)
130 Export(export::ExportArgs),
131 /// FTS5 full-text search index management (rebuild or check)
132 Fts(fts::FtsArgs),
133 /// Vector index maintenance (orphan detection, purge, stats) — G39
134 Vec(vec::VecArgs),
135 /// Bulk-delete all relationships of a given type (e.g. mentions)
136 PruneRelations(prune_relations::PruneRelationsArgs),
137 /// Remove NER bindings (memory_entities rows) for an entity or all entities
138 #[command(name = "prune-ner")]
139 PruneNer(prune_ner::PruneNerArgs),
140 /// Inspect and manage cross-process LLM slot semaphore (GAP-004, v1.0.82)
141 Slots(slots::SlotsArgs),
142 /// Health and per-entry inspection of the pending-embeddings queue (GAP-005, v1.0.82)
143 Embedding(embedding::EmbeddingArgs),
144 /// Batch operations over the pending-embeddings queue (GAP-005, v1.0.82)
145 #[command(name = "pending-embeddings")]
146 PendingEmbeddings(pending_embeddings::PendingEmbeddingsArgs),
147 /// Remove entities that have no memories and no relationships
148 CleanupOrphans(cleanup_orphans::CleanupOrphansArgs),
149 /// List entities linked to a specific memory
150 MemoryEntities(memory_entities::MemoryEntitiesArgs),
151 /// Manage cached resources (embedding models, etc.)
152 Cache(cache::CacheArgs),
153 /// Delete an entity and all its relationships from the graph
154 #[command(name = "delete-entity")]
155 DeleteEntity(delete_entity::DeleteEntityArgs),
156 /// Reclassify one entity or a batch of entities to a new type
157 Reclassify(reclassify::ReclassifyArgs),
158 /// Rename an entity preserving all relationships and memory bindings
159 #[command(name = "rename-entity")]
160 RenameEntity(rename_entity::RenameEntityArgs),
161 /// Merge multiple source entities into a single target entity
162 #[command(name = "merge-entities")]
163 MergeEntities(merge_entities::MergeEntitiesArgs),
164 /// Enrich graph memories and entities using an LLM provider
165 Enrich(Box<enrich::EnrichArgs>),
166 /// Reclassify relationship types across the graph using rules or LLM judgment
167 #[command(name = "reclassify-relation")]
168 ReclassifyRelation(reclassify_relation::ReclassifyRelationArgs),
169 /// Normalize entity names (deduplicate, kebab-case, merge near-duplicates)
170 #[command(name = "normalize-entities")]
171 NormalizeEntities(normalize_entities::NormalizeEntitiesArgs),
172 /// Generate shell completions for Bash, Zsh, Fish, PowerShell, or Elvish
173 Completions(completions::CompletionsArgs),
174 /// List every shipped JSON Schema, or emit one by id (`--name <ID>`)
175 #[command(after_long_help = "EXAMPLES:\n \
176 # Catalogue: one NDJSON record per contract\n \
177 sqlite-graphrag schema\n\n \
178 # One contract by id\n \
179 sqlite-graphrag schema --name recall\n\n\
180 NOTES:\n \
181 - Never opens the database and never requires an embedding API key.\n \
182 - The per-subcommand `--print-schema` flags keep working unchanged.")]
183 Schema(crate::print_schema::SchemaArgs),
184 /// `debug-schema` subcommand.
185 #[command(name = "debug-schema", hide = true)]
186 DebugSchema(debug_schema::DebugSchemaArgs),
187 /// Manage API keys and diagnose provider configuration (v1.0.93)
188 Config(config_cmd::ConfigArgs),
189}
190
191impl Commands {
192 /// Names the subcommand for [`crate::agent_surface`] alias suppression.
193 ///
194 /// The suppression table used to match on the KEY alone, so `results` meant
195 /// the same thing everywhere. It does not: in `recall`, `results` really is
196 /// the concatenation of `direct_matches` and `graph_matches`, so dropping
197 /// the halves loses nothing. In `hybrid-search` the two arrays are DISJOINT
198 /// by construction — the graph expansion skips every id already fused — and
199 /// they do not even hold the same type. Suppressing there deleted unique
200 /// rows and then labelled them redundant, which is worse than losing them
201 /// silently: the envelope asserted the removal was safe.
202 ///
203 /// `None` for every subcommand that declares no alias, which makes the
204 /// default fail-safe: a new command is never suppressed until someone adds
205 /// it to the table deliberately.
206 /// GAP-SG-274: `graph` reports TWO slugs, because it emits two shapes.
207 ///
208 /// The NDJSON snapshot emits one self-contained record per line, discriminated
209 /// by a `kind` field valued `node`, `edge` or `summary`; every other form of
210 /// `graph` emits a single envelope in which `kind` is instead the deprecated
211 /// alias of an entity's `type`. One slug for both made the vocabulary layer
212 /// blind to a distinction [`Self::streams`] was already computing three
213 /// methods away, which is why a field name meaning two different things had
214 /// to be excluded everywhere rather than scoped where it is unambiguous.
215 #[must_use]
216 pub fn agent_surface_slug(&self) -> Option<&'static str> {
217 match self {
218 Self::List(_) => Some("list"),
219 Self::Graph(args) => Some(if Self::is_graph_ndjson(args) {
220 "graph-ndjson"
221 } else {
222 "graph"
223 }),
224 Self::Recall(_) => Some("recall"),
225 Self::Related(_) => Some("related"),
226 _ => None,
227 }
228 }
229
230 /// `true` when this invocation is the NDJSON snapshot form of `graph`.
231 ///
232 /// GAP-SG-274. Both [`Self::streams`] and [`Self::agent_surface_slug`] need
233 /// this answer, and before it was named they disagreed: one computed it, the
234 /// other ignored the args entirely. Asking the question in one place is what
235 /// stops the two from drifting apart again — the same argument the
236 /// [`Self::persists`] conjunction records for its own pair.
237 fn is_graph_ndjson(args: &graph_export::GraphArgs) -> bool {
238 !args.json
239 && args.subcommand.is_none()
240 && args.format == crate::cli::GraphExportFormat::Ndjson
241 }
242
243 /// `true` when this subcommand can change durable state.
244 ///
245 /// GAP-SG-205 reads it to decide whether the target database may be
246 /// inherited from ambient configuration; [`crate::agent_surface::gate`]
247 /// reads it to decide whether a refusal is still safe.
248 ///
249 /// The refusal question is the sharper one. The agent-native surface runs at
250 /// OUTPUT time, after the handler has already done its work, so refusing
251 /// there would hand the caller a non-zero exit for an operation that
252 /// succeeded — and a caller that retries a succeeded `remember` writes the
253 /// memory twice. The gate therefore stays silent on anything this reports as
254 /// mutating.
255 ///
256 /// Read-only variants are listed EXPLICITLY and everything else answers
257 /// `true`. The default has to be the conservative one: a subcommand added
258 /// later and forgotten here loses a refusal it might have wanted, which
259 /// costs a diagnostic, while the opposite default would let the gate fire
260 /// after an unlisted write, which costs data.
261 pub fn mutates(&self) -> bool {
262 match self {
263 Self::Recall(_)
264 | Self::Read(_)
265 | Self::List(_)
266 | Self::History(_)
267 | Self::HybridSearch(_)
268 | Self::Health(_)
269 | Self::NamespaceDetect(_)
270 | Self::Stats(_)
271 | Self::DeepResearch(_)
272 | Self::Related(_)
273 | Self::Export(_)
274 | Self::MemoryEntities(_)
275 | Self::Schema(_)
276 | Self::DebugSchema(_)
277 | Self::Completions(_) => false,
278 // `graph` is read-only in four of its five forms; `recompute-degree`
279 // rewrites the cached degree column.
280 Self::Graph(args) => matches!(
281 args.subcommand,
282 Some(crate::commands::graph_export::GraphSubcommand::RecomputeDegree(_))
283 ),
284 _ => true,
285 }
286 }
287
288 /// `true` when this subcommand actually persists, so its envelope is a receipt.
289 ///
290 /// GAP-SG-206. Neither half answers this on its own. [`Self::mutates`] lists
291 /// the read-only variants explicitly and answers `true` for all the rest, so
292 /// it reports `true` for `config list-keys`, `embedding list` and `fts check`,
293 /// which write nothing — right default for a refusal fence, wrong one for
294 /// withholding an answer. [`Self::may_inherit_target`] classifies exactly
295 /// those split families at the subcommand level.
296 ///
297 /// The conjunction is not a new list: `Cli::install_write_policy` already
298 /// asks precisely this question to decide whether the target must be named
299 /// in the argv, which is the same question — "did something get written".
300 /// Naming it once is what stops a third hand-written copy from drifting away
301 /// from the other two.
302 ///
303 /// That hook is a plain code span rather than an intra-doc link because it is
304 /// private, and `rustdoc::private_intra_doc_links` — denied in `Cargo.toml`
305 /// — rejects a link from public documentation to an item the public
306 /// documentation does not contain. `tests/rustdoc_link_gate.rs` now catches
307 /// that class, which `cargo test` and `cargo clippy` are both blind to.
308 pub fn persists(&self) -> bool {
309 self.mutates() && !self.may_inherit_target()
310 }
311
312 /// `true` when this subcommand emits one self-contained record per line.
313 ///
314 /// GAP-SG-209. [`crate::agent_surface::gate`] reads it to refuse the knobs
315 /// that need a complete set, because the surface runs once per emitted
316 /// envelope and a stream has no complete set by construction. Measured:
317 /// `--count-only export --limit 10` answered with eleven `{"count":1}` lines.
318 ///
319 /// The property belongs to the SUBCOMMAND and not to the emitting function,
320 /// which is the distinction that makes this a list rather than a flag on
321 /// `emit_json_compact`. That function is also how `config path`, `slots
322 /// release` and `embedding list` emit ONE envelope; keying the refusal off it
323 /// would have rejected `--count-only config path`, which is perfectly
324 /// answerable.
325 ///
326 /// Streaming variants are listed EXPLICITLY and everything else answers
327 /// `false`. The conservative default is the opposite of [`Self::mutates`]
328 /// here, and deliberately so: a subcommand added later and forgotten keeps
329 /// exactly today's behaviour, while the opposite default would refuse flags
330 /// on a command that can honour them perfectly well.
331 ///
332 /// GAP-SG-229 added `graph --format ndjson`, which had been streaming since
333 /// v1.0.35 without ever answering `true` here. The consequence was worse than
334 /// a missing refusal: `render_ndjson_streaming` returns before the surface
335 /// layer runs, so `--select`, `--filter`, `--sort` and `--dedupe-by` were
336 /// ACCEPTED and then IGNORED, with no refusal and no warning — the exact
337 /// shape of "flag aceita e silenciosamente ignorada" this project catalogues.
338 ///
339 /// The format has to be read from `args`, and it can be: this predicate
340 /// matches on the parsed arguments exactly as [`Self::mutates`] already does
341 /// for `graph recompute-degree`. GAP-SG-274 gave
342 /// [`Self::agent_surface_slug`] the same reach through the shared
343 /// `is_graph_ndjson` helper, so the two no longer disagree about which shape
344 /// of `graph` is in front of them. That helper is a plain code span rather
345 /// than an intra-doc link because it is private, and
346 /// `rustdoc::private_intra_doc_links` — denied in `Cargo.toml` — rejects a
347 /// link from public documentation to an item the public documentation does
348 /// not contain, exactly as [`Self::persists`] records for its own hook. The
349 /// `--json` override is mirrored from
350 /// `graph_export::handlers`, where it promotes the format to `Json` and turns
351 /// the streaming path off entirely; forgetting it here would refuse
352 /// whole-set knobs on an invocation that emits a single envelope.
353 ///
354 /// `dot` and `mermaid` stay outside: they are rendered text, not JSON, so
355 /// there is no record for a knob to act on.
356 pub fn streams(&self) -> bool {
357 match self {
358 Self::Export(_) | Self::Ingest(_) => true,
359 Self::Graph(args) => Self::is_graph_ndjson(args),
360 _ => false,
361 }
362 }
363
364 /// Whether this subcommand may resolve its target from ambient configuration.
365 ///
366 /// GAP-SG-207. [`Self::mutates`] answers "does this change durable state";
367 /// this answers "is naming the target nonetheless optional for THIS
368 /// invocation". The two differ, and reusing `mutates` alone would have been
369 /// a defect: it lists the read-only variants explicitly and answers `true`
370 /// for everything else, which is the right conservative default for the
371 /// output-time refusal fence and the WRONG one here. For the fence a
372 /// mistaken `true` costs a diagnostic; here it would cost a false refusal on
373 /// a command that has no side effect to protect — `fts check`, `vec stats`,
374 /// `embedding list` and `embedding status` all read and write nothing.
375 ///
376 /// So the families whose subcommands split between reading and writing are
377 /// classified at the SUBCOMMAND level. The Explicit Target Designation rule
378 /// governs side effects, and a read inherits no authority it could misuse.
379 ///
380 /// Enforcement lives in [`crate::paths::AppPaths::resolve`]. That placement
381 /// keeps this list short: a subcommand that never resolves a database —
382 /// `config`, `completions`, `locale`, `slots`, `cache` — is exempt by
383 /// construction and needs no entry here at all.
384 pub fn may_inherit_target(&self) -> bool {
385 use crate::commands::embedding::EmbeddingCmd;
386 use crate::commands::fts::FtsSubcommand;
387 use crate::commands::pending_embeddings::PendingEmbeddingsCmd;
388 use crate::commands::vec::VecSubcommand;
389
390 match self {
391 // Creating the XDG database when no `--db` is given IS the command,
392 // so requiring the flag would invert `init` rather than protect it.
393 Self::Init(_) => true,
394 // Host leaves. GAP-SG-139 fixed these to accept `--db` as a no-op
395 // precisely because they touch no database — but they still call
396 // `AppPaths::resolve` to locate the MODELS directory, which shares
397 // that resolver. Without this arm the target policy fired on
398 // `cache list`, a command that reads a cache and nothing else.
399 Self::Config(_) | Self::Cache(_) | Self::Slots(_) | Self::Completions(_) => true,
400 Self::Fts(args) => matches!(
401 args.command,
402 FtsSubcommand::Check(_) | FtsSubcommand::Stats(_)
403 ),
404 Self::Vec(args) => matches!(
405 args.command,
406 VecSubcommand::OrphanList(_) | VecSubcommand::Stats(_)
407 ),
408 Self::Embedding(args) => {
409 matches!(args.cmd, EmbeddingCmd::List(_) | EmbeddingCmd::Status(_))
410 }
411 Self::PendingEmbeddings(args) => matches!(
412 args.cmd,
413 PendingEmbeddingsCmd::List(_) | PendingEmbeddingsCmd::Status(_)
414 ),
415 _ => false,
416 }
417 }
418
419 /// Returns true for subcommands that load the ONNX model locally.
420 pub fn is_embedding_heavy(&self) -> bool {
421 matches!(
422 self,
423 Self::Init(_)
424 | Self::Remember(_)
425 | Self::RememberBatch(_)
426 | Self::Recall(_)
427 | Self::HybridSearch(_)
428 | Self::DeepResearch(_)
429 )
430 }
431
432 /// Return whether this command occupies a CLI concurrency slot.
433 pub fn uses_cli_slot(&self) -> bool {
434 true
435 }
436
437 /// Read-only / no-embedding subcommands that MUST run without an embedding
438 /// API key. `init` warms a best-effort smoke test internally and degrades to
439 /// `ok_no_embedding` when the backend is unreachable; the `enrich` queue
440 /// inspectors (`--status` / `--list-dead` / `--requeue-dead` /
441 /// `--prune-dead-orphans`) never embed and never call the LLM. The eager
442 /// OpenRouter key preflight in `main` must skip its hard-fail for these.
443 pub fn tolerates_missing_embedding_key(&self) -> bool {
444 match self {
445 Self::Init(_) => true,
446 // `schema` only writes embedded documents: no database, no key.
447 Self::Schema(_) => true,
448 // The host leaves must never be gated by embedding configuration:
449 // they are the only way to REPAIR that configuration. Registering
450 // `embedding.backend` (v1.2.5, GAP-SG-198) made the omission
451 // load-bearing — `config set embedding.backend openrouter` with no
452 // model stored started failing every later invocation at the
453 // preflight, `config unset` included, leaving hand-editing the TOML
454 // as the only exit. `cache`, `slots` and `completions` never embed
455 // either, so the same reasoning covers them.
456 Self::Config(_) | Self::Cache(_) | Self::Slots(_) | Self::Completions(_) => true,
457 Self::Enrich(args) => {
458 args.status
459 || args.list_dead
460 || args.requeue_dead
461 || args.list_skipped
462 || args.requeue_skipped
463 || args.prune_dead_orphans
464 || args.prune_dead_entity_orphans
465 || args.print_schema
466 }
467 _ => false,
468 }
469 }
470}
471
472// FIX-1 (v1.0.89): manual `Debug` impl so test panic messages that print
473// `{:?}` on a captured `Commands` variant compile without requiring every
474// contained subcommand arg struct to derive `Debug`. The Debug output is
475// only used in test assertions for diagnostic messages; we emit the variant
476// name only — arg payload is intentionally omitted.
477impl std::fmt::Debug for Commands {
478 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
479 let name = match self {
480 Self::Init(_) => "Init",
481 Self::Health(_) => "Health",
482 Self::Stats(_) => "Stats",
483 Self::List(_) => "List",
484 Self::Read(_) => "Read",
485 Self::Edit(_) => "Edit",
486 Self::Rename(_) => "Rename",
487 Self::SplitBody(_) => "SplitBody",
488 Self::Restore(_) => "Restore",
489 Self::History(_) => "History",
490 Self::Forget(_) => "Forget",
491 Self::Purge(_) => "Purge",
492 Self::Remember(_) => "Remember",
493 Self::RememberBatch(_) => "RememberBatch",
494 Self::Recall(_) => "Recall",
495 Self::HybridSearch(_) => "HybridSearch",
496 Self::Enrich(_) => "Enrich",
497 Self::Ingest(_) => "Ingest",
498 Self::Optimize(_) => "Optimize",
499 Self::Migrate(_) => "Migrate",
500 Self::SyncSafeCopy(_) => "SyncSafeCopy",
501 Self::Backup(_) => "Backup",
502 Self::Vacuum(_) => "Vacuum",
503 Self::Link(_) => "Link",
504 Self::Unlink(_) => "Unlink",
505 Self::DeepResearch(_) => "DeepResearch",
506 Self::Related(_) => "Related",
507 Self::Graph(_) => "Graph",
508 Self::Export(_) => "Export",
509 Self::Fts(_) => "Fts",
510 Self::Vec(_) => "Vec",
511 Self::PruneRelations(_) => "PruneRelations",
512 Self::PruneNer(_) => "PruneNer",
513 Self::Slots(_) => "Slots",
514 Self::Embedding(_) => "Embedding",
515 Self::PendingEmbeddings(_) => "PendingEmbeddings",
516 Self::CleanupOrphans(_) => "CleanupOrphans",
517 Self::MemoryEntities(_) => "MemoryEntities",
518 Self::Cache(_) => "Cache",
519 Self::DeleteEntity(_) => "DeleteEntity",
520 Self::Reclassify(_) => "Reclassify",
521 Self::RenameEntity(_) => "RenameEntity",
522 Self::ReclassifyRelation(_) => "ReclassifyRelation",
523 Self::NormalizeEntities(_) => "NormalizeEntities",
524 Self::MergeEntities(_) => "MergeEntities",
525 Self::NamespaceDetect(_) => "NamespaceDetect",
526 Self::Completions(_) => "Completions",
527 Self::Schema(_) => "Schema",
528 Self::DebugSchema(_) => "DebugSchema",
529 Self::Config(_) => "Config",
530 };
531 f.write_str(name)
532 }
533}