Skip to content

feat(cli): add shunt add provider to scaffold provider config#69

Draft
amondnet wants to merge 1 commit into
mainfrom
amondnet/provider-add
Draft

feat(cli): add shunt add provider to scaffold provider config#69
amondnet wants to merge 1 commit into
mainfrom
amondnet/provider-add

Conversation

@amondnet

@amondnet amondnet commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a new CLI subcommand, shunt add provider <name>, that prints a ready-to-paste [providers.<name>] TOML block for a known backend — the built-in providers (openai/codex/xai/grok/anthropic) plus the Anthropic-compatible gateways (kimi, deepseek, glm, minimax, mimo, openrouter, vercel). It is print-only and never writes files; the output is valid TOML so it can be piped straight into a config file (e.g. shunt add provider kimi >> shunt.toml). Modeled on Flue's flue add print-only philosophy.

Milestone / spec

Not tied to a specific docs/ milestone spec — this is a standalone CLI ergonomics feature (config scaffolding), not a protocol/translation change.

Changes

  • New static catalog table in src/catalog.rs (AGENTS.md table-driven rule); case-insensitive provider names plus aliases (zai/z.aiglm, moonshotkimi, chatgptcodex, xiaomimimo).
  • No name argument → lists the full catalog; unknown name → errors and lists known providers.
  • Unit tests, including catalog_entries_render_to_valid_config, which round-trips every catalog entry through the real config loader + Config::validate() so a bad base_url/auth/kind can't ship; plus a test pinning the literal auth= token per mode, and a catalog name/alias uniqueness guard.
  • Docs updated in the same PR (AGENTS.md docs-no-drift rule): README ×4 (en/ko/ja/zh-CN), site reference/cli.md (new ## shunt add provider section), and guides/providers.md.

Checklist

  • cargo build passes
  • cargo test passes (new behavior is covered; tests run without network/loopback where possible) — 148 passed
  • cargo clippy --all-targets -- -D warnings clean
  • cargo fmt --all --check clean
  • Source files stay under 500 lines
  • English only; matches surrounding style
  • Frozen spec in docs/ updated if this change deviates from it — N/A, no spec deviation
  • User-facing docs updated for behavior/config/endpoint/CLI/provider/model changes — README.md / site/ as applicable (wiki/ is generated; don't hand-edit)
  • Any new GitHub Action is pinned to a full commit SHA — N/A, no workflow changes

Notes for reviewers

  • catalog_entries_render_to_valid_config is the key correctness guard here: it forces every catalog entry to round-trip through the real config loader and Config::validate(), so a typo'd base_url/auth/kind fails CI instead of shipping silently.
  • This also passed a /review:code-review-loop pass locally before pushing.

Summary by cubic

Adds shunt add provider <name> to print a ready-to-paste [providers.<name>] TOML block for known backends. Output is print-only and can be piped into shunt.toml for quick setup.

  • New Features
    • Supports built-ins (openai, codex, xai, grok, anthropic) and Anthropic-compatible gateways (kimi, deepseek, glm, minimax, mimo, openrouter, vercel); names are case-insensitive with aliases (e.g., zai/z.aiglm, moonshotkimi, chatgptcodex).
    • Populates kind, base_url, auth, and api_key_env, and adds a commented example route; never writes files. Omit the name to list known providers; unknown names error and show the list.
    • Adds a static catalog in src/catalog.rs with tests that round-trip each entry through the real config loader and Config::validate(); docs updated (README ×4, CLI reference, providers guide).

Written for commit caa9b7e. Summary will update on new commits.

Print a ready-to-paste [providers.<name>] TOML block for known
backends (built-in providers plus Anthropic-compatible gateways
like kimi/deepseek/glm/minimax/mimo/openrouter/vercel). Print-only,
never writes files, and the output is valid TOML so it can be piped:
`shunt add provider kimi >> shunt.toml`.

Adds a static catalog table (src/catalog.rs), unit tests that
round-trip validate the generated TOML through the real config
loader, and docs updates (README x4, site cli.md and providers.md).

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

이번 풀리퀘스트는 알려진 백엔드 프로바이더의 설정을 쉽게 생성할 수 있는 shunt add provider <name> CLI 명령어를 추가하고 관련 문서들을 업데이트합니다. 리뷰어는 핵심 비즈니스 로직에서 입출력(I/O) 부작용을 격리하기 위해 src/catalog.rsadd_provider 함수가 표준 출력에 직접 출력하는 대신 Result<String>을 반환하도록 리팩토링하고, 실제 출력 처리는 애플리케이션 진입점인 src/main.rs에서 담당하도록 책임을 분리할 것을 제안했습니다.

Comment thread src/catalog.rs
Comment on lines +263 to +284
pub fn add_provider(name: Option<&str>) -> anyhow::Result<()> {
match name {
None => {
print!("{}", list_text());
Ok(())
}
Some(name) => match find(name) {
Some(entry) => {
print!("{}", render(entry));
Ok(())
}
None => {
let known = CATALOG
.iter()
.map(|entry| entry.name)
.collect::<Vec<_>>()
.join(", ");
anyhow::bail!("unknown provider '{name}'. Known providers: {known}")
}
},
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

[MEDIUM] add_provider 함수가 표준 출력(stdout)에 직접 출력하는 대신 String을 반환하도록 리팩토링

Symptom: add_provider 함수가 포맷팅된 문자열을 반환하는 대신 print! 매크로를 사용하여 stdout에 직접 출력하고 있습니다.
Source: Hunt & Thomas — Pragmatic Programmer (Orthogonality)
Consequence: 비즈니스 로직과 입출력(I/O) 부작용(side effect)이 결합되어 코드의 직교성(Orthogonality)이 저하됩니다. 이로 인해 CLI가 아닌 다른 환경(예: GUI, 웹 API, 또는 라이브러리 형태)에서 이 함수를 재사용하기 어려워지며, 표준 출력을 캡처하여 검증하는 테스트를 작성하기가 까다로워집니다.
Remedy: add_provider 함수가 anyhow::Result<String>을 반환하도록 수정하여 부작용을 제거하고, 실제 출력은 애플리케이션의 진입점인 main.rs에서 처리하도록 책임을 분리합니다.

pub fn add_provider(name: Option<&str>) -> anyhow::Result<String> {
    match name {
        None => Ok(list_text()),
        Some(name) => match find(name) {
            Some(entry) => Ok(render(entry)),
            None => {
                let known = CATALOG
                    .iter()
                    .map(|entry| entry.name)
                    .collect::<Vec<_>>()
                    .join(", ");
                anyhow::bail!("unknown provider '{name}'. Known providers: {known}")
            }
        },
    }
}
References
  1. Isolate side effects at the boundaries of the application rather than inside core/validation functions.

Comment thread src/main.rs
Comment on lines +77 to +79
Some(Command::Add {
target: AddTarget::Provider { name },
}) => shunt::catalog::add_provider(name.as_deref()),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

[MEDIUM] add_provider의 반환 값을 받아 main에서 출력하도록 수정

Symptom: main.rs에서 add_provider를 호출할 때, 해당 함수가 내부적으로 직접 출력할 것을 기대하고 반환된 Result<()>만 처리하고 있습니다.
Source: Martin — Clean Architecture (Conceptual Integrity)
Consequence: 입출력(I/O) 부작용이 라이브러리 모듈 내부에서 발생하여 애플리케이션 경계(boundary)에서의 부작용 격리 원칙이 깨집니다.
Remedy: add_provider가 반환하는 Result<String>을 받아 main 함수 내에서 직접 print!를 통해 출력하도록 수정합니다.

        Some(Command::Add {
            target: AddTarget::Provider { name },
        }) => {
            let output = shunt::catalog::add_provider(name.as_deref())?;
            print!("{output}");
            Ok(())
        }
References
  1. Isolate side effects at the boundaries of the application rather than inside core/validation functions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant