Skip to content

deep_research_setup_cmd

deep_research_setup_cmd

jarvis deep-research-setup — auto-detect local sources, ingest, and chat.

Walks the user through connecting local data sources (Apple Notes, iMessage, Obsidian), ingesting them into a shared KnowledgeStore, and launching an interactive Deep Research chat session with Qwen3.5 via Ollama.

Classes

Functions

detect_local_sources

detect_local_sources(*, notes_db_path: Optional[Path] = None, imessage_db_path: Optional[Path] = None, obsidian_vault_path: Optional[Path] = None) -> List[Dict[str, Any]]

Return a list of available local sources with their config.

Each entry is a dict with keys: connector_id, display_name, config (kwargs for the connector constructor).

Source code in src/openjarvis/cli/deep_research_setup_cmd.py
def detect_local_sources(
    *,
    notes_db_path: Optional[Path] = None,
    imessage_db_path: Optional[Path] = None,
    obsidian_vault_path: Optional[Path] = None,
) -> List[Dict[str, Any]]:
    """Return a list of available local sources with their config.

    Each entry is a dict with keys: ``connector_id``, ``display_name``,
    ``config`` (kwargs for the connector constructor).
    """
    sources: List[Dict[str, Any]] = []

    notes_path = notes_db_path or _DEFAULT_NOTES_DB
    if notes_path.exists():
        sources.append(
            {
                "connector_id": "apple_notes",
                "display_name": "Apple Notes",
                "config": {"db_path": str(notes_path)},
            }
        )

    imessage_path = imessage_db_path or _DEFAULT_IMESSAGE_DB
    if imessage_path.exists():
        sources.append(
            {
                "connector_id": "imessage",
                "display_name": "iMessage",
                "config": {"db_path": str(imessage_path)},
            }
        )

    if obsidian_vault_path and obsidian_vault_path.is_dir():
        sources.append(
            {
                "connector_id": "obsidian",
                "display_name": "Obsidian / Markdown",
                "config": {"vault_path": str(obsidian_vault_path)},
            }
        )

    return sources

detect_token_sources

detect_token_sources(*, connectors_dir: Optional[Path] = None) -> List[Dict[str, Any]]

Return token-based sources that already have valid credentials.

Source code in src/openjarvis/cli/deep_research_setup_cmd.py
def detect_token_sources(
    *,
    connectors_dir: Optional[Path] = None,
) -> List[Dict[str, Any]]:
    """Return token-based sources that already have valid credentials."""
    cdir = connectors_dir or (DEFAULT_CONFIG_DIR / "connectors")
    sources: List[Dict[str, Any]] = []

    for ts in _TOKEN_SOURCES:
        creds_file = cdir / ts["creds_file"]
        if not creds_file.exists():
            continue
        try:
            data = json.loads(creds_file.read_text(encoding="utf-8"))
        except (json.JSONDecodeError, OSError):
            continue
        if not data or not any(v for v in data.values() if v):
            continue
        sources.append(
            {
                "connector_id": ts["connector_id"],
                "display_name": ts["display_name"],
                "config": {},
            }
        )

    return sources

ingest_sources

ingest_sources(sources: List[Dict[str, Any]], store: KnowledgeStore, *, state_db: str = '') -> int

Connect and ingest all sources into the KnowledgeStore.

PARAMETER DESCRIPTION
state_db

Path for the SyncEngine checkpoint database. Defaults to ~/.openjarvis/sync_state.db when empty.

TYPE: str DEFAULT: ''

Returns

Source code in src/openjarvis/cli/deep_research_setup_cmd.py
def ingest_sources(
    sources: List[Dict[str, Any]],
    store: KnowledgeStore,
    *,
    state_db: str = "",
) -> int:
    """Connect and ingest all sources into the KnowledgeStore.

    Parameters
    ----------
    state_db:
        Path for the SyncEngine checkpoint database.  Defaults to
        ``~/.openjarvis/sync_state.db`` when empty.

    Returns total chunks indexed across all sources.
    """
    pipeline = IngestionPipeline(store)
    engine = SyncEngine(pipeline, state_db=state_db)
    total = 0
    for src in sources:
        connector = _instantiate_connector(src["connector_id"], src["config"])
        chunks = engine.sync(connector)
        total += chunks
    return total

deep_research_setup

deep_research_setup(obsidian_vault: Optional[str], skip_chat: bool) -> None

Auto-detect local data sources, ingest, and launch Deep Research chat.

Source code in src/openjarvis/cli/deep_research_setup_cmd.py
@click.command("deep-research-setup")
@click.option(
    "--obsidian-vault",
    type=click.Path(exists=True, file_okay=False),
    default=None,
    help="Path to an Obsidian vault directory.",
)
@click.option("--skip-chat", is_flag=True, help="Ingest only, don't launch chat.")
def deep_research_setup(obsidian_vault: Optional[str], skip_chat: bool) -> None:
    """Auto-detect local data sources, ingest, and launch Deep Research chat."""
    console = Console()
    console.print("\n[bold]Deep Research Setup[/bold]\n")

    # 1. Detect local sources
    vault_path = Path(obsidian_vault) if obsidian_vault else None
    local_sources = detect_local_sources(obsidian_vault_path=vault_path)

    # 2. Detect already-connected token sources
    token_sources = detect_token_sources()

    all_sources = local_sources + token_sources

    # 3. Show what we found
    if all_sources:
        table = Table(title="Detected Sources")
        table.add_column("Source", style="bold")
        table.add_column("Type", style="dim")
        table.add_column("Status", style="green")
        for src in local_sources:
            table.add_row(src["display_name"], "local", "ready")
        for src in token_sources:
            table.add_row(src["display_name"], "token", "connected")
        console.print(table)
        console.print()

    # 4. Offer to connect new token sources
    newly_connected = _prompt_connect_sources(console)
    all_sources.extend(newly_connected)

    if not all_sources:
        console.print(
            "[yellow]No data sources detected or connected.[/yellow]\n"
            "On macOS, ensure Full Disk Access is granted in "
            "System Settings > Privacy & Security."
        )
        sys.exit(1)

    # 5. Confirm and ingest
    if not click.confirm("Ingest these sources?", default=True):
        sys.exit(0)

    db_path = DEFAULT_CONFIG_DIR / "knowledge.db"
    db_path.parent.mkdir(parents=True, exist_ok=True)
    store = KnowledgeStore(str(db_path))

    console.print("\n[bold]Ingesting...[/bold]")
    for src in all_sources:
        try:
            connector = _instantiate_connector(
                src["connector_id"],
                src["config"],
            )
            pipeline = IngestionPipeline(store)
            engine = SyncEngine(pipeline)
            chunks = engine.sync(connector)
            console.print(f"  {src['display_name']}: [green]{chunks} chunks[/green]")
        except Exception as exc:  # noqa: BLE001
            console.print(f"  {src['display_name']}: [red]error: {exc}[/red]")

    total = store.count()
    console.print(
        f"\n[bold green]Done![/bold green] {total} total chunks in {db_path}\n"
    )

    # 6. Chat
    if skip_chat:
        return

    _launch_chat(store, console)