Skip to main content

sqlite_graphrag/commands/graph_export/
args.rs

1//! CLI argument types for `graph` subcommands.
2
3use crate::cli::GraphExportFormat;
4use serde::Serialize;
5use std::path::PathBuf;
6
7/// Optional nested subcommands. When absent, the default behavior exports
8/// the full entity snapshot for backward compatibility.
9#[derive(clap::Subcommand)]
10pub enum GraphSubcommand {
11    /// Traverse relationships from a starting entity using BFS
12    Traverse(GraphTraverseArgs),
13    /// Show graph statistics (node/edge counts, degree distribution)
14    Stats(GraphStatsArgs),
15    /// List entities stored in the graph with optional filters
16    Entities(GraphEntitiesArgs),
17    /// Audit the entity-type vocabulary actually present in the database
18    EntityTypes(GraphEntityTypesArgs),
19    /// Reconcile the cached `degree` column with the real edge counts (P3)
20    RecomputeDegree(GraphRecomputeDegreeArgs),
21}
22
23/// Graph traverse format.
24#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
25pub enum GraphTraverseFormat {
26    /// JSON variant.
27    Json,
28}
29
30/// Graph stats format.
31#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
32pub enum GraphStatsFormat {
33    /// JSON variant.
34    Json,
35    /// Text variant.
36    Text,
37}
38
39#[derive(clap::Args)]
40#[command(after_long_help = "EXAMPLES:\n  \
41    # Export full entity snapshot as JSON (default)\n  \
42    sqlite-graphrag graph\n\n  \
43    # Traverse relationships from a starting entity\n  \
44    sqlite-graphrag graph traverse --from acme-corp --depth 2\n\n  \
45    # Show graph statistics as structured JSON\n  \
46    sqlite-graphrag graph stats --format json\n\n  \
47    # List entities filtered by type\n  \
48    sqlite-graphrag graph entities --entity-type person\n\n  \
49    # Export full snapshot in DOT format for Graphviz\n  \
50    sqlite-graphrag graph --format dot --output graph.dot\n\n  \
51NOTES:\n  \
52    Without a subcommand, exports the full entity+edge snapshot.\n  \
53    Use `traverse`, `stats`, or `entities` for targeted queries.")]
54/// Graph args.
55pub struct GraphArgs {
56    /// Optional subcommand; without one, export the full entity snapshot.
57    #[command(subcommand)]
58    pub subcommand: Option<GraphSubcommand>,
59    /// Filter by namespace. Defaults to all namespaces.
60    #[arg(long)]
61    pub namespace: Option<String>,
62    /// Snapshot output format.
63    #[arg(long, value_enum, default_value = "json")]
64    pub format: GraphExportFormat,
65    /// File path to write output instead of stdout.
66    #[arg(long)]
67    pub output: Option<PathBuf>,
68    /// Emit machine-readable JSON on stdout.
69    #[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
70    pub json: bool,
71    /// Path to the SQLite database file.
72    #[arg(long)]
73    pub db: Option<String>,
74}
75
76#[derive(clap::Args)]
77#[command(after_long_help = "EXAMPLES:\n  \
78    # Traverse relationships from an entity with default depth (2)\n  \
79    sqlite-graphrag graph traverse --from acme-corp\n\n  \
80    # Increase traversal depth to 3 hops\n  \
81    sqlite-graphrag graph traverse --from acme-corp --depth 3\n\n  \
82    # Traverse within a specific namespace\n  \
83    sqlite-graphrag graph traverse --from acme-corp --namespace project-x\n\n  \
84NOTES:\n  \
85    Output is always JSON. The `hops` array contains each reachable entity\n  \
86    with its relation, direction (inbound/outbound), weight, and depth level.\n  \
87    Short nicknames (e.g. `alice` vs `alice-martins-souza`) do not exact-match;\n  \
88    without `--fuzzy` the error includes ranked suggestions (v1.1.05). With\n  \
89    `--fuzzy`, a clear single winner is auto-resolved and warned on stderr.")]
90/// Graph traverse args.
91pub struct GraphTraverseArgs {
92    /// Root entity name for the traversal.
93    #[arg(long)]
94    pub from: String,
95    /// Maximum traversal depth.
96    #[arg(long, default_value_t = 2u32, value_parser = crate::parsers::parse_hops_range_u32)]
97    pub depth: u32,
98    /// When exact name match fails, auto-resolve a clear fuzzy match
99    /// (prefix / first-token / Jaro-Winkler). Without this flag, NotFound
100    /// (exit 4) includes ranked suggestions of canonical names (v1.1.05 Bug 3).
101    #[arg(long, default_value_t = false)]
102    pub fuzzy: bool,
103    /// Namespace scope.
104    #[arg(long)]
105    pub namespace: Option<String>,
106    /// Output format.
107    #[arg(long, value_enum, default_value = "json")]
108    pub format: GraphTraverseFormat,
109    /// Emit machine-readable JSON on stdout.
110    #[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
111    pub json: bool,
112    /// Path to the SQLite database file.
113    #[arg(long)]
114    pub db: Option<String>,
115}
116
117#[derive(clap::Args)]
118#[command(after_long_help = "EXAMPLES:\n  \
119    # Show stats for all namespaces (human-readable text)\n  \
120    sqlite-graphrag graph stats --format text\n\n  \
121    # Show stats as structured JSON\n  \
122    sqlite-graphrag graph stats --format json\n\n  \
123    # Show stats for a specific namespace\n  \
124    sqlite-graphrag graph stats --namespace project-x --format text\n\n  \
125NOTES:\n  \
126    Reports node_count, edge_count, avg_degree, and max_degree.\n  \
127    Default format is JSON. Use `--format text` for a compact single-line summary.")]
128/// Graph stats args.
129pub struct GraphStatsArgs {
130    /// Namespace scope.
131    #[arg(long)]
132    pub namespace: Option<String>,
133    /// Output format for the stats response.
134    #[arg(long, value_enum, default_value = "json")]
135    pub format: GraphStatsFormat,
136    /// Emit machine-readable JSON on stdout.
137    #[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
138    pub json: bool,
139    /// Path to the SQLite database file.
140    #[arg(long)]
141    pub db: Option<String>,
142}
143
144/// Graph entity-types format.
145#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
146pub enum GraphEntityTypesFormat {
147    /// JSON variant.
148    Json,
149    /// Text variant.
150    Text,
151}
152
153#[derive(clap::Args)]
154#[command(after_long_help = "EXAMPLES:\n  \
155    # List every entity type present, most frequent first\n  \
156    sqlite-graphrag graph entity-types\n\n  \
157    # Restrict the audit to one namespace\n  \
158    sqlite-graphrag graph entity-types --namespace project-x\n\n  \
159    # Compact human-readable summary\n  \
160    sqlite-graphrag graph entity-types --format text\n\n  \
161    # Only the labels outside the canonical set\n  \
162    sqlite-graphrag graph entity-types --filter canonical=false\n\n\
163NOTES:\n  \
164    v1.2.8 opened the entity-type vocabulary, so the set of stored labels is\n  \
165    no longer knowable from the source. This reports what the database\n  \
166    actually holds: each `type` with its `count` and whether it belongs to\n  \
167    the canonical thirteen. `--entity-type` on `graph entities` can only\n  \
168    filter by a label you already guessed; this is where you find the label.")]
169/// Graph entity types args.
170pub struct GraphEntityTypesArgs {
171    /// Namespace scope. Omit to audit ALL namespaces.
172    #[arg(long)]
173    pub namespace: Option<String>,
174    /// Output format for the vocabulary report.
175    #[arg(long, value_enum, default_value = "json")]
176    pub format: GraphEntityTypesFormat,
177    /// Emit machine-readable JSON on stdout.
178    #[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
179    pub json: bool,
180    /// Path to the SQLite database file.
181    #[arg(long)]
182    pub db: Option<String>,
183}
184
185/// One row of the entity-type vocabulary audit.
186#[derive(Serialize)]
187pub(crate) struct EntityTypeCount {
188    /// The label exactly as stored, after V017 removed the SQL `CHECK`.
189    #[serde(rename = "type")]
190    pub(crate) entity_type: String,
191    /// Entities carrying this label within the requested scope.
192    pub(crate) count: i64,
193    /// Whether the label is one of `CANONICAL_ENTITY_TYPES`. False is a normal
194    /// result, not a defect: the vocabulary is open by design.
195    pub(crate) canonical: bool,
196}
197
198#[derive(Serialize)]
199pub(crate) struct GraphEntityTypesResponse {
200    pub(crate) types: Vec<EntityTypeCount>,
201    /// Distinct labels found, which is `types.len()` before any agent-surface
202    /// trimming and therefore survives `--max-items`.
203    pub(crate) total_types: usize,
204    /// Entities summed across every label in scope.
205    pub(crate) total_entities: i64,
206    pub(crate) namespace: Option<String>,
207    pub(crate) elapsed_ms: u64,
208}
209
210/// Field to sort entities by in `graph entities`.
211#[derive(Debug, Clone, Copy, clap::ValueEnum)]
212pub enum EntitySortField {
213    /// Sort alphabetically by entity name.
214    Name,
215    /// Sort by degree (total number of relationships). Use `--order desc` for most-connected-first.
216    Degree,
217    /// Sort by entity creation timestamp.
218    CreatedAt,
219}
220
221/// Sort direction for `graph entities`.
222#[derive(Debug, Clone, Copy, Default, clap::ValueEnum)]
223pub enum SortOrder {
224    /// Asc variant.
225    #[default]
226    Asc,
227    /// Desc variant.
228    Desc,
229}
230
231#[derive(clap::Args)]
232#[command(after_long_help = "EXAMPLES:\n  \
233    # List all entities (default limit applies)\n  \
234    sqlite-graphrag graph entities\n\n  \
235    # Filter by entity type\n  \
236    sqlite-graphrag graph entities --entity-type person\n\n  \
237    # Filter by namespace and type\n  \
238    sqlite-graphrag graph entities --namespace project-x --entity-type concept\n\n  \
239    # Paginate results (skip first 20, return next 10)\n  \
240    sqlite-graphrag graph entities --offset 20 --limit 10\n\n  \
241    # Sort by degree descending (most connected first)\n  \
242    sqlite-graphrag graph entities --sort-by degree --order desc\n\n  \
243    # Sort by creation date ascending\n  \
244    sqlite-graphrag graph entities --sort-by created-at --order asc\n\n  \
245NOTES:\n  \
246    Output is always JSON with `entities`, `total_count`, `limit`, and `offset` fields.\n  \
247    Entity types are free-form strings; the canonical ones (e.g. `person`,\n  \
248    `organization`, `location`) are recommended rather than exhaustive.")]
249/// Graph entities args.
250pub struct GraphEntitiesArgs {
251    /// Namespace scope.
252    #[arg(long)]
253    pub namespace: Option<String>,
254    /// Filter by entity type. Any stored label is accepted (v1.2.8); the
255    /// thirteen canonical types are recommended, not exhaustive. A label no
256    /// entity carries returns an empty list, which is a result and not an
257    /// error.
258    #[arg(long, value_name = "TYPE")]
259    pub entity_type: Option<String>,
260    /// Maximum number of results to return.
261    #[arg(long, default_value_t = crate::constants::K_GRAPH_ENTITIES_DEFAULT_LIMIT, value_parser = crate::parsers::parse_k_range)]
262    pub limit: usize,
263    /// Number of results to skip for pagination.
264    #[arg(long, default_value_t = 0usize)]
265    pub offset: usize,
266    /// Sort entities by this field. When omitted, the default order is by name ascending.
267    #[arg(long, value_enum, help = "Sort entities by field")]
268    pub sort_by: Option<EntitySortField>,
269    /// Sort direction: `asc` (default) or `desc`.
270    #[arg(long, value_enum, default_value_t = SortOrder::Asc, help = "Sort order")]
271    pub order: SortOrder,
272    /// Emit machine-readable JSON on stdout.
273    #[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
274    pub json: bool,
275    /// Path to the SQLite database file.
276    #[arg(long)]
277    pub db: Option<String>,
278}
279
280#[derive(clap::Args)]
281#[command(after_long_help = "EXAMPLES:\n  \
282    # Preview divergences without writing (recommended first run)\n  \
283    sqlite-graphrag graph recompute-degree --dry-run\n\n  \
284    # Reconcile every namespace\n  \
285    sqlite-graphrag graph recompute-degree\n\n  \
286    # Reconcile a single namespace\n  \
287    sqlite-graphrag graph recompute-degree --namespace project-x\n\n\
288NOTES:\n  \
289    `entities.degree` is a derived cache (incremented on link, recalculated\n  \
290    on merge/delete) that drifts when edges are written by paths that skip\n  \
291    the recalculation. This command recomputes every entity's degree from\n  \
292    the real `relationships` rows (same semantics as the canonical\n  \
293    `recalculate_degree` helper: COUNT(*) WHERE source_id = id OR\n  \
294    target_id = id) inside one transaction. Entities left with zero edges\n  \
295    are zeroed. Envelope: {total, updated, zeroed, unchanged}.")]
296/// Graph recompute degree args.
297pub struct GraphRecomputeDegreeArgs {
298    /// Namespace to reconcile. Omit to reconcile ALL namespaces.
299    #[arg(long)]
300    pub namespace: Option<String>,
301    /// Report divergences without writing anything.
302    #[arg(long)]
303    pub dry_run: bool,
304    /// Emit machine-readable JSON on stdout.
305    #[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
306    pub json: bool,
307    /// Path to the SQLite database file.
308    #[arg(long)]
309    pub db: Option<String>,
310}
311
312#[derive(Serialize)]
313pub(crate) struct TraverseHop {
314    pub(crate) entity: String,
315    pub(crate) relation: String,
316    pub(crate) direction: String,
317    pub(crate) weight: f64,
318    pub(crate) depth: u32,
319}
320
321#[derive(Serialize)]
322pub(crate) struct GraphTraverseResponse {
323    pub(crate) from: String,
324    pub(crate) namespace: String,
325    pub(crate) depth: u32,
326    pub(crate) hops: Vec<TraverseHop>,
327    pub(crate) elapsed_ms: u64,
328}
329
330#[derive(Serialize)]
331pub(crate) struct GraphStatsResponse {
332    pub(crate) namespace: Option<String>,
333    pub(crate) node_count: i64,
334    pub(crate) edge_count: i64,
335    pub(crate) avg_degree: f64,
336    pub(crate) max_degree: i64,
337    pub(crate) elapsed_ms: u64,
338}
339
340#[derive(Serialize)]
341pub(crate) struct EntityItem {
342    pub(crate) id: i64,
343    pub(crate) name: String,
344    pub(crate) entity_type: String,
345    pub(crate) namespace: String,
346    pub(crate) created_at: String,
347    /// Total number of relationships (inbound + outbound) for this entity.
348    pub(crate) degree: u32,
349    #[serde(skip_serializing_if = "Option::is_none")]
350    pub(crate) description: Option<String>,
351}
352
353#[derive(Serialize)]
354pub(crate) struct GraphEntitiesResponse {
355    pub(crate) entities: Vec<EntityItem>,
356    pub(crate) total_count: i64,
357    pub(crate) limit: usize,
358    pub(crate) offset: usize,
359    pub(crate) namespace: Option<String>,
360    pub(crate) elapsed_ms: u64,
361}