1use serde::Serialize;
7use std::collections::BTreeMap;
8
9#[derive(Debug, Clone, Serialize)]
10pub struct ConfigSchema {
11 pub version: u32,
12 pub sections: BTreeMap<String, SectionSchema>,
13}
14
15#[derive(Debug, Clone, Serialize)]
16pub struct SectionSchema {
17 pub description: String,
18 pub keys: BTreeMap<String, KeySchema>,
19}
20
21#[derive(Debug, Clone, Serialize)]
22pub struct KeySchema {
23 #[serde(rename = "type")]
24 pub ty: String,
25 pub default: serde_json::Value,
26 pub description: String,
27 #[serde(skip_serializing_if = "Option::is_none")]
28 pub values: Option<Vec<String>>,
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub env_override: Option<String>,
31}
32
33fn clean_f32(v: f32) -> serde_json::Value {
34 let clean: f64 = format!("{v}").parse().unwrap_or(v as f64);
35 serde_json::json!(clean)
36}
37
38fn key(ty: &str, default: serde_json::Value, desc: &str) -> KeySchema {
39 KeySchema {
40 ty: ty.to_string(),
41 default,
42 description: desc.to_string(),
43 values: None,
44 env_override: None,
45 }
46}
47
48fn key_enum(values: &[&str], default: &str, desc: &str) -> KeySchema {
49 KeySchema {
50 ty: "enum".to_string(),
51 default: serde_json::Value::String(default.to_string()),
52 description: desc.to_string(),
53 values: Some(values.iter().map(ToString::to_string).collect()),
54 env_override: None,
55 }
56}
57
58fn key_with_env(ty: &str, default: serde_json::Value, desc: &str, env: &str) -> KeySchema {
59 KeySchema {
60 ty: ty.to_string(),
61 default,
62 description: desc.to_string(),
63 values: None,
64 env_override: Some(env.to_string()),
65 }
66}
67
68fn key_enum_with_env(values: &[&str], default: &str, desc: &str, env: &str) -> KeySchema {
69 KeySchema {
70 ty: "enum".to_string(),
71 default: serde_json::Value::String(default.to_string()),
72 description: desc.to_string(),
73 values: Some(values.iter().map(ToString::to_string).collect()),
74 env_override: Some(env.to_string()),
75 }
76}
77
78impl ConfigSchema {
79 pub fn generate() -> Self {
80 let cfg = super::Config::default();
81 let mut sections = BTreeMap::new();
82
83 let mut root = BTreeMap::new();
84 root.insert(
85 "ultra_compact".into(),
86 key(
87 "bool",
88 serde_json::json!(false),
89 "Legacy flag for maximum compression (use compression_level instead)",
90 ),
91 );
92 root.insert(
93 "tee_mode".into(),
94 key_enum(
95 &["never", "failures", "always"],
96 "failures",
97 "Controls when shell output is tee'd to disk for later retrieval",
98 ),
99 );
100 root.insert(
101 "output_density".into(),
102 key_enum_with_env(
103 &["normal", "terse", "ultra"],
104 "normal",
105 "Controls how dense/compact MCP tool output is formatted",
106 "LEAN_CTX_OUTPUT_DENSITY",
107 ),
108 );
109 root.insert(
110 "checkpoint_interval".into(),
111 key(
112 "u32",
113 serde_json::json!(cfg.checkpoint_interval),
114 "Session checkpoint interval in minutes",
115 ),
116 );
117 root.insert(
118 "excluded_commands".into(),
119 key(
120 "string[]",
121 serde_json::json!(cfg.excluded_commands),
122 "Commands to exclude from shell hook interception",
123 ),
124 );
125 root.insert(
126 "passthrough_urls".into(),
127 key(
128 "string[]",
129 serde_json::json!(cfg.passthrough_urls),
130 "URLs to pass through without proxy interception",
131 ),
132 );
133 root.insert("slow_command_threshold_ms".into(), key("u64", serde_json::json!(cfg.slow_command_threshold_ms), "Commands taking longer than this (ms) are recorded in the slow log. Set to 0 to disable"));
134 root.insert(
135 "theme".into(),
136 key(
137 "string",
138 serde_json::json!(cfg.theme),
139 "Dashboard color theme",
140 ),
141 );
142 root.insert(
143 "buddy_enabled".into(),
144 key(
145 "bool",
146 serde_json::json!(cfg.buddy_enabled),
147 "Enable the buddy system for multi-agent coordination",
148 ),
149 );
150 root.insert(
151 "enable_wakeup_ctx".into(),
152 key(
153 "bool",
154 serde_json::json!(cfg.enable_wakeup_ctx),
155 "Append wakeup briefing (facts, session summary) to ctx_overview output. Set false to reduce context bloat when calling ctx_overview frequently.",
156 ),
157 );
158 root.insert(
159 "redirect_exclude".into(),
160 key(
161 "string[]",
162 serde_json::json!(cfg.redirect_exclude),
163 "URL patterns to exclude from proxy redirection",
164 ),
165 );
166 root.insert(
167 "disabled_tools".into(),
168 key(
169 "string[]",
170 serde_json::json!(cfg.disabled_tools),
171 "Tools to exclude from the MCP tool list",
172 ),
173 );
174 root.insert(
175 "rules_scope".into(),
176 key_enum(
177 &["both", "global", "project"],
178 "both",
179 "Where agent rule files are installed. Override via LEAN_CTX_RULES_SCOPE",
180 ),
181 );
182 root.insert(
183 "extra_ignore_patterns".into(),
184 key(
185 "string[]",
186 serde_json::json!(cfg.extra_ignore_patterns),
187 "Extra glob patterns to ignore in graph/overview/preload",
188 ),
189 );
190 root.insert(
191 "terse_agent".into(),
192 key_enum_with_env(
193 &["off", "lite", "full", "ultra"],
194 "off",
195 "Controls agent output verbosity via instructions injection",
196 "LEAN_CTX_TERSE_AGENT",
197 ),
198 );
199 root.insert(
200 "compression_level".into(),
201 key_enum_with_env(
202 &["off", "lite", "standard", "max"],
203 "off",
204 "Unified compression level for all output",
205 "LEAN_CTX_COMPRESSION",
206 ),
207 );
208 root.insert(
209 "allow_paths".into(),
210 key_with_env(
211 "string[]",
212 serde_json::json!(cfg.allow_paths),
213 "Additional paths allowed by PathJail (absolute)",
214 "LEAN_CTX_ALLOW_PATH",
215 ),
216 );
217 root.insert(
218 "content_defined_chunking".into(),
219 key(
220 "bool",
221 serde_json::json!(false),
222 "Enable Rabin-Karp chunking for cache-optimal output ordering",
223 ),
224 );
225 root.insert(
226 "minimal_overhead".into(),
227 key_with_env(
228 "bool",
229 serde_json::json!(false),
230 "Skip session/knowledge/gotcha blocks in MCP instructions",
231 "LEAN_CTX_MINIMAL",
232 ),
233 );
234 root.insert(
235 "shell_hook_disabled".into(),
236 key_with_env(
237 "bool",
238 serde_json::json!(false),
239 "Disable shell hook injection",
240 "LEAN_CTX_NO_HOOK",
241 ),
242 );
243 root.insert(
244 "shell_activation".into(),
245 key_enum_with_env(
246 &["always", "agents-only", "off"],
247 "always",
248 "Controls when the shell hook auto-activates aliases",
249 "LEAN_CTX_SHELL_ACTIVATION",
250 ),
251 );
252 root.insert(
253 "update_check_disabled".into(),
254 key_with_env(
255 "bool",
256 serde_json::json!(false),
257 "Disable the daily version check",
258 "LEAN_CTX_NO_UPDATE_CHECK",
259 ),
260 );
261 root.insert(
262 "bm25_max_cache_mb".into(),
263 key_with_env(
264 "u64",
265 serde_json::json!(cfg.bm25_max_cache_mb),
266 "Maximum BM25 cache file size in MB",
267 "LEAN_CTX_BM25_MAX_CACHE_MB",
268 ),
269 );
270 root.insert(
271 "graph_index_max_files".into(),
272 key(
273 "u64",
274 serde_json::json!(cfg.graph_index_max_files),
275 "Maximum files in graph index. 0 = unlimited (default). Set >0 to cap for constrained systems",
276 ),
277 );
278 root.insert(
279 "memory_profile".into(),
280 key_enum_with_env(
281 &["low", "balanced", "performance"],
282 "balanced",
283 "Controls RAM vs feature trade-off",
284 "LEAN_CTX_MEMORY_PROFILE",
285 ),
286 );
287 root.insert(
288 "memory_cleanup".into(),
289 key_enum_with_env(
290 &["aggressive", "shared"],
291 "aggressive",
292 "Controls how aggressively memory is freed when idle",
293 "LEAN_CTX_MEMORY_CLEANUP",
294 ),
295 );
296 root.insert(
297 "savings_footer".into(),
298 key_enum_with_env(
299 &["auto", "always", "never"],
300 "never",
301 "Controls visibility of token savings footers: never (default, suppress everywhere), always, auto (context-dependent)",
302 "LEAN_CTX_SAVINGS_FOOTER",
303 ),
304 );
305 root.insert(
306 "max_ram_percent".into(),
307 key_with_env(
308 "u8",
309 serde_json::json!(cfg.max_ram_percent),
310 "Maximum percentage of system RAM that lean-ctx may use (1-50, default 5)",
311 "LEAN_CTX_MAX_RAM_PERCENT",
312 ),
313 );
314 root.insert(
315 "project_root".into(),
316 key_with_env(
317 "string?",
318 serde_json::json!(null),
319 "Explicit project root directory. Prevents accidental home-directory scans",
320 "LEAN_CTX_PROJECT_ROOT",
321 ),
322 );
323 sections.insert(
324 "root".into(),
325 SectionSchema {
326 description: "Top-level configuration keys".into(),
327 keys: root,
328 },
329 );
330
331 sections.insert(
332 "ide_paths".into(),
333 SectionSchema {
334 description: "Per-IDE allowed paths. Keys are agent names (cursor, codex, opencode, antigravity, etc.), values are arrays of paths to index for that agent".into(),
335 keys: BTreeMap::new(),
336 },
337 );
338
339 let mut lsp_keys = BTreeMap::new();
340 lsp_keys.insert(
341 "rust".into(),
342 key(
343 "string?",
344 serde_json::json!(null),
345 "Custom path to rust-analyzer binary",
346 ),
347 );
348 lsp_keys.insert(
349 "typescript".into(),
350 key(
351 "string?",
352 serde_json::json!(null),
353 "Custom path to typescript-language-server binary",
354 ),
355 );
356 lsp_keys.insert(
357 "python".into(),
358 key(
359 "string?",
360 serde_json::json!(null),
361 "Custom path to pylsp binary",
362 ),
363 );
364 lsp_keys.insert(
365 "go".into(),
366 key(
367 "string?",
368 serde_json::json!(null),
369 "Custom path to gopls binary",
370 ),
371 );
372 sections.insert(
373 "lsp".into(),
374 SectionSchema {
375 description: "LSP server binary overrides. Map language name to custom binary path"
376 .into(),
377 keys: lsp_keys,
378 },
379 );
380
381 let mut archive = BTreeMap::new();
382 archive.insert(
383 "enabled".into(),
384 key(
385 "bool",
386 serde_json::json!(cfg.archive.enabled),
387 "Enable zero-loss compression archive",
388 ),
389 );
390 archive.insert(
391 "threshold_chars".into(),
392 key(
393 "usize",
394 serde_json::json!(cfg.archive.threshold_chars),
395 "Minimum output size (chars) to trigger archiving",
396 ),
397 );
398 archive.insert(
399 "max_age_hours".into(),
400 key(
401 "u64",
402 serde_json::json!(cfg.archive.max_age_hours),
403 "Maximum age of archived entries before cleanup",
404 ),
405 );
406 archive.insert(
407 "max_disk_mb".into(),
408 key(
409 "u64",
410 serde_json::json!(cfg.archive.max_disk_mb),
411 "Maximum total disk usage for the archive",
412 ),
413 );
414 sections.insert("archive".into(), SectionSchema {
415 description: "Settings for the zero-loss compression archive (large tool outputs saved to disk)".into(),
416 keys: archive,
417 });
418
419 let mut autonomy = BTreeMap::new();
420 autonomy.insert(
421 "enabled".into(),
422 key(
423 "bool",
424 serde_json::json!(cfg.autonomy.enabled),
425 "Enable autonomous background behaviors",
426 ),
427 );
428 autonomy.insert(
429 "auto_preload".into(),
430 key(
431 "bool",
432 serde_json::json!(cfg.autonomy.auto_preload),
433 "Auto-preload related files on first read",
434 ),
435 );
436 autonomy.insert(
437 "auto_dedup".into(),
438 key(
439 "bool",
440 serde_json::json!(cfg.autonomy.auto_dedup),
441 "Auto-deduplicate repeated reads",
442 ),
443 );
444 autonomy.insert(
445 "auto_related".into(),
446 key(
447 "bool",
448 serde_json::json!(cfg.autonomy.auto_related),
449 "Auto-load graph-related files",
450 ),
451 );
452 autonomy.insert(
453 "auto_consolidate".into(),
454 key(
455 "bool",
456 serde_json::json!(cfg.autonomy.auto_consolidate),
457 "Auto-consolidate knowledge periodically",
458 ),
459 );
460 autonomy.insert(
461 "silent_preload".into(),
462 key(
463 "bool",
464 serde_json::json!(cfg.autonomy.silent_preload),
465 "Suppress preload notifications in output",
466 ),
467 );
468 autonomy.insert(
469 "dedup_threshold".into(),
470 key(
471 "usize",
472 serde_json::json!(cfg.autonomy.dedup_threshold),
473 "Number of repeated reads before dedup triggers",
474 ),
475 );
476 autonomy.insert(
477 "consolidate_every_calls".into(),
478 key(
479 "u32",
480 serde_json::json!(cfg.autonomy.consolidate_every_calls),
481 "Consolidate knowledge every N tool calls",
482 ),
483 );
484 autonomy.insert(
485 "consolidate_cooldown_secs".into(),
486 key(
487 "u64",
488 serde_json::json!(cfg.autonomy.consolidate_cooldown_secs),
489 "Minimum seconds between consolidation runs",
490 ),
491 );
492 sections.insert(
493 "autonomy".into(),
494 SectionSchema {
495 description:
496 "Controls autonomous background behaviors (preload, dedup, consolidation)"
497 .into(),
498 keys: autonomy,
499 },
500 );
501
502 let mut loop_det = BTreeMap::new();
503 loop_det.insert(
504 "normal_threshold".into(),
505 key(
506 "u32",
507 serde_json::json!(cfg.loop_detection.normal_threshold),
508 "Repetitions before reducing output",
509 ),
510 );
511 loop_det.insert(
512 "reduced_threshold".into(),
513 key(
514 "u32",
515 serde_json::json!(cfg.loop_detection.reduced_threshold),
516 "Repetitions before further reducing output",
517 ),
518 );
519 loop_det.insert(
520 "blocked_threshold".into(),
521 key(
522 "u32",
523 serde_json::json!(cfg.loop_detection.blocked_threshold),
524 "Repetitions before blocking. 0 = disabled",
525 ),
526 );
527 loop_det.insert(
528 "window_secs".into(),
529 key(
530 "u64",
531 serde_json::json!(cfg.loop_detection.window_secs),
532 "Time window in seconds for loop detection",
533 ),
534 );
535 loop_det.insert(
536 "search_group_limit".into(),
537 key(
538 "u32",
539 serde_json::json!(cfg.loop_detection.search_group_limit),
540 "Maximum unique searches within a loop window",
541 ),
542 );
543 sections.insert(
544 "loop_detection".into(),
545 SectionSchema {
546 description: "Loop detection settings for preventing repeated identical tool calls"
547 .into(),
548 keys: loop_det,
549 },
550 );
551
552 let mut cloud = BTreeMap::new();
553 cloud.insert(
554 "contribute_enabled".into(),
555 key(
556 "bool",
557 serde_json::json!(cfg.cloud.contribute_enabled),
558 "Enable contributing anonymized stats to lean-ctx cloud",
559 ),
560 );
561 sections.insert(
562 "cloud".into(),
563 SectionSchema {
564 description: "Cloud feature settings".into(),
565 keys: cloud,
566 },
567 );
568
569 let mut proxy = BTreeMap::new();
570 proxy.insert(
571 "anthropic_upstream".into(),
572 key(
573 "string?",
574 serde_json::json!(cfg.proxy.anthropic_upstream),
575 "Custom upstream URL for Anthropic API proxy",
576 ),
577 );
578 proxy.insert(
579 "openai_upstream".into(),
580 key(
581 "string?",
582 serde_json::json!(cfg.proxy.openai_upstream),
583 "Custom upstream URL for OpenAI API proxy",
584 ),
585 );
586 proxy.insert(
587 "gemini_upstream".into(),
588 key(
589 "string?",
590 serde_json::json!(cfg.proxy.gemini_upstream),
591 "Custom upstream URL for Gemini API proxy",
592 ),
593 );
594 sections.insert(
595 "proxy".into(),
596 SectionSchema {
597 description: "Proxy upstream configuration for API routing".into(),
598 keys: proxy,
599 },
600 );
601
602 let mem = &cfg.memory;
603 let mut mem_knowledge = BTreeMap::new();
604 mem_knowledge.insert(
605 "max_facts".into(),
606 key(
607 "usize",
608 serde_json::json!(mem.knowledge.max_facts),
609 "Maximum number of knowledge facts stored per project",
610 ),
611 );
612 mem_knowledge.insert(
613 "max_patterns".into(),
614 key(
615 "usize",
616 serde_json::json!(mem.knowledge.max_patterns),
617 "Maximum number of patterns stored",
618 ),
619 );
620 mem_knowledge.insert(
621 "max_history".into(),
622 key(
623 "usize",
624 serde_json::json!(mem.knowledge.max_history),
625 "Maximum history entries retained",
626 ),
627 );
628 mem_knowledge.insert(
629 "contradiction_threshold".into(),
630 key(
631 "f32",
632 clean_f32(mem.knowledge.contradiction_threshold),
633 "Confidence threshold for contradiction detection",
634 ),
635 );
636 mem_knowledge.insert(
637 "recall_facts_limit".into(),
638 key(
639 "usize",
640 serde_json::json!(mem.knowledge.recall_facts_limit),
641 "Maximum facts returned per recall query",
642 ),
643 );
644 mem_knowledge.insert(
645 "rooms_limit".into(),
646 key(
647 "usize",
648 serde_json::json!(mem.knowledge.rooms_limit),
649 "Maximum number of rooms returned",
650 ),
651 );
652 mem_knowledge.insert(
653 "timeline_limit".into(),
654 key(
655 "usize",
656 serde_json::json!(mem.knowledge.timeline_limit),
657 "Maximum number of timeline entries returned",
658 ),
659 );
660 mem_knowledge.insert(
661 "relations_limit".into(),
662 key(
663 "usize",
664 serde_json::json!(mem.knowledge.relations_limit),
665 "Maximum number of relations returned",
666 ),
667 );
668 sections.insert(
669 "memory.knowledge".into(),
670 SectionSchema {
671 description: "Knowledge memory budgets (facts, patterns, gotchas)".into(),
672 keys: mem_knowledge,
673 },
674 );
675
676 let mut mem_episodic = BTreeMap::new();
677 mem_episodic.insert(
678 "max_episodes".into(),
679 key(
680 "usize",
681 serde_json::json!(mem.episodic.max_episodes),
682 "Maximum number of episodes retained",
683 ),
684 );
685 mem_episodic.insert(
686 "max_actions_per_episode".into(),
687 key(
688 "usize",
689 serde_json::json!(mem.episodic.max_actions_per_episode),
690 "Maximum actions tracked per episode",
691 ),
692 );
693 mem_episodic.insert(
694 "summary_max_chars".into(),
695 key(
696 "usize",
697 serde_json::json!(mem.episodic.summary_max_chars),
698 "Maximum characters in episode summary",
699 ),
700 );
701 sections.insert(
702 "memory.episodic".into(),
703 SectionSchema {
704 description: "Episodic memory budgets (session episodes)".into(),
705 keys: mem_episodic,
706 },
707 );
708
709 let mut mem_procedural = BTreeMap::new();
710 mem_procedural.insert(
711 "max_procedures".into(),
712 key(
713 "usize",
714 serde_json::json!(mem.procedural.max_procedures),
715 "Maximum number of learned procedures stored",
716 ),
717 );
718 mem_procedural.insert(
719 "min_repetitions".into(),
720 key(
721 "usize",
722 serde_json::json!(mem.procedural.min_repetitions),
723 "Minimum repetitions before a pattern is stored",
724 ),
725 );
726 mem_procedural.insert(
727 "min_sequence_len".into(),
728 key(
729 "usize",
730 serde_json::json!(mem.procedural.min_sequence_len),
731 "Minimum sequence length for procedure detection",
732 ),
733 );
734 mem_procedural.insert(
735 "max_window_size".into(),
736 key(
737 "usize",
738 serde_json::json!(mem.procedural.max_window_size),
739 "Maximum window size for pattern analysis",
740 ),
741 );
742 sections.insert(
743 "memory.procedural".into(),
744 SectionSchema {
745 description: "Procedural memory budgets (learned patterns)".into(),
746 keys: mem_procedural,
747 },
748 );
749
750 let mut mem_lifecycle = BTreeMap::new();
751 mem_lifecycle.insert(
752 "decay_rate".into(),
753 key(
754 "f32",
755 clean_f32(mem.lifecycle.decay_rate),
756 "Rate at which knowledge confidence decays over time",
757 ),
758 );
759 mem_lifecycle.insert(
760 "low_confidence_threshold".into(),
761 key(
762 "f32",
763 clean_f32(mem.lifecycle.low_confidence_threshold),
764 "Threshold below which facts are considered low-confidence",
765 ),
766 );
767 mem_lifecycle.insert(
768 "stale_days".into(),
769 key(
770 "i64",
771 serde_json::json!(mem.lifecycle.stale_days),
772 "Days after which unused facts are considered stale",
773 ),
774 );
775 mem_lifecycle.insert(
776 "similarity_threshold".into(),
777 key(
778 "f32",
779 clean_f32(mem.lifecycle.similarity_threshold),
780 "Similarity threshold for deduplication",
781 ),
782 );
783 sections.insert(
784 "memory.lifecycle".into(),
785 SectionSchema {
786 description: "Knowledge lifecycle policy (decay, staleness, dedup)".into(),
787 keys: mem_lifecycle,
788 },
789 );
790
791 let mut mem_gotcha = BTreeMap::new();
792 mem_gotcha.insert(
793 "max_gotchas_per_project".into(),
794 key(
795 "usize",
796 serde_json::json!(mem.gotcha.max_gotchas_per_project),
797 "Maximum gotchas stored per project",
798 ),
799 );
800 mem_gotcha.insert(
801 "retrieval_budget_per_room".into(),
802 key(
803 "usize",
804 serde_json::json!(mem.gotcha.retrieval_budget_per_room),
805 "Maximum gotchas retrieved per room per query",
806 ),
807 );
808 mem_gotcha.insert(
809 "default_decay_rate".into(),
810 key(
811 "f32",
812 clean_f32(mem.gotcha.default_decay_rate),
813 "Default decay rate for gotcha importance",
814 ),
815 );
816 sections.insert(
817 "memory.gotcha".into(),
818 SectionSchema {
819 description: "Gotcha memory settings (project-specific warnings and pitfalls)"
820 .into(),
821 keys: mem_gotcha,
822 },
823 );
824
825 let mut mem_embeddings = BTreeMap::new();
826 mem_embeddings.insert(
827 "max_facts".into(),
828 key(
829 "usize",
830 serde_json::json!(mem.embeddings.max_facts),
831 "Maximum number of embedding facts stored",
832 ),
833 );
834 sections.insert(
835 "memory.embeddings".into(),
836 SectionSchema {
837 description: "Embeddings memory settings for semantic search".into(),
838 keys: mem_embeddings,
839 },
840 );
841
842 let mut aliases = BTreeMap::new();
843 aliases.insert(
844 "command".into(),
845 key(
846 "string",
847 serde_json::json!(""),
848 "The command pattern to match (e.g. 'deploy')",
849 ),
850 );
851 aliases.insert(
852 "alias".into(),
853 key(
854 "string",
855 serde_json::json!(""),
856 "The alias definition to execute",
857 ),
858 );
859 sections.insert("custom_aliases".into(), SectionSchema {
860 description: "Custom command aliases (array of {command, alias} entries). Note: field names are 'command' and 'alias' (not 'name')".into(),
861 keys: aliases,
862 });
863
864 if let Some(root_section) = sections.get_mut("root") {
865 root_section.keys.insert(
866 "custom_aliases".into(),
867 key(
868 "array",
869 serde_json::json!([]),
870 "Custom command aliases (array of {command, alias} entries)",
871 ),
872 );
873 }
874
875 ConfigSchema {
876 version: 1,
877 sections,
878 }
879 }
880
881 pub fn known_keys(&self) -> Vec<String> {
883 let mut keys = Vec::new();
884 for (section, schema) in &self.sections {
885 if section == "root" {
886 for key_name in schema.keys.keys() {
887 keys.push(key_name.clone());
888 }
889 } else {
890 if schema.keys.is_empty() {
891 keys.push(section.clone());
892 }
893 for key_name in schema.keys.keys() {
894 keys.push(format!("{section}.{key_name}"));
895 }
896 }
897 }
898 keys
899 }
900}