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