ort_openrouter_cli/input/
cli.rs1use core::ffi::{c_int, c_void};
8
9extern crate alloc;
10use alloc::string::{String, ToString};
11use alloc::vec::Vec;
12
13use crate::OrtResult;
14use crate::PromptOpts;
15use crate::Write;
16use crate::common::config;
17use crate::common::{buf_read, site};
18use crate::input::args;
19use crate::list;
20use crate::prompt;
21use crate::syscall;
22use crate::{ErrorKind, ort_error};
23
24const STDIN_FILENO: i32 = 0;
25const STDERR_FILENO: i32 = 0;
26
27const USAGE: &str = "Usage: ort [-m <model>] [-s \"<system prompt>\"] [-p <price|throughput|latency>] [-pr provider-slug] [-r] [-rr] [-q] [-nc] <prompt>\n\
29Defaults: -m google/gemma-3n-e4b-it:free; -s omitted ; -p omitted\n\
30Example:\n ort -p price -m openai/gpt-oss-20b -r low -rr -s \"Respond like a pirate\" \"Write a limerick about AI\"
31
32See https://github.com/grahamking/ort for full docs.
33";
34
35pub fn print_usage() {
36 syscall::write(STDERR_FILENO, USAGE.as_ptr() as *const c_void, USAGE.len());
37}
38
39#[allow(nonstandard_style)]
41#[derive(Default)]
42pub struct Env {
43 pub HOME: Option<&'static str>,
44 pub TMUX_PANE: Option<&'static str>,
45 pub XDG_CONFIG_HOME: Option<&'static str>,
46 pub XDG_CACHE_HOME: Option<&'static str>,
47 pub OPENROUTER_API_KEY: Option<&'static str>,
48 pub NVIDIA_API_KEY: Option<&'static str>,
49}
50
51fn parse_args(args: &[String]) -> Result<args::Cmd, args::ArgParseError> {
52 if args.len() == 1 {
54 return Err(args::ArgParseError::show_help());
55 }
56
57 if args[1].as_str() == "list" {
58 args::parse_list_args(args)
59 } else {
60 let is_pipe_input = !syscall::isatty(STDIN_FILENO);
61 let stdin = if is_pipe_input {
62 let mut buffer = String::with_capacity(8 * 1024);
63 buf_read::fd_read_to_string(STDIN_FILENO, &mut buffer);
64 Some(buffer)
65 } else {
66 None
67 };
68 args::parse_prompt_args(args, stdin)
69 }
70}
71
72pub fn main(
73 args: &[String],
74 env: Env,
75 is_terminal: bool,
76 w: impl Write + Send,
77) -> OrtResult<c_int> {
78 let site = match args[0].split('/').next_back().unwrap() {
79 "nrt" => site::NVIDIA,
80 "mrt" => site::MOCK,
81 _ => site::OPENROUTER,
82 };
83
84 let cfg = config::load_config(&env, site.config_filename)?;
86
87 let api_key_ref = match site.name {
89 "OpenRouter" => env.OPENROUTER_API_KEY.unwrap_or_default(),
90 "NVIDIA" => env.NVIDIA_API_KEY.unwrap_or_default(),
91 "MOCK" => "test",
92 _ => panic!("unknown site"),
93 };
94 let mut api_key = api_key_ref.to_string();
95 if api_key.is_empty() {
96 api_key = match cfg.get_api_key() {
97 Some(k) => k,
98 None => {
99 return Err(ort_error(
100 ErrorKind::MissingApiKey,
101 "OPENROUTER_API_KEY or NVIDIA_API_KEY is not set.",
102 ));
103 }
104 }
105 };
106
107 let cmd = match parse_args(args) {
108 Ok(cmd) => cmd,
109 Err(err) if err.is_help() => {
110 print_usage();
111 return Ok(0);
112 }
113 Err(err) => {
114 print_usage();
115 return Err(err.into());
116 }
117 };
118
119 let cmd_result = match cmd {
120 args::Cmd::Prompt(mut cli_opts) => {
121 if cli_opts.merge_config {
122 cli_opts.merge(cfg.prompt_opts.unwrap_or_default());
123 } else {
124 cli_opts.merge(PromptOpts::default());
125 }
126 let mut messages = Vec::with_capacity(3);
132 if let Some(sys) = cli_opts.system.take() {
133 messages.push(crate::Message::system(sys));
134 };
135 let user_message = if cli_opts.files.is_empty() {
136 crate::Message::user(cli_opts.prompt.take().unwrap())
137 } else {
138 crate::Message::with_files(cli_opts.prompt.take().unwrap(), &cli_opts.files)?
139 };
140 messages.push(user_message);
141 if cli_opts.models.len() == 1 {
142 prompt::run(
143 &api_key,
144 cfg.settings.unwrap_or_default(),
145 &env,
146 cli_opts,
147 site,
148 messages,
149 !is_terminal,
150 w,
151 )
152 } else {
153 prompt::run_multi(
154 &api_key,
155 cfg.settings.unwrap_or_default(),
156 cli_opts,
157 site,
158 messages,
159 w,
160 )
161 }
162 }
163 args::Cmd::ContinueConversation(cli_opts) => prompt::run_continue(
164 &api_key,
165 cfg.settings.unwrap_or_default(),
166 &env,
167 cli_opts,
168 site,
169 !is_terminal,
170 w,
171 ),
172 args::Cmd::List(args) => {
173 list::run(&api_key, cfg.settings.unwrap_or_default(), args, site, w)
174 }
175 };
176 cmd_result.map(|_| 0)
177}