Skip to content

init_cmd

init_cmd

jarvis init — detect hardware, generate config, write to disk.

Classes

Functions

init

init(force: bool, config: Optional[Path], full_config: bool = False, engine: Optional[str] = None, no_download: bool = False, skip_scan: bool = False, host: Optional[str] = None) -> None

Detect hardware and generate ~/.openjarvis/config.toml.

Source code in src/openjarvis/cli/init_cmd.py
@click.command()
@click.option(
    "--force", is_flag=True, help="Overwrite existing config without prompting."
)
@click.option(
    "--config",
    type=click.Path(exists=True),
    help="Path to config file to use.",
)
@click.option(
    "--full",
    "full_config",
    is_flag=True,
    help="Generate full reference config with all sections",
)
@click.option(
    "--engine",
    type=click.Choice(_SUPPORTED_ENGINES, case_sensitive=False),
    default=None,
    help="Inference engine to use (skips interactive selection).",
)
@click.option(
    "--no-download", is_flag=True, default=False, help="Skip the model download prompt."
)
@click.option(
    "--no-scan",
    "skip_scan",
    is_flag=True,
    default=False,
    help="Skip the post-init security environment audit.",
)
@click.option(
    "--host",
    default=None,
    help="Remote engine host URL (e.g. http://192.168.1.50:11434).",
)
def init(
    force: bool,
    config: Optional[Path],
    full_config: bool = False,
    engine: Optional[str] = None,
    no_download: bool = False,
    skip_scan: bool = False,
    host: Optional[str] = None,
) -> None:
    """Detect hardware and generate ~/.openjarvis/config.toml."""
    console = Console()

    if DEFAULT_CONFIG_PATH.exists() and not force:
        console.print(
            f"[yellow]Config already exists at {DEFAULT_CONFIG_PATH}[/yellow]"
        )
        console.print("Use [bold]--force[/bold] to overwrite.")
        raise SystemExit(1)

    console.print("[bold]Detecting hardware...[/bold]")
    hw = detect_hardware()

    console.print(f"  Platform : {hw.platform}")
    console.print(f"  CPU      : {hw.cpu_brand} ({hw.cpu_count} cores)")
    console.print(f"  RAM      : {hw.ram_gb} GB")
    if hw.gpu:
        mem_label = "unified memory" if hw.gpu.vendor == "apple" else "VRAM"
        gpu = hw.gpu
        console.print(
            f"  GPU      : {gpu.name} ({gpu.vram_gb} GB {mem_label}, x{gpu.count})"
        )
    else:
        console.print("  GPU      : none detected")

    # Resolve engine: explicit flag > interactive selection > auto-detect
    if engine is None and config is None:
        recommended = recommend_engine(hw)
        console.print()
        console.print("[bold]Detecting running inference engines...[/bold]")
        running = _detect_running_engines()
        if running:
            console.print(f"  Found running: [green]{', '.join(running)}[/green]")
        else:
            console.print("  No running engines detected.")

        # Build choices: show running engines first, then recommended, then rest
        seen: set[str] = set()
        choices: list[str] = []
        for r in running:
            if r not in seen:
                choices.append(r)
                seen.add(r)
        if recommended not in seen:
            choices.append(recommended)
            seen.add(recommended)
        for e in _SUPPORTED_ENGINES:
            if e not in seen:
                choices.append(e)
                seen.add(e)

        # Default: first running engine, or hardware recommendation
        default = running[0] if running else recommended

        labels = []
        for c in choices:
            parts = [c]
            if c in running:
                parts.append("running")
            if c == recommended:
                parts.append("recommended")
            labels.append(
                f"  {c}" + (f"  ({', '.join(parts[1:])})" if len(parts) > 1 else "")
            )

        console.print()
        console.print("[bold]Available engines:[/bold]")
        for label in labels:
            console.print(label)

        engine = click.prompt(
            "\nSelect inference engine",
            type=click.Choice(choices, case_sensitive=False),
            default=default,
        )

    # Probe remote host if specified
    if host:
        console.print("\n[bold]Checking remote host...[/bold]")
        try:
            resp = httpx.get(host.rstrip("/") + "/", timeout=2.0)
            if resp.status_code < 500:
                console.print(f"  [green]Reachable[/green] ({host})")
            else:
                console.print(
                    f"  [yellow]Warning:[/yellow] Host returned status "
                    f"{resp.status_code} — writing config anyway."
                )
        except Exception:
            console.print(
                f"  [yellow]Warning:[/yellow] Host unreachable ({host}) "
                f"— writing config anyway."
            )

    if config:
        toml_content = config.read_text()
    else:
        if full_config:
            toml_content = generate_default_toml(hw, engine=engine, host=host)
        else:
            toml_content = generate_minimal_toml(hw, engine=engine, host=host)

    DEFAULT_CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    if config:
        config.write_text(toml_content)
    else:
        DEFAULT_CONFIG_PATH.write_text(toml_content)

    console.print()
    console.print(
        Panel(
            escape(toml_content),
            title=str(DEFAULT_CONFIG_PATH),
            border_style="green",
        )
    )
    console.print("[green]Config written successfully.[/green]")

    # Create default memory files (skip if they already exist)
    soul_path = DEFAULT_CONFIG_DIR / "SOUL.md"
    if not soul_path.exists():
        soul_path.write_text(
            "# Agent Persona\n\nYou are Jarvis, a helpful personal AI assistant.\n"
        )

    memory_path = DEFAULT_CONFIG_DIR / "MEMORY.md"
    if not memory_path.exists():
        memory_path.write_text("# Agent Memory\n\n")

    user_path = DEFAULT_CONFIG_DIR / "USER.md"
    if not user_path.exists():
        user_path.write_text("# User Profile\n\n")

    skills_dir = DEFAULT_CONFIG_DIR / "skills"
    skills_dir.mkdir(exist_ok=True)

    selected_engine = engine or recommend_engine(hw)
    model = recommend_model(hw, selected_engine)

    if not model:
        console.print(
            "\n  [yellow]! Not enough memory to run any local model.[/yellow]\n"
            "  Consider a cloud engine or a machine with more RAM."
        )
    else:
        spec = find_model_spec(model)
        size_gb = estimated_download_gb(spec.parameter_count_b) if spec else 0
        console.print(
            f"\n  [bold]Recommended model:[/bold] {model} (~{size_gb:.1f} GB estimated)"
        )

        if not no_download and spec:
            prompt = f"  Download {model} (~{size_gb:.1f} GB estimated) now?"
            if click.confirm(prompt, default=True):
                _do_download(selected_engine, model, spec, console)

    if not skip_scan:
        _quick_privacy_check(console)
    console.print()
    console.print(
        Panel(
            _next_steps_text(selected_engine, model),
            title="Getting Started",
            border_style="cyan",
        )
    )