Class: Cline::Cli
Overview
Provide a wrapper over the Cline CLI tool
Defined Under Namespace
Classes: UnexpectedExitStatusError, UnexpectedInteractiveSessionError, UnknownOptionError
Constant Summary collapse
- COMMANDS =
Define all commands and their options
{ # Global options global: { verbose: '--verbose', cwd: '--cwd STRING', config: '--config STRING' }, auth: { # Provider ID for quick setup (e.g., openai-native, anthropic, moonshot) provider: '--provider STRING', # API key for the provider apikey: '--apikey STRING', # Model ID to configure (e.g., gpt-4o, claude-sonnet-4-6, kimi-k2.5) modelid: '--modelid STRING', # Base URL (optional, only for openai provider) baseurl: '--baseurl STRING' }, task: { # Run in plan mode plan: '--plan', # Output messages as JSON instead of styled text json: '--json', # Enable auto-approve all actions auto_approve: '--auto-approve', # Reasoning effort level between none|low|medium|high|xhigh thinking: '--thinking STRING', # Context compaction mode: agentic|basic|off compaction: '--compaction STRIG', # Open the terminal user interface (TUI) for interactive sessions tui: '--tui', # Session ID to resume, or nil for a new session id: '--id STRING', # Provider to use for the session provider: '--provider STRING', # API key to use for the session key: '--key STRING', # Model to use for the task model: '--model STRING', # Override the default system prompt system: '--system STRING', # Start a session that runs in the background hub zen: '--zen', # Number of maximum consecutive mistakes (retries) before exiting retries: '--retries INTEGER', # Optional timeout in seconds (applies only when provided) timeout: '--timeout INTEGER', # Run in Agent Client Protocol (ACP) mode for editor integration acp: '--acp', # Use isolated local state at this directory path data_dir: '--data-dir STRING', # Path to additional hooks directory for runtime hook injection hooks_dir: '--hooks-dir STRING', # Auto-create a detached git worktree under ~/.cline/worktrees/ and run the task there worktree: '--worktree', # Run the kanban app kanban: '--kanban' } }
Public API collapse
-
#cline_pid ⇒ Integer?
readonly
The PID of the running Cline process, if any.
-
#session ⇒ Session?
readonly
The current or last session handled by the Cline process, if any.
Public API collapse
-
#auth(**kwargs) ⇒ Hash{Symbol => Object}
Authenticate the CLI.
-
#initialize(stdout_echo: false, **kwargs) ⇒ Cli
constructor
Constructor.
-
#interrupt ⇒ Object
Interrupt the running Cline command.
-
#task(prompt, on_message: nil, on_question: nil, monitoring_interval_secs: 1, **kwargs) ⇒ Hash{Symbol => Object}
Start a task by sending a prompt.
Methods included from Utils::Logger
#log_debug, sanitize_pty_output
Constructor Details
#initialize(stdout_echo: false, **kwargs) ⇒ Cli
Constructor
92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/cline/cli.rb', line 92 def initialize(stdout_echo: false, **kwargs) @global_options = (**kwargs) @stdout_echo = stdout_echo @config_dir = kwargs[:config] @cline_pid = nil # [Session] Session associated to this CLI run. # The session is discovered using: # 1. Cline logs appearing after executing CLI that contain a session ID. # 2. The session corresponding to this ID. @session = nil end |
Instance Attribute Details
#cline_pid ⇒ Integer? (readonly)
Returns The PID of the running Cline process, if any.
241 242 243 |
# File 'lib/cline/cli.rb', line 241 def cline_pid @cline_pid end |
#session ⇒ Session? (readonly)
Returns The current or last session handled by the Cline process, if any.
244 245 246 |
# File 'lib/cline/cli.rb', line 244 def session @session end |
Instance Method Details
#auth(**kwargs) ⇒ Hash{Symbol => Object}
Authenticate the CLI
108 109 110 |
# File 'lib/cline/cli.rb', line 108 def auth(**kwargs) run_cli(command: 'auth', args: (**kwargs)) end |
#interrupt ⇒ Object
Interrupt the running Cline command
247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/cline/cli.rb', line 247 def interrupt if cline_pid log_debug "Interrupt current command with PID #{cline_pid}" all_pids = [cline_pid] + get_child_pids_recursive(cline_pid) log_debug "Found process tree PIDs: #{all_pids.join(', ')}" @interrupted_on_purpose = true all_pids.reverse.each do |pid| log_debug "Kill process #{pid}" Utils::Os.kill(pid) end else log_debug 'No Cline command started, so no need to interrupt anything' end end |
#task(prompt, on_message: nil, on_question: nil, monitoring_interval_secs: 1, **kwargs) ⇒ Hash{Symbol => Object}
Start a task by sending a prompt
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/cline/cli.rb', line 129 def task(prompt, on_message: nil, on_question: nil, monitoring_interval_secs: 1, **kwargs) result = {} start_time = Time.now stripped_prompt = prompt&.strip task_args = (**kwargs) # Use a prompt temporary file if the prompt is too long to fit in the command line. ( if stripped_prompt && stripped_prompt.size + 2 > Utils::Os.max_cmd_length - (Utils::Os.cline_exe + task_args).map { |arg| "\"#{arg}\"" }.join(' ').size proc do |&run_block| Utils::File.with_temp_dir do |tmp_dir| prompt_file = "#{tmp_dir}/prompt.txt" File.write(prompt_file, stripped_prompt) run_block.call( "The user prompt is given to you in the file `#{File.(prompt_file)}`. " \ 'You have to read it and treat its content as the user prompt.' ) end end else proc { |&run_block| run_block.call(stripped_prompt) } end ).call do |prompt_arg| session_monitor_thread = nil cli_running = true begin result = run_cli( args: task_args + (prompt_arg ? [prompt_arg] : []), on_start: proc do |_reader, writer, _pid| session_monitor_thread = Thread.new do # Start monitoring logs to get the session ID. # Wait for config and logs to exist. sleep 0.1 while cli_running && !config&.logs # Monitor logs to get the session ID session_id = nil config&.logs&.monitor( on_log: proc do |log, _last| session_id = log.properties.ulid if !session_id && log.is_a?(Log) && log.properties&.ulid end, from: start_time ) do sleep 0.1 while cli_running && !session_id end # If CLI has finished, session_id should be already discovered, unless the session could not be created. # If CLI has not finished yet, the we have the session ID discovered already. # So it means that if session_id is nil, there has been a problem (like core dump). if session_id log_debug "Found Cline session ID #{session_id}" # First get Cline models cline_models = config.data&.cline_models || Data.vscode&.cline_models # Wait for the CLI to create the session for real sleep 0.1 while cli_running && !config.sessions(cline_models:) config.sessions(cline_models:) unless cli_running while cli_running && !config.sessions[session_id] sleep 0.1 config.sessions.refresh! end # If CLI has finished, then the session should exist, unless there has been a problem (like file system issue). @session = config.sessions && config.sessions[session_id] # Now monitor the session messages for reporting and possible user callback # [Hash{Integer => Usage}] All usages, per timestamp, for logging purposes usages = {} @session&.( on_message: proc do |, last, previous_version| log_debug do usages[.ts] = .usage if .usage last_usage = usages.values.last prefix = "[#{..strftime('%H:%M:%S')}]#{ unless last_usage.nil? " (#{HumanNumber.currency(usages.values.map { |usage| usage.cost || 0.0 }.sum, currency_code: 'USD')} " \ "#{HumanNumber.human_number(last_usage.context_tokens, max_digits: 2)}" \ "/#{HumanNumber.human_number(last_usage.context_tokens_limit || 0, max_digits: 2)})" end } - " "#{prefix}#{.to_human(limit: 128 - prefix.size)}" end # Call the user callback if any &.call(, last, previous_version) # If the message is the last one and the agent has asked a question, call the corresponding callback if last last_content = .content&.last if last_content&.type == 'tool_use' && last_content.name == 'ask_question' unless on_question raise UnexpectedInteractiveSessionError, "Unexpected interactive session with assistant asking question #{last_content.input&.question}" end writer.puts(on_question.call(last_content.input)) end end end, monitoring_interval_secs: ) do sleep 0.1 while cli_running end end end end ) ensure cli_running = false session_monitor_thread&.join end end if @session result[:message] = @session.&.reverse_each&.find { || .role == 'assistant' } result[:status] = @session.status result[:error] = config.logs.logs(from: start_time).reverse_each.find { |log| log.severity == 'error' } if @session.status == 'failed' end result end |