1use std::fmt::Write as _;
6
7use oino_core::extensions::api::{FlagSpec, FlagType};
8
9#[derive(Clone, Debug, PartialEq)]
11pub struct ExtensionFlag {
12 pub extension_path: String,
14 pub flag: FlagSpec,
16}
17
18const OPTIONS: &str = r#"Usage:
19 oino [options] [--] [@files...] [messages...]
20
21Commands:
22 oino install <source> [-l] [-y] Install an extension package (dir, archive, URL, git+https)
23 oino remove <id> [-l] Remove an installed extension package
24 oino uninstall <id> [-l] Alias for remove
25 oino update [--check] Update Oino itself from the latest release
26 oino update <id>|--extensions Re-install extension packages from their recorded source
27 oino list List distribution, installed and project extension packages
28 oino config Print the settings.json paths
29 oino auth <command> Print credentials or check provider readiness
30 oino serve Serve sessions to remote clients
31 oino <command> --help Show help for install/remove/uninstall/update/list/config/auth
32
33Options:
34 --provider <name> Provider to search for --model (requires --model)
35 --model <pattern> Model pattern or ID (supports "provider/id" and optional ":<thinking>")
36 --api-key <key> API key (defaults to env vars)
37 --system-prompt <text> System prompt (default: coding assistant prompt)
38 --append-system-prompt <text> Append text or file contents to the system prompt (can be used multiple times)
39 --mode <mode> Output mode: text (default), json, rpc, or acp
40 --print, -p Non-interactive mode: process prompt and exit
41 --continue, -c Continue previous session
42 --resume, -r Select a session to resume
43 --session <path|id> Use specific session file or partial UUID
44 --session-id <id> Use exact project session ID, creating it if missing
45 --fork <path|id> Fork specific session file or partial UUID into a new session
46 --session-dir <dir> Directory for session storage and lookup
47 --no-session Don't save session (ephemeral)
48 --name, -n <name> Set session display name
49 --models <patterns> Comma-separated model patterns for Ctrl+P cycling
50 Supports globs (anthropic/*, *sonnet*) and fuzzy matching
51 --no-tools, -nt Disable all tools by default (built-in and extension)
52 --no-builtin-tools, -nbt Disable built-in tools by default but keep extension/custom tools enabled
53 --tools, -t <tools> Comma-separated allowlist of tool names or patterns (*) to enable
54 Only +name/-name entries add to or remove from the defaults
55 --exclude-tools, -xt <tools> Comma-separated denylist of tool names or patterns (*) to disable
56 --thinking <level> Set thinking level: off, minimal, low, medium, high, xhigh, max
57 --extension, -e <path> Load an extension file or builtin:<name> (can be used multiple times)
58 --no-extensions, -ne Disable extension discovery and built-in extensions (explicit -e paths still work)
59 --exclude-extension <id> Exclude an extension or provider package by id for this run (repeatable,
60 comma-separated; e.g. --exclude-extension acme.notify,oino.anthropic)
61 --skill <path> Load a skill file or directory (can be used multiple times)
62 --no-skills, -ns Disable skills discovery and loading
63 --prompt-template <path> Load a prompt template file or directory (can be used multiple times)
64 --no-prompt-templates, -np Disable prompt template discovery and loading
65 --theme <path> Load a theme file or directory (can be used multiple times)
66 --use-theme <name[/name]> Set the initial interactive theme for this run
67 --no-themes Disable theme discovery and loading
68 --no-context-files, -nc Disable AGENTS.md and CLAUDE.md discovery and loading
69 --export <file> Export session file to HTML and exit
70 --list-models [search] List available models (with optional fuzzy search)
71 --verbose Force verbose startup (overrides quietStartup setting)
72 --tui-mode <mode> TUI mode: fullscreen (default) or regular
73 --approve, -a Trust project-local files for this run
74 --no-approve, -na Ignore project-local files for this run
75 --offline Disable startup network operations (same as OINO_OFFLINE=1)
76 --remote <url> Interactive mode over a remote session (oino://host:port?token=…)
77 --agent-dir <dir> Config directory (same as OINO_AGENT_DIR)
78 -- End option parsing; treat remaining arguments as messages/files
79 --help, -h Show this help
80 --version, -v Show version number
81
82Extensions can register additional flags (e.g., --plan from plan-mode extension)."#;
83
84const EXAMPLES: &str = r#"
85
86Examples:
87 # Interactive mode
88 oino
89
90 # Interactive mode with initial prompt
91 oino "List all .ts files in src/"
92
93 # Include files in initial message
94 oino @prompt.md @image.png "What color is the sky?"
95
96 # Non-interactive mode (process and exit)
97 oino -p "List all .ts files in src/"
98
99 # Prompt beginning with a dash
100 oino -p -- "- Summarize these points"
101
102 # Multiple messages (interactive)
103 oino "Read package.json" "What dependencies do we have?"
104
105 # Continue previous session
106 oino --continue "What did we discuss?"
107
108 # Start a named session
109 oino --name "Refactor auth module"
110
111 # Use different model
112 oino --provider openai --model gpt-4o-mini "Help me refactor this code"
113
114 # Use model with provider prefix (no --provider needed)
115 oino --model openai/gpt-4o "Help me refactor this code"
116
117 # Use model with thinking level shorthand
118 oino --model sonnet:high "Solve this complex problem"
119
120 # Limit model cycling to specific models
121 oino --models claude-sonnet,claude-haiku,gpt-4o
122
123 # Limit to a specific provider with glob pattern
124 oino --models "github-copilot/*"
125
126 # Cycle models with fixed thinking levels
127 oino --models sonnet:high,haiku:low
128
129 # Start with a specific thinking level
130 oino --thinking high "Solve this complex problem"
131
132 # Read-only mode (no file modifications possible)
133 oino --tools read,grep,find,ls -p "Review the code in src/"
134
135 # Disable one tool while keeping the rest available
136 oino --exclude-tools ask_question
137
138 # Exclude one extension package for this run
139 oino --exclude-extension acme.notify -p "Summarize src/"
140
141 # Export a session file to HTML
142 oino --export ~/.oino/sessions/--path--/session.jsonl
143 oino --export session.jsonl output.html
144
145Environment Variables:
146 OINO_AGENT_DIR - Config directory (default: ~/.oino)
147 OINO_SESSION_DIR - Session storage directory (overridden by --session-dir)
148 OINO_OFFLINE - Disable startup network operations when set to 1/true/yes
149 OINO_CACHE_RETENTION - Prompt cache retention (none, short, long)
150
151Built-in Tool Names:
152 read - Read file contents
153 bash - Execute bash commands
154 powershell - Execute PowerShell commands on Windows
155 edit - Edit files with find/replace
156 write - Write files (creates/overwrites)
157 grep - Search file contents (read-only, off by default)
158 find - Find files by glob pattern (read-only, off by default)
159 ls - List directory contents (read-only, off by default)
160"#;
161
162#[must_use]
164pub fn help_text(extension_flags: &[ExtensionFlag]) -> String {
165 let mut text =
166 String::from("oino - AI coding assistant with read, bash, edit, write tools\n\n");
167 text.push_str(OPTIONS);
168 if !extension_flags.is_empty() {
169 text.push_str("\nExtension CLI Flags:\n");
170 for ExtensionFlag {
171 extension_path,
172 flag,
173 } in extension_flags
174 {
175 let value = if flag.kind == FlagType::String {
176 " <value>"
177 } else {
178 ""
179 };
180 let description = if flag.description.is_empty() {
181 format!("Registered by {extension_path}")
182 } else {
183 flag.description.clone()
184 };
185 let left = format!(" --{}{value}", flag.name);
186 let _ = writeln!(text, "{left:<30}{description}");
187 }
188 }
189 text.push_str(EXAMPLES);
190 text.push('\n');
192 text
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn lists_extension_flags_padded() {
201 let text = help_text(&[ExtensionFlag {
202 extension_path: "ext.plan".into(),
203 flag: FlagSpec {
204 name: "plan".into(),
205 kind: FlagType::Boolean,
206 default: None,
207 description: String::new(),
208 },
209 }]);
210 let expected = format!(
211 "extension).\nExtension CLI Flags:\n{:<30}Registered by ext.plan\n\n\nExamples:",
212 " --plan"
213 );
214 assert!(text.contains(&expected), "{text}");
215 assert!(help_text(&[]).contains("extension).\n\nExamples:"));
216 assert!(help_text(&[]).starts_with("oino - AI coding assistant"));
217 }
218}