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
@@ -0,0 +1,43 @@
1
+ module Cline
2
+ module Utils
3
+ module Os
4
+ # OS utils for host OS linux
5
+ module Linux
6
+ include Logger
7
+
8
+ # Get the user home directory path
9
+ #
10
+ # @return [String] Normalized absolute path to user home directory
11
+ def user_home_dir
12
+ @user_home_dir ||= `eval echo ~$USER`.strip
13
+ end
14
+
15
+ # Kill a process
16
+ # Handles errors gracefully in case the process has already disappeared.
17
+ #
18
+ # @param pid [Integer] Process to kill
19
+ def kill(pid)
20
+ Process.kill('TERM', pid)
21
+ rescue Errno::ESRCH
22
+ # Could be that the process naturally died before we interrupted it
23
+ log_debug "Process #{pid} was already killed"
24
+ end
25
+
26
+ # @return [Array<String>] The Cline executable command line (can also have some arguments)
27
+ def cline_exe
28
+ ['cline']
29
+ end
30
+
31
+ # @return [String] The user applications data directory
32
+ def user_app_data_dir
33
+ "#{user_home_dir}/.config"
34
+ end
35
+
36
+ # @return [Integer] Maximum length a command line can have
37
+ def max_cmd_length
38
+ @max_cmd_length ||= Integer(`getconf ARG_MAX`.strip)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,46 @@
1
+ module Cline
2
+ module Utils
3
+ module Os
4
+ # OS utils for host OS mingw32
5
+ module Mingw32
6
+ # Get the user home directory path
7
+ #
8
+ # @return [String] Normalized absolute path to user home directory
9
+ def user_home_dir
10
+ ENV['USERPROFILE'].gsub('\\', '/')
11
+ end
12
+
13
+ # Kill a process.
14
+ # Handles errors gracefully in case the process has already disappeared.
15
+ #
16
+ # @param pid [Integer] Process to kill
17
+ def kill(pid)
18
+ # Don't use Process.kill on Windows, because the killed process will have an exit status 0, which is incorrect.
19
+ # TODO: Use Process.kill here when Process.kill will be fixed on Windows systems.
20
+ system("taskkill /f /pid #{pid}")
21
+ end
22
+
23
+ # @return [Array<String>] The Cline executable command line (can also have some arguments)
24
+ def cline_exe
25
+ # As this CLI will be used with PTY.spawn and we want multiline support,
26
+ # don't use cline.cmd npm wrapper as it treats "\n" as new command lines.
27
+ # Therefore we use the node.exe binary directly.
28
+ @cline_exe ||= [
29
+ 'node.exe',
30
+ "#{::File.dirname(`where cline.cmd`.split("\n").first)}/node_modules/cline/bin/cline"
31
+ ]
32
+ end
33
+
34
+ # @return [String] The user applications data directory
35
+ def user_app_data_dir
36
+ ENV['APPDATA'] || raise('APPDATA environment variable should be set to know the applications data dir')
37
+ end
38
+
39
+ # @return [Integer] Maximum length a command line can have
40
+ def max_cmd_length
41
+ 8191
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,31 @@
1
+ require 'os'
2
+
3
+ module Cline
4
+ module Utils
5
+ # Provide OS-specific helpers
6
+ module Os
7
+ class << self
8
+ # @return [String] The Host OS that has been installed
9
+ attr_accessor :installed_host_os
10
+
11
+ # Install OS-specific methods in this module
12
+ #
13
+ # @param host_os [String] Host OS for which we install the methods
14
+ def self.install_os_methods(host_os: OS.host_os)
15
+ # Auto-extend with correct OS implementation at load time
16
+ require_relative "os/#{host_os}.rb"
17
+ host_os_module = Os.const_get(host_os.split('_').map(&:capitalize).join.to_sym)
18
+ Os.installed_host_os = host_os
19
+ # Clean eventually other methods that were installed before
20
+ host_os_module.instance_methods.each do |method|
21
+ undef_method(method) if method_defined?(method)
22
+ define_method(method, host_os_module.instance_method(method))
23
+ end
24
+ end
25
+
26
+ # Install the OS-specific methods
27
+ install_os_methods
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,290 @@
1
+ require 'forwardable'
2
+ require 'shale'
3
+
4
+ module Cline
5
+ module Utils
6
+ # Provide some utility methods that are related to schema handling
7
+ module Schema
8
+ # @!group Internal
9
+
10
+ # Provide a class that can be used by Shale to read and write maps of objects (dynamic keys), like this:
11
+ # {
12
+ # "key1": { ... another Shale structure ... },
13
+ # "key2": { ... another Shale structure ... },
14
+ # ...
15
+ # "keyN": { ... another Shale structure ... }
16
+ # }
17
+ #
18
+ # @param shale_type [Symbol, Class] The shale type that this class should expect for the map's values
19
+ # @return [Class] Class that can be used in an attribute declaration
20
+ def self.map(shale_type)
21
+ shale_type = Shale::Type.lookup(shale_type) if shale_type.is_a?(Symbol)
22
+ # Give a name to this class because Shale needs it to reference it in its accessors.
23
+ # That also allows us to not redefine the same class several times.
24
+ external_name = :"MapOf#{shale_type.to_s.gsub(':', '')}"
25
+ unless Schema.const_defined?(external_name)
26
+ schema_class = Class.new(Cline::Schema) do
27
+ extend Forwardable
28
+
29
+ # @!group Public API
30
+
31
+ include Enumerable
32
+
33
+ def_delegators :elements_hash, *%i[[] each empty? key? keys size to_hash values]
34
+
35
+ # Constructor
36
+ #
37
+ # @param elements_hash [Hash{String => shale_type}] The elements to initialize the structure with
38
+ def initialize(elements_hash = {})
39
+ super()
40
+ @elements_hash = elements_hash
41
+ end
42
+
43
+ # Set a key and its corresponding value.
44
+ # Handle potential casting for the value.
45
+ #
46
+ # @param key [Object] The key to set.
47
+ # @param value [Object] The value to set.
48
+ def []=(key, value)
49
+ elements_hash[key] = self.class.value_type.cast(value)
50
+ end
51
+
52
+ # Equality check
53
+ #
54
+ # @param other [Object] The other to check equality with
55
+ # @return [Boolean] True if objects are equal
56
+ def ==(other)
57
+ other.is_a?(self.class) &&
58
+ other.elements_hash == elements_hash
59
+ end
60
+
61
+ # @!group Internal
62
+
63
+ # @return [Hash] The elements hash taken from the extra attributes
64
+ attr_accessor :elements_hash
65
+
66
+ class << self
67
+ attr_accessor :value_type, :external_name
68
+
69
+ # Hook called when a subclass inherits our class
70
+ #
71
+ # @param subclass [Class] The inheriting class
72
+ def inherited(subclass)
73
+ super
74
+ subclass.value_type = value_type
75
+ end
76
+
77
+ # Parse a Hash object and instantiate the proper instance from it.
78
+ #
79
+ # @param hash [Hash] Data
80
+ # @param args [Array] Remaining arguments to be transferred to Shale
81
+ # @param kwargs [Hash] Remaining kwargs to be transferred to Shale
82
+ # @return [Schema] Corresponding instance
83
+ def of_hash(hash, *args, **kwargs)
84
+ instance = super
85
+ # Move the extra attributes into properly constructed elements_hash
86
+ instance.elements_hash =
87
+ if instance.extra_attributes.nil?
88
+ {}
89
+ else
90
+ instance.extra_attributes.to_h do |key, value|
91
+ [
92
+ key,
93
+ @value_type.of_hash(value, *args, **kwargs)
94
+ ]
95
+ end
96
+ end
97
+ instance
98
+ end
99
+
100
+ # Get a Hash object from an instance.
101
+ #
102
+ # @param instance [Schema] Object to serialize to a Hash
103
+ # @param args [Array] Remaining arguments to be transferred to Shale
104
+ # @param kwargs [Hash] Remaining kwargs to be transferred to Shale
105
+ # @return [Hash] Corresponding hash
106
+ def as_hash(instance, *args, **kwargs)
107
+ # Set the extra_attributes properly
108
+ unless instance.elements_hash.empty?
109
+ instance.extra_attributes = instance.elements_hash.to_h do |key, value|
110
+ [
111
+ key,
112
+ @value_type.as_hash(value, *args, **kwargs)
113
+ ]
114
+ end
115
+ end
116
+ super
117
+ end
118
+
119
+ # Cast an input value to this Schema object.
120
+ # Allow the attribute to be initialized directly using its Hash form.
121
+ #
122
+ # @param value [Schema, Hash, nil] The value that could be used to initialize a new instance of this attribute.
123
+ # @return [Schema, nil] The corresponding instance, or nil if none.
124
+ def cast(value)
125
+ return nil if value.nil?
126
+
127
+ # We expect the value to be a Hash of values that can themselves be cast using the Shale type, or a new instance already initialized.
128
+ if value.is_a?(self)
129
+ value
130
+ elsif value.is_a?(Hash)
131
+ new(value.to_h { |k, v| [k, value_type.cast(v)] })
132
+ else
133
+ raise "Unable to cast #{value} into #{name}"
134
+ end
135
+ end
136
+
137
+ # Return the class name
138
+ #
139
+ # @return [String] The class name
140
+ def to_s
141
+ "::Cline::Utils::Schema::#{@external_name}"
142
+ end
143
+ end
144
+ end
145
+ schema_class.value_type = shale_type
146
+ schema_class.external_name = external_name
147
+ Schema.const_set(external_name, schema_class)
148
+ end
149
+ Schema.const_get(external_name)
150
+ end
151
+
152
+ # Provide a class that can be used by Shale to read and write arrays of objects, like this:
153
+ # [
154
+ # { ... another Shale structure ... },
155
+ # { ... another Shale structure ... },
156
+ # ...
157
+ # { ... another Shale structure ... }
158
+ # ]
159
+ # We don't use the native Shale collection's feature as it does not support type casting at element,
160
+ # and so prevents initialization like that: my_object.my_collection = [{ attr: 1 }, { attr: 2 }].
161
+ #
162
+ # @param shale_type [Symbol, Class] The shale type that this class should expect for the array's values
163
+ # @return [Class] Class that can be used in an attribute declaration
164
+ def self.collection(shale_type)
165
+ shale_type = Shale::Type.lookup(shale_type) if shale_type.is_a?(Symbol)
166
+ # Give a name to this class because Shale needs it to reference it in its accessors.
167
+ # That also allows us to not redefine the same class several times.
168
+ external_name = :"CollectionOf#{shale_type.to_s.gsub(':', '')}"
169
+ unless Schema.const_defined?(external_name)
170
+ schema_class = Class.new(Cline::Schema) do
171
+ extend Forwardable
172
+
173
+ # @!group Public API
174
+
175
+ include Enumerable
176
+
177
+ def_delegators :elements, *%i[[] each empty? last size]
178
+
179
+ # Constructor
180
+ #
181
+ # @param elements [Array<shale_type>] The elements to initialize the structure with
182
+ def initialize(elements = [])
183
+ super()
184
+ @elements = elements
185
+ end
186
+
187
+ # Set a value at a given index.
188
+ # Handle potential casting for the value.
189
+ #
190
+ # @param idx [Integer] The integer to set.
191
+ # @param value [Object] The value to set.
192
+ def []=(idx, value)
193
+ elements[idx] = self.class.value_type.cast(value)
194
+ end
195
+
196
+ # Append a value.
197
+ # Handle potential casting for the value.
198
+ #
199
+ # @param value [Object] The value to set.
200
+ def <<(value)
201
+ elements << self.class.value_type.cast(value)
202
+ end
203
+
204
+ # Equality check
205
+ #
206
+ # @param other [Object] The other to check equality with
207
+ # @return [Boolean] True if objects are equal
208
+ def ==(other)
209
+ other.is_a?(self.class) &&
210
+ other.elements == elements
211
+ end
212
+
213
+ # @!group Internal
214
+
215
+ # Output this object as a Ruby object that can be JSON-serialized.
216
+ #
217
+ # @return [Object] Ruby object ready for JSON
218
+ def to_hash
219
+ elements.map { |element| element.respond_to?(:to_hash) ? element.to_hash : element }
220
+ end
221
+
222
+ # @return [Hash] The elements array
223
+ attr_accessor :elements
224
+
225
+ class << self
226
+ attr_accessor :value_type, :external_name
227
+
228
+ # Hook called when a subclass inherits our class
229
+ #
230
+ # @param subclass [Class] The inheriting class
231
+ def inherited(subclass)
232
+ super
233
+ subclass.value_type = value_type
234
+ end
235
+
236
+ # Parse a Ruby object and instantiate the proper instance from it.
237
+ #
238
+ # @param hash [Object] Data
239
+ # @param args [Array] Remaining arguments to be transferred to Shale
240
+ # @param kwargs [Hash] Remaining kwargs to be transferred to Shale
241
+ # @return [Schema] Corresponding instance
242
+ def of_hash(hash, *args, **kwargs)
243
+ new(hash.map { |element| value_type.of_hash(element, *args, **kwargs) })
244
+ end
245
+
246
+ # Get a Ruby object from an instance.
247
+ #
248
+ # @param instance [Schema] Object to serialize to a Hash
249
+ # @param args [Array] Remaining arguments to be transferred to Shale
250
+ # @param kwargs [Hash] Remaining kwargs to be transferred to Shale
251
+ # @return [Object] Corresponding Ruby object
252
+ def as_hash(instance, *args, **kwargs)
253
+ instance.elements.map { |element| value_type.as_hash(element, *args, **kwargs) }
254
+ end
255
+
256
+ # Cast an input value to this Schema object.
257
+ # Allow the attribute to be initialized directly using its Array form.
258
+ #
259
+ # @param value [Schema, Array, nil] The value that could be used to initialize a new instance of this attribute.
260
+ # @return [Schema, nil] The corresponding instance, or nil if none.
261
+ def cast(value)
262
+ return nil if value.nil?
263
+
264
+ # We expect the value to be a Array of values that can themselves be cast using the Shale type, or a new instance already initialized.
265
+ if value.is_a?(self)
266
+ value
267
+ elsif value.is_a?(Array)
268
+ new(value.map { |element| value_type.cast(element) })
269
+ else
270
+ raise "Unable to cast #{value} into #{name}"
271
+ end
272
+ end
273
+
274
+ # Return the class name
275
+ #
276
+ # @return [String] The class name
277
+ def to_s
278
+ "::Cline::Utils::Schema::#{@external_name}"
279
+ end
280
+ end
281
+ end
282
+ schema_class.value_type = shale_type
283
+ schema_class.external_name = external_name
284
+ Schema.const_set(external_name, schema_class)
285
+ end
286
+ Schema.const_get(external_name)
287
+ end
288
+ end
289
+ end
290
+ end
@@ -0,0 +1,6 @@
1
+ module Cline
2
+ # @!group Public API
3
+
4
+ # Gem version
5
+ VERSION = '1.0.0'
6
+ end
@@ -0,0 +1,25 @@
1
+ module Cline
2
+ # A workspace defined in a directory
3
+ class Workspace
4
+ # @!group Public API
5
+
6
+ include Serializable::Dir
7
+
8
+ # Get the workspace settings
9
+ #
10
+ # @param create [Boolean] Should the data be created if it does not exist?
11
+ # @return [WorkspaceSettings, nil] The workspace settings or nil if none
12
+ def settings(create: self.create)
13
+ @settings ||= WorkspaceSettings.from_cline_data(dir, create:)
14
+ end
15
+
16
+ # Equality check
17
+ #
18
+ # @param other [Object] The other to check equality with
19
+ # @return [Boolean] True if objects are equal
20
+ def ==(other)
21
+ other.is_a?(Workspace) &&
22
+ other.settings == settings
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ module Cline
2
+ # Workspace settings from workspaceState.json
3
+ class WorkspaceSettings < Schema
4
+ # @!group Public API
5
+
6
+ Serializable::ClineData.include_for(self, 'workspaceState.json')
7
+
8
+ # @return [Hash{String => Boolean}] Local skills toggle states
9
+ attribute :local_skills_toggles, Utils::Schema.map(:boolean)
10
+
11
+ # @return [Hash{String => Boolean}] Local Cline rules toggle states
12
+ attribute :local_cline_rules_toggles, Utils::Schema.map(:boolean)
13
+
14
+ # @return [Hash{String => Boolean}] Local Agents rules toggle states
15
+ attribute :local_agents_rules_toggles, Utils::Schema.map(:boolean)
16
+
17
+ # @return [Hash{String => Boolean}] Workflow toggle states
18
+ attribute :workflow_toggles, Utils::Schema.map(:boolean)
19
+
20
+ # @return [Hash{String => Boolean}] Local Windsurf rules toggle states
21
+ attribute :local_windsurf_rules_toggles, Utils::Schema.map(:boolean)
22
+
23
+ # @return [Hash{String => Boolean}] Local Cursor rules toggle states
24
+ attribute :local_cursor_rules_toggles, Utils::Schema.map(:boolean)
25
+
26
+ # @return [Integer] VSCode migration version
27
+ attribute :__vscode_migration_version, :integer
28
+ end
29
+ end
@@ -0,0 +1,8 @@
1
+ module Cline
2
+ # Provide a set of wokspaces from a directory
3
+ class Workspaces
4
+ # @!group Public API
5
+
6
+ Utils::EnumerableDirObjects.include_for(self, Workspace)
7
+ end
8
+ end
data/lib/cline.rb ADDED
@@ -0,0 +1,22 @@
1
+ require 'zeitwerk'
2
+
3
+ Zeitwerk::Loader.for_gem.setup
4
+
5
+ # All Cline objects are accessible here.
6
+ module Cline
7
+ class << self
8
+ # @!group Public API
9
+
10
+ # Configure the behaviour of the cline-rb Rubygem
11
+ #
12
+ # @yield [config] The configuration
13
+ def configure
14
+ yield config
15
+ end
16
+
17
+ # @return [Configuration] The cline-rb Rubygem configuration
18
+ def config
19
+ @config ||= Configuration.new
20
+ end
21
+ end
22
+ end