sqlite_graphrag/commands/ingest/
args.rs1use crate::cli::MemoryType;
4use crate::output::JsonOutputFormat;
5use std::path::PathBuf;
6
7#[derive(clap::Args)]
8#[command(after_long_help = "EXAMPLES:\n \
9 # Ingest every Markdown file under ./docs as `document` memories\n \
10 sqlite-graphrag ingest ./docs --type document\n\n \
11 # Ingest .txt files recursively under ./notes\n \
12 sqlite-graphrag ingest ./notes --type note --pattern '*.txt' --recursive\n\n \
13 # Namespace derived names with a kebab-case prefix (projx-<derived>)\n \
14 sqlite-graphrag ingest ./docs --name-prefix projx- --dry-run\n\n \
15 # Enable automatic URL extraction (URL-regex only since v1.0.79)\n \
16 sqlite-graphrag ingest ./big-corpus --type reference --enable-ner\n\n \
17 # Preview file-to-name mapping without ingesting\n \
18 sqlite-graphrag ingest ./docs --dry-run\n\n \
19 # LLM-curated extraction via Claude Code CLI\n \
20 sqlite-graphrag ingest ./docs --mode claude-code --recursive --json\n\n \
21 # Resume interrupted claude-code ingest\n \
22 sqlite-graphrag ingest ./docs --mode claude-code --resume --json\n\n \
23 # Claude Code with budget cap and custom timeout\n \
24 sqlite-graphrag ingest ./docs --mode claude-code --max-cost-usd 5.00 --claude-timeout 600 --json\n\n \
25AUTHENTICATION:\n \
26 --mode claude-code: Uses existing Claude Code authentication.\n \
27 OAuth (Pro/Max/Team): works automatically from ~/.claude/.credentials.json\n \
28 API key: set ANTHROPIC_API_KEY for faster startup (optional)\n\n \
29 --mode codex: Uses existing Codex CLI authentication.\n \
30 Device auth: run `codex auth login` first\n \
31 API key: set OPENAI_API_KEY (optional)\n\n \
32NOTES:\n \
33 Each file becomes a separate memory. Names derive from file basenames\n \
34 (kebab-case, lowercase, ASCII). Output is NDJSON: one JSON object per file,\n \
35 followed by a final summary line with counts. Per-file errors are reported\n \
36 inline and processing continues unless --fail-fast is set.")]
37pub struct IngestArgs {
39 #[arg(
41 value_name = "DIR",
42 help = "Directory to ingest recursively (each matching file becomes a memory)"
43 )]
44 pub dir: PathBuf,
45
46 #[arg(long, value_enum, default_value_t = MemoryType::Document)]
48 pub r#type: MemoryType,
49
50 #[arg(long, default_value = "*.md")]
53 pub pattern: String,
54
55 #[arg(long, default_value_t = false)]
57 pub recursive: bool,
58
59 #[arg(
60 long,
61 value_parser = crate::parsers::parse_bool_flexible,
62 action = clap::ArgAction::Set,
63 num_args = 0..=1,
64 default_missing_value = "true",
65 default_value = "false",
66 help = "Enable automatic URL-regex extraction (URL-regex only since v1.0.79)"
67 )]
68 pub enable_ner: bool,
70
71 #[arg(
75 long,
76 default_value_t = true,
77 overrides_with = "no_auto_describe",
78 help = "Derive memory description from the first meaningful body line instead of the legacy `ingested from <path>` placeholder."
79 )]
80 pub auto_describe: bool,
81 #[arg(
82 long = "no-auto-describe",
83 default_value_t = false,
84 help = "Disable `--auto-describe` and fall back to the legacy `ingested from <path>` description placeholder."
85 )]
86 pub no_auto_describe: bool,
88
89 #[arg(long, default_value_t = false, hide = true)]
91 pub skip_extraction: bool,
92
93 #[arg(long, default_value_t = false)]
95 pub fail_fast: bool,
96
97 #[arg(long, default_value_t = false)]
99 pub dry_run: bool,
100
101 #[arg(long, default_value_t = 10_000)]
103 pub max_files: usize,
104
105 #[arg(long)]
107 pub namespace: Option<String>,
108
109 #[arg(long)]
112 pub db: Option<String>,
113
114 #[arg(long, value_enum, default_value_t = JsonOutputFormat::Json)]
116 pub format: JsonOutputFormat,
117
118 #[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
120 pub json: bool,
121
122 #[arg(
124 long,
125 help = "Number of files to extract+embed in parallel; default = max(1, cpus/2).min(4)"
126 )]
127 pub ingest_parallelism: Option<usize>,
128
129 #[arg(
137 long,
138 default_value_t = false,
139 help = "Forces single-threaded ingest (--ingest-parallelism 1) to reduce RSS pressure. \
140 Recommended for environments with <4 GB available RAM or container/cgroup \
141 constraints. Trade-off: 3-4x longer wall time. Also honored via \
142 XDG ingest.low_memory=1."
143 )]
144 pub low_memory: bool,
145
146 #[arg(long, default_value_t = crate::constants::DEFAULT_MAX_RSS_MB,
148 help = "Maximum process RSS in MiB; abort if exceeded during embedding (default: 8192)")]
149 pub max_rss_mb: u64,
150
151 #[arg(long, default_value_t = 2, value_name = "N",
156 value_parser = clap::value_parser!(u64).range(1..=32),
157 help = "Maximum simultaneous LLM embedding subprocesses per file (default: 2, clamp [1,32])")]
158 pub llm_parallelism: u64,
159
160 #[arg(long, default_value_t = crate::constants::DERIVED_NAME_MAX_LEN,
165 help = "Maximum length for derived memory names (default: 60)")]
166 pub max_name_length: usize,
167
168 #[arg(
174 long,
175 value_name = "PREFIX",
176 help = "Kebab-case prefix applied to every derived memory name (e.g. 'projx-')"
177 )]
178 pub name_prefix: Option<String>,
179
180 #[arg(long, value_enum, default_value_t = IngestMode::None)]
182 pub mode: IngestMode,
183
184 #[arg(long)]
186 pub claude_binary: Option<std::path::PathBuf>,
187
188 #[arg(long)]
190 pub claude_model: Option<String>,
191
192 #[arg(long, default_value_t = false)]
194 pub resume: bool,
195
196 #[arg(long, default_value_t = false)]
198 pub retry_failed: bool,
199
200 #[arg(long, default_value_t = false)]
202 pub keep_queue: bool,
203
204 #[arg(long)]
206 pub queue_db: Option<String>,
207
208 #[arg(long, default_value_t = 60)]
210 pub rate_limit_wait: u64,
211
212 #[arg(long)]
214 pub max_cost_usd: Option<f64>,
215
216 #[arg(
218 long,
219 default_value_t = 300,
220 help = "Timeout in seconds for each claude -p invocation (default: 300)"
221 )]
222 pub claude_timeout: u64,
223
224 #[arg(
226 long,
227 help = "Explicit path to the Codex CLI binary (only with --mode codex)"
228 )]
229 pub codex_binary: Option<PathBuf>,
230
231 #[arg(
233 long,
234 help = "Model override for Codex extraction (e.g. o4-mini, gpt-5.1-codex)"
235 )]
236 pub codex_model: Option<String>,
237
238 #[arg(
240 long,
241 default_value_t = 300,
242 help = "Timeout in seconds for each codex exec invocation (default: 300)"
243 )]
244 pub codex_timeout: u64,
245
246 #[arg(long, value_name = "PATH")]
248 pub opencode_binary: Option<PathBuf>,
249
250 #[arg(
252 long,
253 value_name = "MODEL",
254 help = "Model override for OpenCode extraction"
255 )]
256 pub opencode_model: Option<String>,
257
258 #[arg(
260 long,
261 value_name = "SECONDS",
262 default_value_t = 300,
263 help = "Timeout in seconds for each opencode run invocation (default: 300)"
264 )]
265 pub opencode_timeout: u64,
266
267 #[arg(long, value_name = "SECONDS")]
270 pub wait_job_singleton: Option<u64>,
271
272 #[arg(long, default_value_t = false)]
275 pub force_job_singleton: bool,
276
277 #[arg(
280 long,
281 default_value_t = false,
282 help = "Run enrich --operation memory-bindings after all files are ingested"
283 )]
284 pub enrich_after: bool,
285
286 #[arg(
291 long,
292 default_value_t = false,
293 help = "Update existing memories on name collision instead of skipping (idempotent re-ingest)"
294 )]
295 pub force_merge: bool,
296}
297
298#[derive(Clone, Debug, PartialEq, Eq, clap::ValueEnum)]
300pub enum IngestMode {
301 None,
303 ClaudeCode,
305 Codex,
307 #[value(name = "opencode")]
309 Opencode,
310}
311
312pub(crate) fn low_memory_setting_enabled() -> bool {
321 match crate::config::get_setting("ingest.low_memory") {
322 Ok(Some(v)) if v.is_empty() => false,
323 Ok(Some(v)) => match v.to_lowercase().as_str() {
324 "1" | "true" | "yes" | "on" => true,
325 "0" | "false" | "no" | "off" => false,
326 other => {
327 tracing::warn!(
328 target: "ingest",
329 value = %other,
330 "ingest.low_memory value not recognized; treating as disabled"
331 );
332 false
333 }
334 },
335 _ => false,
336 }
337}
338
339pub(crate) fn resolve_parallelism(
351 low_memory_flag: bool,
352 ingest_parallelism: Option<usize>,
353) -> usize {
354 let setting_flag = low_memory_setting_enabled();
355 let low_memory = low_memory_flag || setting_flag;
356
357 if low_memory {
358 if let Some(n) = ingest_parallelism {
359 if n > 1 {
360 tracing::warn!(
361 target: "ingest",
362 requested = n,
363 "--ingest-parallelism overridden by --low-memory; using 1"
364 );
365 }
366 }
367 if low_memory_flag {
368 tracing::info!(
369 target: "ingest",
370 source = "flag",
371 "low-memory mode enabled: forcing --ingest-parallelism 1"
372 );
373 } else {
374 tracing::info!(
375 target: "ingest",
376 source = "xdg",
377 "low-memory mode enabled via XDG ingest.low_memory: forcing --ingest-parallelism 1"
378 );
379 }
380 return 1;
381 }
382
383 ingest_parallelism
384 .unwrap_or_else(|| {
385 std::thread::available_parallelism()
386 .map(|v| v.get() / 2)
387 .unwrap_or(1)
388 .clamp(1, 4)
389 })
390 .max(1)
391}