Skip to main content

outrig_cli/init/
mod.rs

1//! `outrig init` -- three-phase orchestrator for end-to-end setup.
2//!
3//! Each phase is independently idempotent:
4//! 1. **Global config** -- defer to [`config::init::run_with`] when
5//!    `~/.outrig/config.toml` is absent; log + skip when present.
6//! 2. **Repo config** -- [`repo::ensure`] writes `.agents/outrig/config.toml`
7//!    against `cwd` if missing. Also reused by `outrig image add` as a
8//!    fallback when run in an uninitialized repo.
9//! 3. **Image loop** -- offer to scaffold image-configs by
10//!    delegating to [`image::add::run_with`] in a loop.
11//!
12//! One [`PromptSource`] is threaded through all three phases so scripted
13//! tests can drive the entire flow with a single byte stream.
14
15pub mod prompt;
16pub mod repo;
17
18use std::path::Path;
19
20use crate::config_init;
21use crate::error::Result;
22use crate::hf::{self, HfTreeFetcher};
23use crate::image_setup;
24use crate::init::prompt::{Field, PromptSource};
25use crate::paths::global_config_path;
26
27pub async fn run(force: bool, global_override: Option<&Path>) -> Result<()> {
28    let cwd = std::env::current_dir()?;
29    let mut prompt = prompt::auto();
30    let mut hf = hf::auto();
31    run_with(force, global_override, &cwd, &mut prompt, &mut hf).await
32}
33
34/// Drives the three-phase flow against an arbitrary `PromptSource`.
35/// `cwd` anchors the repo-config phase (no walk-up; init is meant for
36/// initial setup of the directory you're standing in). `hf` is the
37/// HuggingFace tree-listing client used by mistralrs `model-id` prompts.
38pub async fn run_with(
39    force: bool,
40    global_override: Option<&Path>,
41    cwd: &Path,
42    prompt: &mut impl PromptSource,
43    hf: &mut impl HfTreeFetcher,
44) -> Result<()> {
45    // Phase 1: global config.
46    let global_path = global_config_path(global_override);
47    if global_path.exists() {
48        eprintln!(
49            "[outrig] using existing global config at {}",
50            global_path.display()
51        );
52    } else {
53        eprintln!(
54            "[outrig] no global config found at {} -- let's create one.",
55            global_path.display()
56        );
57        config_init::run_with(force, &global_path, prompt, hf).await?;
58        eprintln!("[outrig] wrote {}", global_path.display());
59    }
60
61    // Phase 2: repo config. Returns the bootstrapped image name (if
62    // we wrote the config) so phase 3's first image-add can skip its
63    // name prompt.
64    let mut bootstrapped_name = repo::ensure(cwd, &global_path, prompt, hf).await?;
65
66    // Phase 3: image loop. The gate prompt is skipped on the first
67    // iteration when phase 2 just bootstrapped an image -- the user
68    // already chose to add one by walking through the image section,
69    // so asking again would be redundant. When phase 2 short-circuited
70    // on an existing config, the gate fires (re-runs may not want to
71    // add an image).
72    let mut first = true;
73    loop {
74        let should_run = if first && bootstrapped_name.is_some() {
75            true
76        } else if first {
77            prompt.ask_bool(&ADD_FIRST_IMAGE_FIELD, true).await?
78        } else {
79            prompt.ask_bool(&ADD_ANOTHER_IMAGE_FIELD, false).await?
80        };
81        if !should_run {
82            break;
83        }
84        let name = if first {
85            bootstrapped_name.take()
86        } else {
87            None
88        };
89        image_setup::add::run_with(cwd, name, force, prompt).await?;
90        first = false;
91    }
92
93    Ok(())
94}
95
96const ADD_FIRST_IMAGE_FIELD: Field = Field {
97    name: "Add an image-config now?",
98    description: "Yes: walk through `outrig image add` to scaffold a \
99                  Dockerfile and [images.<name>] block.",
100    options: &[],
101    doc_link: "doc/usage/init.md",
102};
103
104const ADD_ANOTHER_IMAGE_FIELD: Field = Field {
105    name: "Add another image-config?",
106    description: "Yes: scaffold one more image-config via \
107                  `outrig image add`. No: finish init.",
108    options: &[],
109    doc_link: "doc/usage/init.md",
110};
111
112/// Slice of every `Field` declared in this module, for `prompt_doc_sync.rs`.
113pub const DOC_SYNC_FIELDS: &[&Field] = &[&ADD_FIRST_IMAGE_FIELD, &ADD_ANOTHER_IMAGE_FIELD];