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