Skip to main content

sqlite_graphrag/commands/graph_export/
args.rs

1//! CLI argument types for `graph` subcommands.
2
3use crate::cli::GraphExportFormat;
4use crate::entity_type::EntityType;
5use serde::Serialize;
6use std::path::PathBuf;
7
8/// Optional nested subcommands. When absent, the default behavior exports
9/// the full entity snapshot for backward compatibility.
10#[derive(clap::Subcommand)]
11pub enum GraphSubcommand {
12    /// Traverse relationships from a starting entity using BFS
13    Traverse(GraphTraverseArgs),
14    /// Show graph statistics (node/edge counts, degree distribution)
15    Stats(GraphStatsArgs),
16    /// List entities stored in the graph with optional filters
17    Entities(GraphEntitiesArgs),
18    /// Reconcile the cached `degree` column with the real edge counts (P3)
19    RecomputeDegree(GraphRecomputeDegreeArgs),
20}
21
22/// Graph traverse format.
23#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
24pub enum GraphTraverseFormat {
25    /// JSON variant.
26    Json,
27}
28
29/// Graph stats format.
30#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
31pub enum GraphStatsFormat {
32    /// JSON variant.
33    Json,
34    /// Text variant.
35    Text,
36}
37
38#[derive(clap::Args)]
39#[command(after_long_help = "EXAMPLES:\n  \
40    # Export full entity snapshot as JSON (default)\n  \
41    sqlite-graphrag graph\n\n  \
42    # Traverse relationships from a starting entity\n  \
43    sqlite-graphrag graph traverse --from acme-corp --depth 2\n\n  \
44    # Show graph statistics as structured JSON\n  \
45    sqlite-graphrag graph stats --format json\n\n  \
46    # List entities filtered by type\n  \
47    sqlite-graphrag graph entities --entity-type person\n\n  \
48    # Export full snapshot in DOT format for Graphviz\n  \
49    sqlite-graphrag graph --format dot --output graph.dot\n\n  \
50NOTES:\n  \
51    Without a subcommand, exports the full entity+edge snapshot.\n  \
52    Use `traverse`, `stats`, or `entities` for targeted queries.")]
53/// Graph args.
54pub struct GraphArgs {
55    /// Optional subcommand; without one, export the full entity snapshot.
56    #[command(subcommand)]
57    pub subcommand: Option<GraphSubcommand>,
58    /// Filter by namespace. Defaults to all namespaces.
59    #[arg(long)]
60    pub namespace: Option<String>,
61    /// Snapshot output format.
62    #[arg(long, value_enum, default_value = "json")]
63    pub format: GraphExportFormat,
64    /// File path to write output instead of stdout.
65    #[arg(long)]
66    pub output: Option<PathBuf>,
67    /// Emit machine-readable JSON on stdout.
68    #[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
69    pub json: bool,
70    /// Path to the SQLite database file.
71    #[arg(long)]
72    pub db: Option<String>,
73}
74
75#[derive(clap::Args)]
76#[command(after_long_help = "EXAMPLES:\n  \
77    # Traverse relationships from an entity with default depth (2)\n  \
78    sqlite-graphrag graph traverse --from acme-corp\n\n  \
79    # Increase traversal depth to 3 hops\n  \
80    sqlite-graphrag graph traverse --from acme-corp --depth 3\n\n  \
81    # Traverse within a specific namespace\n  \
82    sqlite-graphrag graph traverse --from acme-corp --namespace project-x\n\n  \
83NOTES:\n  \
84    Output is always JSON. The `hops` array contains each reachable entity\n  \
85    with its relation, direction (inbound/outbound), weight, and depth level.\n  \
86    Short nicknames (e.g. `danilo` vs `danilo-aguiar-teixeira`) do not exact-match;\n  \
87    without `--fuzzy` the error includes ranked suggestions (v1.1.05). With\n  \
88    `--fuzzy`, a clear single winner is auto-resolved and warned on stderr.")]
89/// Graph traverse args.
90pub struct GraphTraverseArgs {
91    /// Root entity name for the traversal.
92    #[arg(long)]
93    pub from: String,
94    /// Maximum traversal depth.
95    #[arg(long, default_value_t = 2u32)]
96    pub depth: u32,
97    /// When exact name match fails, auto-resolve a clear fuzzy match
98    /// (prefix / first-token / Jaro-Winkler). Without this flag, NotFound
99    /// (exit 4) includes ranked suggestions of canonical names (v1.1.05 Bug 3).
100    #[arg(long, default_value_t = false)]
101    pub fuzzy: bool,
102    /// Namespace scope.
103    #[arg(long)]
104    pub namespace: Option<String>,
105    /// Output format.
106    #[arg(long, value_enum, default_value = "json")]
107    pub format: GraphTraverseFormat,
108    /// Emit machine-readable JSON on stdout.
109    #[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
110    pub json: bool,
111    /// Path to the SQLite database file.
112    #[arg(long)]
113    pub db: Option<String>,
114}
115
116#[derive(clap::Args)]
117#[command(after_long_help = "EXAMPLES:\n  \
118    # Show stats for all namespaces (human-readable text)\n  \
119    sqlite-graphrag graph stats --format text\n\n  \
120    # Show stats as structured JSON\n  \
121    sqlite-graphrag graph stats --format json\n\n  \
122    # Show stats for a specific namespace\n  \
123    sqlite-graphrag graph stats --namespace project-x --format text\n\n  \
124NOTES:\n  \
125    Reports node_count, edge_count, avg_degree, and max_degree.\n  \
126    Default format is JSON. Use `--format text` for a compact single-line summary.")]
127/// Graph stats args.
128pub struct GraphStatsArgs {
129    /// Namespace scope.
130    #[arg(long)]
131    pub namespace: Option<String>,
132    /// Output format for the stats response.
133    #[arg(long, value_enum, default_value = "json")]
134    pub format: GraphStatsFormat,
135    /// Emit machine-readable JSON on stdout.
136    #[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
137    pub json: bool,
138    /// Path to the SQLite database file.
139    #[arg(long)]
140    pub db: Option<String>,
141}
142
143/// Field to sort entities by in `graph entities`.
144#[derive(Debug, Clone, Copy, clap::ValueEnum)]
145pub enum EntitySortField {
146    /// Sort alphabetically by entity name.
147    Name,
148    /// Sort by degree (total number of relationships). Use `--order desc` for most-connected-first.
149    Degree,
150    /// Sort by entity creation timestamp.
151    CreatedAt,
152}
153
154/// Sort direction for `graph entities`.
155#[derive(Debug, Clone, Copy, Default, clap::ValueEnum)]
156pub enum SortOrder {
157    /// Asc variant.
158    #[default]
159    Asc,
160    /// Desc variant.
161    Desc,
162}
163
164#[derive(clap::Args)]
165#[command(after_long_help = "EXAMPLES:\n  \
166    # List all entities (default limit applies)\n  \
167    sqlite-graphrag graph entities\n\n  \
168    # Filter by entity type\n  \
169    sqlite-graphrag graph entities --entity-type person\n\n  \
170    # Filter by namespace and type\n  \
171    sqlite-graphrag graph entities --namespace project-x --entity-type concept\n\n  \
172    # Paginate results (skip first 20, return next 10)\n  \
173    sqlite-graphrag graph entities --offset 20 --limit 10\n\n  \
174    # Sort by degree descending (most connected first)\n  \
175    sqlite-graphrag graph entities --sort-by degree --order desc\n\n  \
176    # Sort by creation date ascending\n  \
177    sqlite-graphrag graph entities --sort-by created-at --order asc\n\n  \
178NOTES:\n  \
179    Output is always JSON with `entities`, `total_count`, `limit`, and `offset` fields.\n  \
180    Entity types are canonical strings (e.g. `person`, `organization`, `location`).")]
181/// Graph entities args.
182pub struct GraphEntitiesArgs {
183    /// Namespace scope.
184    #[arg(long)]
185    pub namespace: Option<String>,
186    /// Filter by entity type (one of the 13 canonical types).
187    #[arg(long, value_enum)]
188    pub entity_type: Option<EntityType>,
189    /// Maximum number of results to return.
190    #[arg(long, default_value_t = crate::constants::K_GRAPH_ENTITIES_DEFAULT_LIMIT)]
191    pub limit: usize,
192    /// Number of results to skip for pagination.
193    #[arg(long, default_value_t = 0usize)]
194    pub offset: usize,
195    /// Sort entities by this field. When omitted, the default order is by name ascending.
196    #[arg(long, value_enum, help = "Sort entities by field")]
197    pub sort_by: Option<EntitySortField>,
198    /// Sort direction: `asc` (default) or `desc`.
199    #[arg(long, value_enum, default_value_t = SortOrder::Asc, help = "Sort order")]
200    pub order: SortOrder,
201    /// Emit machine-readable JSON on stdout.
202    #[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
203    pub json: bool,
204    /// Path to the SQLite database file.
205    #[arg(long)]
206    pub db: Option<String>,
207}
208
209#[derive(clap::Args)]
210#[command(after_long_help = "EXAMPLES:\n  \
211    # Preview divergences without writing (recommended first run)\n  \
212    sqlite-graphrag graph recompute-degree --dry-run\n\n  \
213    # Reconcile every namespace\n  \
214    sqlite-graphrag graph recompute-degree\n\n  \
215    # Reconcile a single namespace\n  \
216    sqlite-graphrag graph recompute-degree --namespace project-x\n\n\
217NOTES:\n  \
218    `entities.degree` is a derived cache (incremented on link, recalculated\n  \
219    on merge/delete) that drifts when edges are written by paths that skip\n  \
220    the recalculation. This command recomputes every entity's degree from\n  \
221    the real `relationships` rows (same semantics as the canonical\n  \
222    `recalculate_degree` helper: COUNT(*) WHERE source_id = id OR\n  \
223    target_id = id) inside one transaction. Entities left with zero edges\n  \
224    are zeroed. Envelope: {total, updated, zeroed, unchanged}.")]
225/// Graph recompute degree args.
226pub struct GraphRecomputeDegreeArgs {
227    /// Namespace to reconcile. Omit to reconcile ALL namespaces.
228    #[arg(long)]
229    pub namespace: Option<String>,
230    /// Report divergences without writing anything.
231    #[arg(long)]
232    pub dry_run: bool,
233    /// Emit machine-readable JSON on stdout.
234    #[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
235    pub json: bool,
236    /// Path to the SQLite database file.
237    #[arg(long)]
238    pub db: Option<String>,
239}
240
241
242#[derive(Serialize)]
243pub(crate) struct TraverseHop {
244    pub(crate) entity: String,
245    pub(crate) relation: String,
246    pub(crate) direction: String,
247    pub(crate) weight: f64,
248    pub(crate) depth: u32,
249}
250
251#[derive(Serialize)]
252pub(crate) struct GraphTraverseResponse {
253    pub(crate) from: String,
254    pub(crate) namespace: String,
255    pub(crate) depth: u32,
256    pub(crate) hops: Vec<TraverseHop>,
257    pub(crate) elapsed_ms: u64,
258}
259
260#[derive(Serialize)]
261pub(crate) struct GraphStatsResponse {
262    pub(crate) namespace: Option<String>,
263    pub(crate) node_count: i64,
264    pub(crate) edge_count: i64,
265    pub(crate) avg_degree: f64,
266    pub(crate) max_degree: i64,
267    pub(crate) elapsed_ms: u64,
268}
269
270#[derive(Serialize)]
271pub(crate) struct EntityItem {
272    pub(crate) id: i64,
273    pub(crate) name: String,
274    pub(crate) entity_type: String,
275    pub(crate) namespace: String,
276    pub(crate) created_at: String,
277    /// Total number of relationships (inbound + outbound) for this entity.
278    pub(crate) degree: u32,
279    #[serde(skip_serializing_if = "Option::is_none")]
280    pub(crate) description: Option<String>,
281}
282
283#[derive(Serialize)]
284pub(crate) struct GraphEntitiesResponse {
285    pub(crate) entities: Vec<EntityItem>,
286    pub(crate) total_count: i64,
287    pub(crate) limit: usize,
288    pub(crate) offset: usize,
289    pub(crate) namespace: Option<String>,
290    pub(crate) elapsed_ms: u64,
291}