|
1 | 1 | use engraph::config; |
2 | 2 | use engraph::indexer; |
3 | | -use engraph::profile; |
4 | 3 | use engraph::search; |
5 | 4 | use engraph::store; |
6 | 5 |
|
@@ -66,10 +65,44 @@ enum Command { |
66 | 65 | all: bool, |
67 | 66 | }, |
68 | 67 |
|
69 | | - /// Initialize vault profile with auto-detection. |
| 68 | + /// Initialize vault profile, identity, and search index. |
70 | 69 | Init { |
71 | | - /// Path to the vault (defaults to current directory). |
| 70 | + /// Path to vault directory. |
72 | 71 | path: Option<PathBuf>, |
| 72 | + /// Only run identity setup (skip indexing). |
| 73 | + #[arg(long)] |
| 74 | + identity: bool, |
| 75 | + /// Only re-index (skip identity prompts). |
| 76 | + #[arg(long)] |
| 77 | + reindex: bool, |
| 78 | + /// Detect vault without writing anything (agent mode). |
| 79 | + #[arg(long)] |
| 80 | + detect: bool, |
| 81 | + /// Output as JSON (agent mode). |
| 82 | + #[arg(long)] |
| 83 | + json: bool, |
| 84 | + /// Suppress interactive prompts. |
| 85 | + #[arg(long)] |
| 86 | + quiet: bool, |
| 87 | + /// User name (non-interactive mode). |
| 88 | + #[arg(long)] |
| 89 | + name: Option<String>, |
| 90 | + /// User role (non-interactive mode). |
| 91 | + #[arg(long)] |
| 92 | + role: Option<String>, |
| 93 | + /// Vault purpose (non-interactive mode). |
| 94 | + #[arg(long)] |
| 95 | + purpose: Option<String>, |
| 96 | + }, |
| 97 | + |
| 98 | + /// Print identity block (L0 + L1 context for AI agents). |
| 99 | + Identity { |
| 100 | + /// Output as JSON. |
| 101 | + #[arg(long)] |
| 102 | + json: bool, |
| 103 | + /// Force L1 re-extraction without full reindex. |
| 104 | + #[arg(long)] |
| 105 | + refresh: bool, |
73 | 106 | }, |
74 | 107 |
|
75 | 108 | /// Configure engraph settings. |
@@ -515,158 +548,73 @@ async fn main() -> Result<()> { |
515 | 548 | } |
516 | 549 | } |
517 | 550 |
|
518 | | - Command::Init { path } => { |
519 | | - // Resolve vault path: CLI arg > config > cwd. |
| 551 | + Command::Init { path, identity, reindex, detect, json, quiet, name, role, purpose } => { |
520 | 552 | cfg.merge_vault_path(path); |
521 | 553 | let vault_path = match &cfg.vault_path { |
522 | 554 | Some(p) => p.clone(), |
523 | 555 | None => std::env::current_dir()?, |
524 | 556 | }; |
525 | 557 | let vault_path = vault_path.canonicalize().unwrap_or(vault_path); |
526 | 558 |
|
527 | | - println!("Detecting vault profile for: {}", vault_path.display()); |
528 | | - |
529 | | - let vault_type = profile::detect_vault_type(&vault_path); |
530 | | - let structure = profile::detect_structure(&vault_path)?; |
531 | | - let stats = profile::scan_vault_stats(&vault_path)?; |
532 | | - |
533 | | - // Print detection results. |
534 | | - println!(); |
535 | | - println!(" Vault type: {:?}", vault_type); |
536 | | - println!(" Structure: {:?}", structure.method); |
537 | | - if let Some(ref inbox) = structure.folders.inbox { |
538 | | - println!(" inbox: {}", inbox); |
539 | | - } |
540 | | - if let Some(ref projects) = structure.folders.projects { |
541 | | - println!(" projects: {}", projects); |
542 | | - } |
543 | | - if let Some(ref areas) = structure.folders.areas { |
544 | | - println!(" areas: {}", areas); |
545 | | - } |
546 | | - if let Some(ref resources) = structure.folders.resources { |
547 | | - println!(" resources: {}", resources); |
548 | | - } |
549 | | - if let Some(ref archive) = structure.folders.archive { |
550 | | - println!(" archive: {}", archive); |
551 | | - } |
552 | | - if let Some(ref templates) = structure.folders.templates { |
553 | | - println!(" templates: {}", templates); |
554 | | - } |
555 | | - if let Some(ref daily) = structure.folders.daily { |
556 | | - println!(" daily: {}", daily); |
557 | | - } |
558 | | - if let Some(ref people) = structure.folders.people { |
559 | | - println!(" people: {}", people); |
560 | | - } |
561 | | - println!(); |
562 | | - println!(" Total .md files: {}", stats.total_files); |
563 | | - println!(" With frontmatter: {}", stats.files_with_frontmatter); |
564 | | - println!(" Wikilinks: {}", stats.wikilink_count); |
565 | | - println!(" Unique tags: {}", stats.unique_tags); |
566 | | - println!(" Folders: {}", stats.folder_count); |
567 | | - println!(" Max folder depth: {}", stats.folder_depth); |
568 | | - |
569 | | - let vault_profile = profile::VaultProfile { |
570 | | - vault_path: vault_path.clone(), |
571 | | - vault_type, |
572 | | - structure, |
573 | | - stats, |
574 | | - }; |
575 | | - |
576 | | - // Ensure data dir exists and write vault.toml. |
577 | | - std::fs::create_dir_all(&data_dir)?; |
578 | | - profile::write_vault_toml(&vault_profile, &data_dir)?; |
579 | | - |
580 | | - println!(); |
581 | | - println!("Wrote {}", data_dir.join("vault.toml").display()); |
582 | | - |
583 | | - // Intelligence onboarding (only if not yet configured) |
584 | | - if cfg.intelligence.is_none() { |
585 | | - let enable = prompt_intelligence(&data_dir)?; |
586 | | - cfg.intelligence = Some(enable); |
587 | | - cfg.save()?; |
588 | | - } |
589 | | - |
590 | | - // Obsidian CLI detection |
591 | | - let obsidian_running = std::process::Command::new("pgrep") |
592 | | - .args(["-x", "Obsidian"]) |
593 | | - .stdout(std::process::Stdio::null()) |
594 | | - .stderr(std::process::Stdio::null()) |
595 | | - .status() |
596 | | - .map(|s| s.success()) |
597 | | - .unwrap_or(false); |
598 | | - |
599 | | - let obsidian_in_path = std::process::Command::new("which") |
600 | | - .arg("obsidian") |
601 | | - .stdout(std::process::Stdio::null()) |
602 | | - .stderr(std::process::Stdio::null()) |
603 | | - .status() |
604 | | - .map(|s| s.success()) |
605 | | - .unwrap_or(false); |
606 | | - |
607 | | - if obsidian_running && obsidian_in_path { |
608 | | - eprint!("\nObsidian CLI detected. Enable integration? [Y/n] "); |
609 | | - io::stderr().flush()?; |
610 | | - let mut answer = String::new(); |
611 | | - io::stdin().lock().read_line(&mut answer)?; |
612 | | - let answer = answer.trim(); |
613 | | - let enable = answer.is_empty() || answer.eq_ignore_ascii_case("y"); |
614 | | - if enable { |
615 | | - let vault_name = vault_path |
616 | | - .file_name() |
617 | | - .and_then(|n| n.to_str()) |
618 | | - .unwrap_or("Personal") |
619 | | - .to_string(); |
620 | | - cfg.obsidian.enabled = true; |
621 | | - cfg.obsidian.vault_name = Some(vault_name.clone()); |
622 | | - cfg.save()?; |
623 | | - println!("Obsidian CLI enabled (vault: {vault_name})."); |
| 559 | + if detect { |
| 560 | + let result = engraph::onboarding::run_detect_json(&vault_path)?; |
| 561 | + if json { |
| 562 | + println!("{}", serde_json::to_string_pretty(&result)?); |
624 | 563 | } else { |
625 | | - println!( |
626 | | - "Obsidian CLI disabled. Enable later with: engraph configure --enable-obsidian-cli" |
627 | | - ); |
| 564 | + println!("{}", serde_json::to_string_pretty(&result)?); |
628 | 565 | } |
| 566 | + return Ok(()); |
629 | 567 | } |
630 | 568 |
|
631 | | - // AI agent detection |
632 | | - let home = dirs::home_dir().unwrap_or_default(); |
633 | | - let agent_configs: &[(&str, &str, &str)] = &[ |
634 | | - ("Claude Code", "claude-code", ".claude/settings.json"), |
635 | | - ("Cursor", "cursor", ".cursor/mcp.json"), |
636 | | - ("Windsurf", "windsurf", ".codeium/windsurf/mcp_config.json"), |
637 | | - ]; |
638 | | - |
639 | | - let mut detected: Vec<(&str, &str, String)> = Vec::new(); |
640 | | - for (name, key, rel_path) in agent_configs { |
641 | | - let full = home.join(rel_path); |
642 | | - if full.exists() { |
643 | | - detected.push((name, key, format!("~/{rel_path}"))); |
644 | | - } |
| 569 | + if json { |
| 570 | + let flags = engraph::onboarding::ApplyFlags { |
| 571 | + name, role, purpose, |
| 572 | + identity_only: identity, |
| 573 | + reindex_only: reindex, |
| 574 | + }; |
| 575 | + let result = engraph::onboarding::run_apply_json(&vault_path, &mut cfg, &data_dir, flags)?; |
| 576 | + println!("{}", serde_json::to_string_pretty(&result)?); |
| 577 | + return Ok(()); |
645 | 578 | } |
646 | 579 |
|
647 | | - if !detected.is_empty() { |
648 | | - println!("\nAI agents detected:"); |
649 | | - for (name, _key, path) in &detected { |
650 | | - println!(" \u{2713} {name} ({path})"); |
651 | | - } |
652 | | - println!( |
653 | | - "\nTo register engraph as MCP server, add to your agent's config:\n \ |
654 | | - \"engraph\": {{\n \ |
655 | | - \"command\": \"engraph\",\n \ |
656 | | - \"args\": [\"serve\"]\n \ |
657 | | - }}" |
658 | | - ); |
| 580 | + let flags = engraph::onboarding::InteractiveFlags { |
| 581 | + name, role, purpose, |
| 582 | + identity_only: identity, |
| 583 | + reindex_only: reindex, |
| 584 | + quiet, |
| 585 | + }; |
| 586 | + engraph::onboarding::run_interactive(&vault_path, &mut cfg, &data_dir, flags)?; |
| 587 | + } |
659 | 588 |
|
660 | | - // Record detected agents in config |
661 | | - for (_name, key, _path) in &detected { |
662 | | - match *key { |
663 | | - "claude-code" => cfg.agents.claude_code = true, |
664 | | - "cursor" => cfg.agents.cursor = true, |
665 | | - "windsurf" => cfg.agents.windsurf = true, |
666 | | - _ => {} |
| 589 | + Command::Identity { json, refresh } => { |
| 590 | + let db_path = data_dir.join("engraph.db"); |
| 591 | + if !db_path.exists() { |
| 592 | + anyhow::bail!("No index found. Run `engraph init` first."); |
| 593 | + } |
| 594 | + let store = engraph::store::Store::open(&db_path)?; |
| 595 | + if refresh { |
| 596 | + let profile = engraph::config::Config::load_vault_profile()?; |
| 597 | + match profile { |
| 598 | + Some(ref p) => { |
| 599 | + engraph::identity::extract_l1_facts(&store, p)?; |
| 600 | + eprintln!("L1 facts refreshed."); |
| 601 | + } |
| 602 | + None => { |
| 603 | + anyhow::bail!("No vault profile found. Run `engraph init` first."); |
667 | 604 | } |
668 | 605 | } |
669 | | - cfg.save()?; |
| 606 | + } |
| 607 | + if json { |
| 608 | + let l0 = store.get_identity_facts(0)?; |
| 609 | + let l1 = store.get_identity_facts(1)?; |
| 610 | + let result = serde_json::json!({ |
| 611 | + "l0": l0.iter().map(|f| serde_json::json!({"key": &f.key, "value": &f.value})).collect::<Vec<_>>(), |
| 612 | + "l1": l1.iter().map(|f| serde_json::json!({"key": &f.key, "value": &f.value, "source": &f.source, "updated_at": &f.updated_at})).collect::<Vec<_>>(), |
| 613 | + }); |
| 614 | + println!("{}", serde_json::to_string_pretty(&result)?); |
| 615 | + } else { |
| 616 | + let block = engraph::identity::format_identity_block(&cfg, &store)?; |
| 617 | + println!("{}", block); |
670 | 618 | } |
671 | 619 | } |
672 | 620 |
|
|
0 commit comments