cline-rb 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +139 -0
  3. data/README.md +1216 -0
  4. data/TODO.md +2 -0
  5. data/lib/cline/cli.rb +373 -0
  6. data/lib/cline/config.rb +100 -0
  7. data/lib/cline/configuration.rb +23 -0
  8. data/lib/cline/data.rb +119 -0
  9. data/lib/cline/file_content.rb +33 -0
  10. data/lib/cline/global_settings.rb +17 -0
  11. data/lib/cline/global_state/api_providers.rb +48 -0
  12. data/lib/cline/global_state/auto_approval.rb +73 -0
  13. data/lib/cline/global_state/browser.rb +52 -0
  14. data/lib/cline/global_state/features.rb +56 -0
  15. data/lib/cline/global_state/general.rb +77 -0
  16. data/lib/cline/global_state/models.rb +127 -0
  17. data/lib/cline/global_state/toggles.rb +33 -0
  18. data/lib/cline/global_state/workspace.rb +41 -0
  19. data/lib/cline/global_state.rb +16 -0
  20. data/lib/cline/log.rb +288 -0
  21. data/lib/cline/logs.rb +136 -0
  22. data/lib/cline/mcp_settings.rb +30 -0
  23. data/lib/cline/model.rb +47 -0
  24. data/lib/cline/models.rb +11 -0
  25. data/lib/cline/overlay_hash.rb +125 -0
  26. data/lib/cline/providers.rb +59 -0
  27. data/lib/cline/schema.rb +144 -0
  28. data/lib/cline/secret_string.rb +83 -0
  29. data/lib/cline/secrets.rb +119 -0
  30. data/lib/cline/serializable/cline_data.rb +131 -0
  31. data/lib/cline/serializable/dir.rb +81 -0
  32. data/lib/cline/serializable/file.rb +106 -0
  33. data/lib/cline/session.rb +87 -0
  34. data/lib/cline/session_data.rb +154 -0
  35. data/lib/cline/session_message.rb +178 -0
  36. data/lib/cline/session_messages.rb +61 -0
  37. data/lib/cline/sessions.rb +30 -0
  38. data/lib/cline/skill.rb +148 -0
  39. data/lib/cline/skills.rb +8 -0
  40. data/lib/cline/task.rb +75 -0
  41. data/lib/cline/task_message.rb +247 -0
  42. data/lib/cline/task_messages.rb +11 -0
  43. data/lib/cline/tasks.rb +30 -0
  44. data/lib/cline/usage.rb +37 -0
  45. data/lib/cline/utils/enumerable_dir_objects.rb +103 -0
  46. data/lib/cline/utils/file.rb +71 -0
  47. data/lib/cline/utils/file_monitor.rb +56 -0
  48. data/lib/cline/utils/logger.rb +37 -0
  49. data/lib/cline/utils/os/linux.rb +43 -0
  50. data/lib/cline/utils/os/mingw32.rb +46 -0
  51. data/lib/cline/utils/os.rb +31 -0
  52. data/lib/cline/utils/schema.rb +290 -0
  53. data/lib/cline/version.rb +6 -0
  54. data/lib/cline/workspace.rb +25 -0
  55. data/lib/cline/workspace_settings.rb +29 -0
  56. data/lib/cline/workspaces.rb +8 -0
  57. data/lib/cline.rb +22 -0
  58. metadata +249 -0
