1mod cli;
2mod download;
3mod error;
4mod i18n;
5mod output;
6
7pub use cli::{
8 Cli, CliInvocation, PrefixProxyMode, ResolvedOptions, command, command_for_language,
9 parse_cli_from_args, parse_cli_from_env, parse_cli_invocation_from_args,
10 parse_cli_invocation_from_env, pick_token, resolve_cli, resolve_debug, resolve_local_target,
11 resolve_prefix_mode, resolve_proxy_base,
12};
13pub use download::{
14 DEFAULT_GH_PROXY, DEFAULT_GITHUB_API_BASE, DownloadStats, RunOutcome, Runner, RuntimeConfig,
15 build_contents_api_url, choose_directory_target, format_remote_path, join_proxy_url,
16 normalize_repo_path, relative_item_path,
17};
18pub use error::{AppError, UserFacingError, classify_error};
19pub use i18n::{Language, detect_language_from_args_and_env};
20pub use output::Output;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct ErrorContext {
24 pub language: Language,
25 pub token_present: bool,
26}
27
28pub fn resolve_error_context(invocation: &CliInvocation) -> ErrorContext {
29 let github_token = std::env::var("GITHUB_TOKEN").ok();
30 let gh_token = std::env::var("GH_TOKEN").ok();
31 let config = crate::cli::load_active_config(invocation.config_path.as_deref())
32 .ok()
33 .flatten();
34 let language = crate::cli::resolve_language(
35 invocation.cli.language,
36 config.as_ref().and_then(|value| value.lang),
37 std::env::var("LC_ALL").ok().as_deref(),
38 std::env::var("LC_MESSAGES").ok().as_deref(),
39 std::env::var("LANG").ok().as_deref(),
40 );
41 let token_present = crate::cli::token_present(
42 invocation.cli.token.as_deref(),
43 config.as_ref().and_then(|value| value.token.as_deref()),
44 github_token.as_deref(),
45 gh_token.as_deref(),
46 );
47
48 ErrorContext {
49 language,
50 token_present,
51 }
52}
53
54pub fn run_cli(cli: Cli) -> Result<RunOutcome, AppError> {
55 let explicit_concurrency = cli.concurrency;
56 run_cli_invocation(CliInvocation {
57 cli,
58 config_path: None,
59 explicit_concurrency: Some(explicit_concurrency),
60 })
61}
62
63pub fn run_cli_invocation(invocation: CliInvocation) -> Result<RunOutcome, AppError> {
64 let github_token = std::env::var("GITHUB_TOKEN").ok();
65 let gh_token = std::env::var("GH_TOKEN").ok();
66 let config = crate::cli::load_active_config(invocation.config_path.as_deref())?;
67 let token_source = crate::cli::debug_token_source_label(
68 invocation.cli.token.as_deref(),
69 config.as_ref().and_then(|value| value.token.as_deref()),
70 github_token.as_deref(),
71 gh_token.as_deref(),
72 );
73 let options = crate::cli::resolve_cli_with_config(
74 invocation.cli,
75 config,
76 invocation.explicit_concurrency,
77 )?;
78 let output = if options.json {
79 Output::new(!options.no_color, options.language).with_json_mode()
80 } else {
81 Output::new(!options.no_color, options.language)
82 };
83 output.startup(&options);
84 if options.debug {
85 output.debug_line(&format!("[debug] token-source: {}", token_source));
86 }
87 let runner = Runner::new(
88 RuntimeConfig {
89 api_base: options.api_base.clone(),
90 },
91 output.clone(),
92 );
93 let outcome = runner.run(&options)?;
94 if options.json {
95 output.print_json_success(&outcome.saved_path, &outcome.stats);
96 }
97 Ok(outcome)
98}