data/lib/cline/log.rb ADDED
@@ -0,0 +1,288 @@
1
+ module Cline
2
+ # Cline Log entry
3
+ class Log < Schema
4
+ # @!group Public API
5
+
6
+ # Cause of an API call error
7
+ class ErrorCause < Schema
8
+ # @!group Public API
9
+
10
+ # @return [String, nil] Error code (e.g. "ConnectionRefused")
11
+ attribute :code, :string
12
+
13
+ # @return [String, nil] URL path that caused the error
14
+ attribute :path, :string
15
+
16
+ # @return [Integer, nil] Error number
17
+ attribute :errno, :integer
18
+ end
19
+
20
+ # Individual API call error (used in errors[], aggregateErrors[], and lastError)
21
+ class ApiError < Schema
22
+ # @!group Public API
23
+
24
+ # @return [String, nil] Error type identifier
25
+ attribute :type, :string
26
+
27
+ # @return [String, nil] Human-readable error message
28
+ attribute :message, :string
29
+
30
+ # @return [String, nil] Error stack trace
31
+ attribute :stack, :string
32
+
33
+ # @return [String, nil] Error class name (e.g. "AI_APICallError")
34
+ attribute :name, :string
35
+
36
+ # @return [String, nil] API endpoint URL
37
+ attribute :url, :string
38
+
39
+ # @return [Boolean, nil] Whether the request is retryable
40
+ attribute :is_retryable, :boolean
41
+
42
+ # @return [ErrorCause, nil] Underlying cause of the error
43
+ attribute :cause, ErrorCause
44
+ end
45
+
46
+ # Top-level error wrapper (the err field in error logs)
47
+ class Error < Schema
48
+ # @!group Public API
49
+
50
+ # @return [String, nil] Error type identifier
51
+ attribute :type, :string
52
+
53
+ # @return [String, nil] Human-readable error message
54
+ attribute :message, :string
55
+
56
+ # @return [String, nil] Error stack trace
57
+ attribute :stack, :string
58
+
59
+ # @return [String, nil] Error class name (e.g. "AI_RetryError")
60
+ attribute :name, :string
61
+
62
+ # @return [String, nil] Reason for the error (e.g. "maxRetriesExceeded")
63
+ attribute :reason, :string
64
+
65
+ # @return [Array<ApiError>, nil] Individual errors in a retry chain
66
+ attribute :errors, Utils::Schema.collection(ApiError)
67
+
68
+ # @return [Array<ApiError>, nil] Aggregate errors from retry attempts
69
+ attribute :aggregate_errors, Utils::Schema.collection(ApiError)
70
+
71
+ # @return [ApiError, nil] Last error in a retry chain
72
+ attribute :last_error, ApiError
73
+ end
74
+
75
+ # Event-specific properties for telemetry entries
76
+ class Properties < Schema
77
+ # @!group Public API
78
+
79
+ # @return [String, nil] Unique identifier for tasks and sessions
80
+ attribute :ulid, :string
81
+
82
+ # @return [String, nil] API provider name (e.g. "cline")
83
+ attribute :api_provider, :string
84
+
85
+ # @return [String, nil] Agent identifier
86
+ attribute :agent_id, :string
87
+
88
+ # @return [String, nil] Agent kind (e.g. "team_lead")
89
+ attribute :agent_kind, :string
90
+
91
+ # @return [String, nil] Conversation identifier
92
+ attribute :conversation_id, :string
93
+
94
+ # @return [Boolean, nil] Whether this is a subagent
95
+ attribute :is_subagent, :boolean
96
+
97
+ # @return [String, nil] Team identifier
98
+ attribute :team_id, :string
99
+
100
+ # @return [String, nil] Team name
101
+ attribute :team_name, :string
102
+
103
+ # @return [String, nil] Team role (e.g. "lead")
104
+ attribute :team_role, :string
105
+
106
+ # @return [String, nil] Lead agent identifier for teams
107
+ attribute :lead_agent_id, :string
108
+
109
+ # @return [String, nil] Model provider (e.g. "cline")
110
+ attribute :provider, :string
111
+
112
+ # @return [String, nil] Model identifier (e.g. "deepseek/deepseek-v4-flash")
113
+ attribute :model_id, :string
114
+
115
+ # @return [String, nil] Model name (e.g. "deepseek/deepseek-v4-flash")
116
+ attribute :model, :string
117
+
118
+ # @return [String, nil] Source of the conversation turn (e.g. "user", "assistant")
119
+ attribute :source, :string
120
+
121
+ # @return [String, nil] Mode of operation (e.g. "act")
122
+ attribute :mode, :string
123
+
124
+ # @return [String, nil] Timestamp in ISO 8601 format
125
+ attribute :timestamp, :string
126
+
127
+ # @return [String, nil] Run identifier for agent runs
128
+ attribute :run_id, :string
129
+
130
+ # @return [String, nil] Status of a run or task (e.g. "running", "completed")
131
+ attribute :status, :string
132
+
133
+ # @return [Integer, nil] Iteration number
134
+ attribute :iteration, :integer
135
+
136
+ # @return [String, nil] Event type (e.g. "run-started", "turn-started")
137
+ attribute :event_type, :string
138
+
139
+ # @return [String, nil] Session identifier
140
+ attribute :session_id, :string
141
+
142
+ # @return [Boolean, nil] Whether tools are enabled
143
+ attribute :enable_tools, :boolean
144
+
145
+ # @return [Boolean, nil] Whether spawn agent is enabled
146
+ attribute :enable_spawn_agent, :boolean
147
+
148
+ # @return [Boolean, nil] Whether agent teams are enabled
149
+ attribute :enable_agent_teams, :boolean
150
+
151
+ # @return [Integer, nil] Input tokens count
152
+ attribute :tokens_in, :integer
153
+
154
+ # @return [Integer, nil] Output tokens count
155
+ attribute :tokens_out, :integer
156
+
157
+ # @return [Float, nil] Total cost of the API call
158
+ attribute :total_cost, :float
159
+
160
+ # @return [Integer, nil] Cache read tokens count
161
+ attribute :cache_read_tokens, :integer
162
+
163
+ # @return [Integer, nil] Cache write tokens count
164
+ attribute :cache_write_tokens, :integer
165
+
166
+ # @return [String, nil] Tool name used (e.g. "ask_question", "run_commands")
167
+ attribute :tool, :string
168
+
169
+ # @return [Boolean, nil] Whether the tool use was successful
170
+ attribute :success, :boolean
171
+
172
+ # @return [Integer, nil] Duration in milliseconds
173
+ attribute :duration_ms, :integer
174
+
175
+ # @return [String, nil] Provider identifier
176
+ attribute :provider_id, :string
177
+
178
+ # Workspace initialization properties
179
+
180
+ # @return [Integer, nil] Number of workspace roots
181
+ attribute :root_count, :integer
182
+
183
+ # @return [Array<String>, nil] VCS types (e.g. ["git"])
184
+ attribute :vcs_types, Utils::Schema.collection(:string)
185
+
186
+ # @return [Boolean, nil] Whether the workspace has multiple roots
187
+ attribute :is_multi_root, :boolean
188
+
189
+ # @return [Boolean, nil] Whether the workspace has git
190
+ attribute :has_git, :boolean
191
+
192
+ # @return [Boolean, nil] Whether the workspace has mercurial
193
+ attribute :has_mercurial, :boolean
194
+
195
+ # @return [Float, nil] Initialization duration in milliseconds
196
+ attribute :init_duration_ms, :float
197
+
198
+ # @return [Boolean, nil] Whether feature flags are enabled
199
+ attribute :feature_flag_enabled, :boolean
200
+
201
+ # @return [String, nil] Extension version (e.g. "3.0.7")
202
+ attribute :extension_version, :string
203
+
204
+ # @return [String, nil] Cline type (e.g. "cli")
205
+ attribute :cline_type, :string
206
+
207
+ # @return [String, nil] Platform name (e.g. "cline")
208
+ attribute :platform, :string
209
+
210
+ # @return [String, nil] Platform version (e.g. "v24.3.0")
211
+ attribute :platform_version, :string
212
+
213
+ # @return [String, nil] Operating system type (e.g. "win32")
214
+ attribute :os_type, :string
215
+
216
+ # @return [String, nil] Operating system version (e.g. "Windows 11 Pro")
217
+ attribute :os_version, :string
218
+
219
+ # @return [String, nil] Distinct identifier
220
+ attribute :distinct_id, :string
221
+
222
+ # @return [String, nil] Whether the session was restored from persistence
223
+ attribute :restored_from_persistence, :boolean
224
+
225
+ # All attributes are already snake case
226
+ cline_snake_attributes(*attributes.keys)
227
+ end
228
+
229
+ # @!group Public API
230
+
231
+ # @return [Integer] Log level number (e.g. 30 for info, 40 for warn)
232
+ attribute :level, :integer
233
+
234
+ # @return [String] Timestamp in ISO 8601 format
235
+ attribute :time, :string
236
+
237
+ # @return [Integer] Process ID
238
+ attribute :pid, :integer
239
+
240
+ # @return [String] Hostname of the machine
241
+ attribute :hostname, :string
242
+
243
+ # @return [String] Logger name (e.g. "cline.cli")
244
+ attribute :name, :string
245
+
246
+ # @return [String] Component name (e.g. "main")
247
+ attribute :component, :string
248
+
249
+ # @return [String] Log message
250
+ attribute :msg, :string
251
+
252
+ # @return [Boolean, nil] Whether the session is interactive
253
+ attribute :interactive, :boolean
254
+
255
+ # @return [Boolean, nil] Whether a prompt is present
256
+ attribute :has_prompt, :boolean
257
+
258
+ # @return [String, nil] Current working directory
259
+ attribute :cwd, :string
260
+
261
+ # @return [String, nil] Reason for a fallback or decision
262
+ attribute :reason, :string
263
+
264
+ # @return [String, nil] Backend routing mode (e.g. "env-managed", "auto")
265
+ attribute :backend_mode, :string
266
+
267
+ # @return [Boolean, nil] Whether the local backend is forced
268
+ attribute :force_local_backend, :boolean
269
+
270
+ # @return [String, nil] Telemetry sink name (e.g. "TelemetryLoggerSink")
271
+ attribute :telemetry_sink, :string
272
+
273
+ # @return [String, nil] Event name for telemetry entries (e.g. "workspace.initialized")
274
+ attribute :event, :string
275
+
276
+ # @return [Properties, nil] Event-specific properties for telemetry entries
277
+ attribute :properties, Properties
278
+
279
+ # @return [String, nil] Severity level for error/warn logs (e.g. "error", "warn")
280
+ attribute :severity, :string
281
+
282
+ # @return [String, nil] Provider identifier (e.g. "cline")
283
+ attribute :provider_id, :string
284
+
285
+ # @return [Error, nil] Error details for error logs
286
+ attribute :err, Error
287
+ end
288
+ end
data/lib/cline/logs.rb ADDED
@@ -0,0 +1,136 @@
1
+ require 'forwardable'
2
+ require 'json'
3
+
4
+ module Cline
5
+ # Cline Logs file content
6
+ class Logs
7
+ extend Forwardable
8
+ include Serializable::File
9
+
10
+ # @!group Public API
11
+
12
+ # Fetch logs
13
+ #
14
+ # @param from [Time, String, nil] The horizon (exclusive) from which we select lines. Can be one of:
15
+ # - [Time] The timestamp to get lines from
16
+ # - [String] The exact log line to start after
17
+ # - [nil] Get all log lines
18
+ # @return [Array<Log>] Logs list
19
+ def logs(from: nil)
20
+ logs_lines_from(from).map { |line| line.start_with?('{') ? Log.of_hash(JSON.parse(line)) : line }
21
+ end
22
+
23
+ # Delegates enumerating methods to the internal logs
24
+ def_delegators :logs, *%i[[] each empty? first last size]
25
+
26
+ # Add a new log line to the logs
27
+ #
28
+ # @param line [Log, String] The log entry to add (either a Log object or a raw string)
29
+ # @return [Logs] self
30
+ def <<(line)
31
+ logs_lines << (
32
+ if line.is_a?(Log)
33
+ line.to_cline_json
34
+ elsif line.is_a?(Hash)
35
+ Log.cast(line).to_cline_json
36
+ else
37
+ line
38
+ end
39
+ )
40
+ self
41
+ end
42
+
43
+ # Save the logs lines to the file
44
+ def save
45
+ raise 'This instance has not been initialized from a file' unless file
46
+
47
+ FileUtils.mkdir_p(::File.dirname(file))
48
+ ::File.write(file, logs_lines.map { |line| "#{line}\n" }.join)
49
+ end
50
+
51
+ # Monitor logs with a callback called when new or updated logs arrive
52
+ #
53
+ # @param on_log [#call] Block called each time there is a new log.
54
+ # * Param log [Log, String] Log that has happened, either as a Log object or as a String if it is not JSON.
55
+ # * Param last [Boolean] Is this the last log fetched from the logs?
56
+ # @param from [Time, String, nil] The horizon (exclusive) from which we select lines (see #logs)
57
+ # @param monitoring_interval_secs [Float] The monitoring interval in seconds
58
+ # @yield Optional code called while monitoring is in place.
59
+ # If used then monitoring is stopped at the end of the block's execution.
60
+ # @return [FileMonitor, nil] If no block has been given, return the monitor that needs to be
61
+ # stopped by the caller when monitoring should end.
62
+ def monitor(on_log:, from: nil, monitoring_interval_secs: 1, &)
63
+ # Keep the last log line that we have read
64
+ last_log = from
65
+ Logs.monitor_file_changes(
66
+ file,
67
+ on_change: proc do |_mtime|
68
+ refresh!
69
+ new_lines = logs_lines_from(last_log)
70
+ unless new_lines.empty?
71
+ last_idx = new_lines.size - 1
72
+ new_lines.each.with_index do |line, idx|
73
+ on_log.call(line.start_with?('{') ? Log.of_hash(JSON.parse(line)) : line, idx == last_idx)
74
+ end
75
+ last_log = new_lines.last
76
+ end
77
+ end,
78
+ monitoring_interval_secs:,
79
+ &
80
+ )
81
+ end
82
+
83
+ # Clear the cache in case the log file has changed
84
+ def refresh!
85
+ @logs_lines = nil
86
+ end
87
+
88
+ # Equality check
89
+ #
90
+ # @param other [Object] The other to check equality with
91
+ # @return [Boolean] True if objects are equal
92
+ def ==(other)
93
+ other.is_a?(Logs) &&
94
+ other.logs_lines == logs_lines
95
+ end
96
+
97
+ protected
98
+
99
+ # Get the logs lines
100
+ #
101
+ # @return [Array<String>] Logs lines
102
+ def logs_lines
103
+ @logs_lines ||= Utils::File.safe_read(file).split("\n")
104
+ end
105
+
106
+ private
107
+
108
+ # @!group Internal
109
+
110
+ # The regexp used to match string timestamps from log lines
111
+ LOG_ENTRY_TIME_REGEXP = /"time":"(\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ)"/
112
+ private_constant :LOG_ENTRY_TIME_REGEXP
113
+
114
+ # Get the logs lines from a given time or line
115
+ #
116
+ # @param from [Time, String, nil] The horizon (exclusive) from which we select lines (see #logs)
117
+ # @return [Array<String>] Selected logs lines
118
+ def logs_lines_from(from = nil)
119
+ if from
120
+ found_reverse_idx =
121
+ if from.is_a?(String)
122
+ logs_lines.reverse_each.find_index(from)
123
+ else
124
+ horizon = from.utc.strftime('%FT%T.%LZ')
125
+ logs_lines.reverse_each.find_index do |line|
126
+ match = line.match(LOG_ENTRY_TIME_REGEXP)
127
+ !match.nil? && match[1] <= horizon
128
+ end
129
+ end
130
+ found_reverse_idx ? logs_lines[(logs_lines.size - found_reverse_idx)..] : logs_lines
131
+ else
132
+ logs_lines
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,30 @@
1
+ module Cline
2
+ # MCP settings
3
+ class McpSettings < Schema
4
+ # @!group Public API
5
+ Serializable::ClineData.include_for(self, 'settings/cline_mcp_settings.json')
6
+
7
+ # Settings associated to 1 MCP server
8
+ class McpServer < Schema
9
+ # @!group Public API
10
+
11
+ # @return [Array<String>] List of tools that are automatically approved for this server
12
+ attribute :auto_approve, Utils::Schema.collection(:string)
13
+
14
+ # @return [Boolean] Flag indicating if this server is disabled
15
+ attribute :disabled, :boolean
16
+
17
+ # @return [Integer] Server timeout in seconds
18
+ attribute :timeout, :integer
19
+
20
+ # @return [String] Server connection type (e.g. "sse", "stdio")
21
+ attribute :type, :string
22
+
23
+ # @return [String] Server URL for SSE connections
24
+ attribute :url, :string
25
+ end
26
+
27
+ # @return [Hash] Set of MCP servers settings
28
+ attribute :mcp_servers, Utils::Schema.map(McpServer)
29
+ end
30
+ end
@@ -0,0 +1,47 @@
1
+ module Cline
2
+ # Cline model information
3
+ class Model < Schema
4
+ # @!group Public API
5
+
6
+ # @return [String] Model display name
7
+ attribute :name, :string
8
+
9
+ # @return [Integer] Maximum tokens output for this model
10
+ attribute :max_tokens, :integer
11
+
12
+ # @return [Integer] Context window size in tokens
13
+ attribute :context_window, :integer
14
+
15
+ # @return [Boolean] True if this model supports image inputs
16
+ attribute :supports_images, :boolean
17
+
18
+ # @return [Boolean] True if this model supports prompt caching
19
+ attribute :supports_prompt_cache, :boolean
20
+
21
+ # @return [Float] Input price per million tokens
22
+ attribute :input_price, :float
23
+
24
+ # @return [Float] Output price per million tokens
25
+ attribute :output_price, :float
26
+
27
+ # @return [Float, nil] Cache reads price per million tokens
28
+ attribute :cache_reads_price, :float
29
+
30
+ # @return [Float, nil] Cache writes price per million tokens
31
+ attribute :cache_writes_price, :float
32
+
33
+ # @return [String, nil] Model description
34
+ attribute :description, :string
35
+
36
+ # Thinking configuration for this model
37
+ class ThinkingConfig < Schema
38
+ # @!group Public API
39
+
40
+ # @return [Integer] Maximum thinking budget in tokens
41
+ attribute :max_budget, :integer
42
+ end
43
+
44
+ # @return [ThinkingConfig, nil] Thinking configuration
45
+ attribute :thinking_config, ThinkingConfig
46
+ end
47
+ end
@@ -0,0 +1,11 @@
1
+ module Cline
2
+ # Base class, dynamically defined
3
+ ModelMap = Utils::Schema.map(Model)
4
+
5
+ # Access cached Cline models
6
+ class Models < ModelMap
7
+ # @!group Public API
8
+
9
+ Serializable::ClineData.include_for(self, 'cache/cline_models.json')
10
+ end
11
+ end
@@ -0,0 +1,125 @@
1
+ require 'forwardable'
2
+
3
+ module Cline
4
+ # Class merging several Hash-like objects to give a uniform view of them through another Hash-like interface.
5
+ # This is used to provide unifide representations of objects present in both project and global configs.
6
+ # Write operations are performed on the top layer only.
7
+ class OverlayHash
8
+ extend Forwardable
9
+
10
+ # @!group Public API
11
+
12
+ include Enumerable
13
+
14
+ # Loop over all elements.
15
+ #
16
+ # @yield Optional code called for each key and value.
17
+ # @yieldparam key [Object] The key being iterated on.
18
+ # @yieldparam value [Object] The value being iterated on.
19
+ # @return [Enumerator] The enumerator if no block is given.
20
+ def each
21
+ return enum_for(:each) unless block_given?
22
+
23
+ seen = {}
24
+ @layers.each do |layer|
25
+ layer.each do |key, value|
26
+ next if seen.key?(key)
27
+
28
+ seen[key] = true
29
+ yield key, value
30
+ end
31
+ end
32
+ end
33
+
34
+ # Retrieve the value for a given key from the first layer that contains it.
35
+ #
36
+ # @param key [Object] The key to look up.
37
+ # @return [Object, nil] The value associated with the key, or +nil+ if not found in any layer.
38
+ def [](key)
39
+ @layers.each do |layer|
40
+ return layer[key] if layer.key?(key)
41
+ end
42
+ nil
43
+ end
44
+
45
+ # Check whether the given key exists in any layer.
46
+ #
47
+ # @param key [Object] The key to check for.
48
+ # @return [Boolean] +true+ if the key is present in at least one layer, +false+ otherwise.
49
+ def key?(key)
50
+ @layers.any? { |layer| layer.key?(key) }
51
+ end
52
+
53
+ # Return all unique keys across all layers, in priority order.
54
+ #
55
+ # @return [Array<Object>] The list of unique keys.
56
+ def keys
57
+ map { |k, _v| k }
58
+ end
59
+
60
+ # Return all values across all layers, in priority order.
61
+ #
62
+ # @return [Array<Object>] The list of values corresponding to the unique keys.
63
+ def values
64
+ map { |_k, v| v }
65
+ end
66
+
67
+ # Return the number of unique keys across all layers.
68
+ #
69
+ # @return [Integer] The total count of unique keys.
70
+ def size
71
+ keys.size
72
+ end
73
+
74
+ # Check whether the overlay hash contains any entries.
75
+ #
76
+ # @return [Boolean] +true+ if there are no entries across all layers, +false+ otherwise.
77
+ def empty?
78
+ none?
79
+ end
80
+
81
+ # Convert the overlay hash into a plain Ruby Hash.
82
+ #
83
+ # @return [Hash] A new hash containing all unique key-value pairs in priority order.
84
+ def to_h
85
+ each_with_object({}) do |(key, value), h|
86
+ h[key] = value
87
+ end
88
+ end
89
+
90
+ # Equality check
91
+ #
92
+ # @param other [Object] The other to check equality with
93
+ # @return [Boolean] True if objects are equal
94
+ def ==(other)
95
+ other.is_a?(OverlayHash) &&
96
+ other.layers == layers
97
+ end
98
+
99
+ # Delegate all write and singleton operations to the first layer
100
+ def_delegators :first_layer, *%i[dir new]
101
+
102
+ # @!group Internal
103
+
104
+ # Constructor
105
+ #
106
+ # @param layers [Array] List of Hash-like objects that should be served.
107
+ # In case of conflicting keys, the first ones in the list get priority.
108
+ # Write operations are performed on the first one only.
109
+ def initialize(*layers)
110
+ @layers = layers
111
+ end
112
+
113
+ protected
114
+
115
+ # @return [Array<Object>] The list of layers
116
+ attr_reader :layers
117
+
118
+ private
119
+
120
+ # @return [Object] First layer
121
+ def first_layer
122
+ @layers.first
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,59 @@
1
+ module Cline
2
+ # Providers configuration stored in settings/providers.json
3
+ class Providers < Schema
4
+ # @!group Public API
5
+
6
+ Serializable::ClineData.include_for(self, 'settings/providers.json')
7
+
8
+ # A provider entry with settings, update timestamp and token source
9
+ class ProviderEntry < Schema
10
+ # @!group Public API
11
+
12
+ # Settings for a single provider entry
13
+ class ProviderSettings < Schema
14
+ # @!group Public API
15
+
16
+ # Settings specific to a provider's reasoning configuration
17
+ class ReasoningSettings < Schema
18
+ # @!group Public API
19
+
20
+ # @return [Boolean] Whether reasoning is enabled
21
+ attribute :enabled, :boolean
22
+
23
+ # @return [String] The reasoning effort level (e.g. "xhigh")
24
+ attribute :effort, :string
25
+ end
26
+
27
+ # @return [String] The provider name
28
+ attribute :provider, :string
29
+
30
+ # @return [SecretString, nil] The API key for this provider
31
+ attribute :api_key, SecretString
32
+
33
+ # @return [String] The model identifier
34
+ attribute :model, :string
35
+
36
+ # @return [ReasoningSettings, nil] Optional reasoning configuration
37
+ attribute :reasoning, ReasoningSettings
38
+ end
39
+
40
+ # @return [ProviderSettings] The provider settings
41
+ attribute :settings, ProviderSettings
42
+
43
+ # @return [String] The timestamp when this provider was last updated
44
+ attribute :updated_at, :string
45
+
46
+ # @return [String] The token source (e.g. "manual")
47
+ attribute :token_source, :string
48
+ end
49
+
50
+ # @return [Integer] The version of the providers configuration
51
+ attribute :version, :integer
52
+
53
+ # @return [String] The last used provider identifier
54
+ attribute :last_used_provider, :string
55
+
56
+ # @return [Hash] The map of provider entries keyed by provider name
57
+ attribute :providers, Utils::Schema.map(ProviderEntry)
58
+ end
59
+ end