rubycode 0.1.4 → 0.1.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 38eb208124cc78e03b6a8fb2778309dc17999ed1ab50270fbbfc3635f5d144d2
4
- data.tar.gz: c02ee8d739bf3162d2e3e2fb5e16adcd00d9a373b3223e6e3d2ae6694f75c22d
3
+ metadata.gz: 50f110cea87f9ae98d0613a54a8feec231d1f050c62382b42428d4233bf871c0
4
+ data.tar.gz: d9a0171878bdcbad488afd81146a6887a2ea64a8d361d5932f9e5e162202d36e
5
5
  SHA512:
6
- metadata.gz: 29a2f388ff00077112e5f3697a1fbca342ba5a9974bf7b306c862f4b97f78585c3357366c6980ca5c71c21944e191243ac4e84e59f6451492e140fb711d15314
7
- data.tar.gz: bc30616c062dd24865f4ec9d2c93cae55569dec5d17c0648d39d6c6aa3059ca91d736b7867e4f0b7015de514182d145b47a5b93e7b297832134f26e922e6e93b
6
+ metadata.gz: eecbe47b72a9ea6b36c905f5b8560a6f8611c381ec3de35d4a50db991dc6576442d04604c51f87ee45f253862c7771cfd70e644833f317d137c33e9d1f40bdf2
7
+ data.tar.gz: 5c15e211f5605d8b986b49704b6d55ac3764364758efaca44d46f552a61ef6eecc993a8bf24eed55452776136859c361f91c1226c94cda7a32edf3f4ecc4c7d9
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.5] - 2026-03-08
4
+
5
+ ### Added
6
+ - **CLI Executable**: `rubycode_client` command for interactive chat after gem installation
7
+
8
+ ### Changed
9
+ - **README**: Reorganized Quick Start section to prioritize CLI usage
10
+ - **Testing**: Updated documentation to use `bin/test` command
11
+
3
12
  ## [0.1.4] - 2026-03-07
4
13
 
5
14
  ### Added
data/README.md CHANGED
@@ -59,23 +59,48 @@ Then execute:
59
59
  bundle install
60
60
  ```
61
61
 
62
- ## Usage
62
+ ## Quick Start
63
63
 
64
- ### Basic Usage
64
+ ### Interactive CLI
65
65
 
66
- #### Interactive CLI (Recommended)
66
+ After installing the gem, run the interactive client:
67
67
 
68
68
  ```bash
69
- rubycode
69
+ rubycode_client
70
70
  ```
71
71
 
72
- The first time you run RubyCode, an interactive setup wizard will guide you through:
72
+ The first time you run it, an interactive setup wizard will guide you through:
73
73
  1. Selecting your LLM provider (Ollama Cloud, DeepSeek, Gemini, OpenAI, or OpenRouter)
74
74
  2. Choosing a model
75
75
  3. Entering API keys (saved securely with encryption)
76
76
 
77
77
  Your configuration is automatically saved and reloaded on subsequent runs.
78
78
 
79
+ ### Programmatic Usage
80
+
81
+ You can also use RubyCode in your Ruby projects:
82
+
83
+ ```ruby
84
+ require "rubycode"
85
+
86
+ # Configure the LLM adapter
87
+ RubyCode.configure do |config|
88
+ config.adapter = :ollama
89
+ config.url = "https://api.ollama.com"
90
+ config.model = "qwen3-coder:480b-cloud"
91
+ config.root_path = Dir.pwd
92
+ end
93
+
94
+ # Create a client and ask a question
95
+ client = RubyCode::Client.new
96
+ response = client.ask(prompt: "Find the User model in the codebase")
97
+ puts response
98
+ ```
99
+
100
+ ## Usage
101
+
102
+ ### Basic Usage
103
+
79
104
  #### Programmatic Usage
80
105
 
81
106
  ```ruby
@@ -163,6 +188,11 @@ export EXA_API_KEY=your_api_key_here
163
188
  export BRAVE_API_KEY=your_api_key_here
164
189
  ```
165
190
 
191
+ ### New in 0.1.5
192
+
193
+ - **CLI Executable**: `rubycode_client` command for interactive chat after gem installation
194
+ - **Improved Documentation**: Reorganized Quick Start section with CLI-first approach
195
+
166
196
  ### New in 0.1.4
167
197
 
168
198
  - **Bug Fix**: Agent now properly stops when `done` tool is called, even if tool execution fails
@@ -186,10 +216,10 @@ After checking out the repo, run `bundle install` to install dependencies.
186
216
 
187
217
  ```bash
188
218
  # Run all tests
189
- bundle exec rake test
219
+ bin/test
190
220
 
191
- # Run tests without warnings
192
- bundle exec rake test 2>/dev/null
221
+ # Or with rake
222
+ bundle exec rake test
193
223
 
194
224
  # Run specific test file
195
225
  bundle exec rake test TEST=test/test_adapters.rb
@@ -0,0 +1,274 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/rubycode"
5
+ require "tty-prompt"
6
+
7
+ prompt = TTY::Prompt.new
8
+
9
+ # Setup wizard for first-time configuration or reconfiguration
10
+ def setup_wizard(prompt)
11
+ puts RubyCode::Views::Cli::SetupTitle.build
12
+
13
+ adapter = select_adapter(prompt)
14
+ model = select_model_for_adapter(prompt, adapter)
15
+ url = get_url_for_adapter(prompt, adapter)
16
+
17
+ configure_adapter_api_key(prompt, adapter) if adapter_requires_key?(adapter)
18
+ configure_exa_api_key(prompt) if prompt.yes?(I18n.t("rubycode.setup.configure_exa"), default: false)
19
+
20
+ save_and_return_config(adapter, model, url)
21
+ end
22
+
23
+ def select_adapter(prompt)
24
+ prompt.select(I18n.t("rubycode.setup.adapter_prompt"), {
25
+ I18n.t("rubycode.adapters.ollama.name") => :ollama,
26
+ I18n.t("rubycode.adapters.deepseek.name") => :deepseek,
27
+ I18n.t("rubycode.adapters.gemini.name") => :gemini,
28
+ I18n.t("rubycode.adapters.openai.name") => :openai,
29
+ I18n.t("rubycode.adapters.openrouter.name") => :openrouter
30
+ })
31
+ end
32
+
33
+ def select_model_for_adapter(prompt, adapter)
34
+ models = I18n.t("rubycode.models.#{adapter}")
35
+ choices = {}
36
+ models.each do |key, model_data|
37
+ # Skip the :default key which is just a string
38
+ next if %i[default].include?(key) || ["default"].include?(key)
39
+ # Skip if model_data is not a hash (defensive)
40
+ next unless model_data.is_a?(Hash)
41
+
42
+ label = model_data[:label] || model_data["label"]
43
+ name = model_data[:name] || model_data["name"]
44
+ choices[label] = name
45
+ end
46
+ prompt.select(I18n.t("rubycode.setup.model_prompt"), choices)
47
+ end
48
+
49
+ def get_url_for_adapter(prompt, adapter)
50
+ case adapter
51
+ when :ollama
52
+ prompt.ask(I18n.t("rubycode.setup.url_prompt"), default: "https://api.ollama.com")
53
+ when :deepseek
54
+ "https://api.deepseek.com/v1/chat/completions"
55
+ when :gemini
56
+ "https://generativelanguage.googleapis.com/v1beta/models"
57
+ when :openai
58
+ "https://api.openai.com/v1/chat/completions"
59
+ when :openrouter
60
+ "https://openrouter.ai/api/v1/chat/completions"
61
+ end
62
+ end
63
+
64
+ def adapter_requires_key?(adapter)
65
+ I18n.t("rubycode.adapters.#{adapter}")[:requires_key]
66
+ end
67
+
68
+ def configure_adapter_api_key(prompt, adapter)
69
+ env_var_name = "#{adapter.to_s.upcase}_API_KEY"
70
+ saved_exists = RubyCode::Models::ApiKey.key_exists?(adapter: adapter)
71
+ env_exists = ENV.fetch(env_var_name, nil)
72
+
73
+ if saved_exists
74
+ handle_existing_saved_key(prompt, adapter)
75
+ elsif env_exists
76
+ handle_existing_env_key(prompt, adapter, env_var_name)
77
+ else
78
+ prompt_for_required_key(prompt, adapter)
79
+ end
80
+ end
81
+
82
+ def handle_existing_saved_key(prompt, adapter)
83
+ use_saved = prompt.yes?(I18n.t("rubycode.setup.use_saved_api_key", adapter: adapter.to_s.upcase), default: true)
84
+ return if use_saved
85
+
86
+ new_key = prompt.mask("#{I18n.t("rubycode.setup.api_key_prompt", adapter: adapter.to_s.upcase)} " \
87
+ "#{I18n.t("rubycode.setup.api_key_optional")}")
88
+ RubyCode::Models::ApiKey.save_key(adapter: adapter, api_key: new_key) if new_key && !new_key.empty?
89
+ end
90
+
91
+ def handle_existing_env_key(prompt, adapter, env_var_name)
92
+ save_to_db = prompt.yes?(I18n.t("rubycode.setup.save_env_key_to_db", adapter: adapter.to_s.upcase), default: true)
93
+ RubyCode::Models::ApiKey.save_key(adapter: adapter, api_key: ENV.fetch(env_var_name, nil)) if save_to_db
94
+ end
95
+
96
+ def prompt_for_required_key(prompt, adapter)
97
+ puts RubyCode::Views::Cli::ApiKeyMissing.build(adapter: adapter)
98
+ api_key = prompt.mask(I18n.t("rubycode.setup.api_key_prompt", adapter: adapter.to_s.upcase))
99
+
100
+ if api_key.nil? || api_key.empty?
101
+ puts "\n#{I18n.t("rubycode.setup.api_key_required")}\n"
102
+ exit 1
103
+ end
104
+
105
+ RubyCode::Models::ApiKey.save_key(adapter: adapter, api_key: api_key)
106
+ end
107
+
108
+ def configure_exa_api_key(prompt)
109
+ saved_exists = RubyCode::Models::ApiKey.key_exists?(adapter: :exa)
110
+ env_exists = ENV.fetch("EXA_API_KEY", nil)
111
+
112
+ if saved_exists
113
+ handle_existing_saved_key(prompt, :exa)
114
+ elsif env_exists
115
+ handle_exa_env_key(prompt)
116
+ else
117
+ prompt_for_optional_exa_key(prompt)
118
+ end
119
+ end
120
+
121
+ def handle_exa_env_key(prompt)
122
+ save_to_db = prompt.yes?(I18n.t("rubycode.setup.save_env_key_to_db", adapter: "EXA"), default: true)
123
+ RubyCode::Models::ApiKey.save_key(adapter: :exa, api_key: ENV.fetch("EXA_API_KEY", nil)) if save_to_db
124
+ end
125
+
126
+ def prompt_for_optional_exa_key(prompt)
127
+ exa_key = prompt.mask("#{I18n.t("rubycode.setup.api_key_prompt", adapter: "EXA")} " \
128
+ "#{I18n.t("rubycode.setup.api_key_optional")}")
129
+ RubyCode::Models::ApiKey.save_key(adapter: :exa, api_key: exa_key) if exa_key && !exa_key.empty?
130
+ end
131
+
132
+ def save_and_return_config(adapter, model, url)
133
+ config = { adapter: adapter, model: model, url: url }
134
+ RubyCode::ConfigManager.save(config)
135
+ puts RubyCode::Views::Cli::ConfigSaved.build
136
+ config
137
+ end
138
+
139
+ # Load or setup configuration
140
+ if RubyCode::ConfigManager.exists?
141
+ saved_config = RubyCode::ConfigManager.load
142
+ use_saved = prompt.yes?(I18n.t("rubycode.setup.use_saved",
143
+ adapter: saved_config[:adapter],
144
+ model: saved_config[:model]),
145
+ default: true)
146
+ config = use_saved ? saved_config : setup_wizard(prompt)
147
+ else
148
+ puts RubyCode::Views::Cli::FirstTimeSetup.build
149
+ config = setup_wizard(prompt)
150
+ end
151
+ adapter = config[:adapter]
152
+ model = config[:model]
153
+ url = config[:url]
154
+
155
+ puts "\n#{RubyCode::Views::Welcome.build}"
156
+
157
+ directory = prompt.ask("What directory do you want to work on?") do |q|
158
+ q.default Dir.pwd
159
+ q.required false
160
+ end
161
+ directory = Dir.pwd if directory.nil? || directory.empty?
162
+
163
+ full_path = File.expand_path(directory)
164
+
165
+ unless Dir.exist?(full_path)
166
+ puts RubyCode::Views::Cli::ErrorMessage.build(message: "Directory '#{full_path}' does not exist!")
167
+ exit 1
168
+ end
169
+
170
+ RubyCode.configure do |config|
171
+ config.adapter = adapter
172
+ config.url = url
173
+ config.model = model
174
+ config.root_path = full_path
175
+ end
176
+
177
+ puts RubyCode::Views::Cli::ConfigurationTable.build(
178
+ adapter: adapter,
179
+ model: model,
180
+ directory: full_path
181
+ )
182
+
183
+ # Ensure database is connected before checking for API keys
184
+ RubyCode::Database.connect
185
+
186
+ # Check if adapter requires API key and ensure it's available
187
+ adapter_info = I18n.t("rubycode.adapters.#{adapter}")
188
+ if adapter_info[:requires_key]
189
+ env_var_name = "#{adapter.to_s.upcase}_API_KEY"
190
+ db_key_exists = RubyCode::Models::ApiKey.key_exists?(adapter: adapter)
191
+ env_key_exists = ENV.fetch(env_var_name, nil)
192
+
193
+ unless db_key_exists || env_key_exists
194
+ # No API key found - prompt user
195
+ puts RubyCode::Views::Cli::ApiKeyMissing.build(adapter: adapter)
196
+ api_key = prompt.mask(I18n.t("rubycode.setup.api_key_prompt", adapter: adapter.to_s.upcase))
197
+
198
+ if api_key.nil? || api_key.empty?
199
+ puts "\n#{I18n.t("rubycode.setup.api_key_required")}\n"
200
+ exit 1
201
+ end
202
+
203
+ # Save to database
204
+ RubyCode::Models::ApiKey.save_key(adapter: adapter, api_key: api_key)
205
+ puts "\n✓ API key saved\n"
206
+ end
207
+ end
208
+
209
+ ChatContext = Struct.new(:prompt, :client, :adapter, :model, :full_path, :debug_mode)
210
+
211
+ def run_chat_loop(context)
212
+ loop do
213
+ user_input = get_user_input(context.prompt)
214
+ break if user_input.nil?
215
+
216
+ next if handle_special_command(user_input, context)
217
+
218
+ process_user_message(context.client, user_input)
219
+ end
220
+ end
221
+
222
+ def get_user_input(prompt)
223
+ prompt.ask("You: ")
224
+ rescue StandardError
225
+ nil
226
+ end
227
+
228
+ def handle_special_command(input, context)
229
+ case input.strip.downcase
230
+ when "exit", "quit"
231
+ puts RubyCode::Views::Cli::ExitMessage.build
232
+ exit 0
233
+ when "clear"
234
+ context.client.clear_memory
235
+ puts RubyCode::Views::Cli::MemoryClearedMessage.build
236
+ true
237
+ when "config"
238
+ show_config_and_reconfigure(context)
239
+ true
240
+ else
241
+ false
242
+ end
243
+ end
244
+
245
+ def show_config_and_reconfigure(context)
246
+ puts RubyCode::Views::Cli::ConfigurationTable.build(
247
+ adapter: context.adapter,
248
+ model: context.model,
249
+ directory: context.full_path,
250
+ debug_mode: context.debug_mode
251
+ )
252
+
253
+ return unless context.prompt.yes?(I18n.t("rubycode.setup.reconfigure"), default: false)
254
+
255
+ puts RubyCode::Views::Cli::RestartMessage.build
256
+ setup_wizard(context.prompt)
257
+ end
258
+
259
+ def process_user_message(client, user_input)
260
+ response = client.ask(prompt: user_input)
261
+ puts RubyCode::Views::Cli::ResponseBox.build(response: response)
262
+ rescue Interrupt
263
+ puts RubyCode::Views::Cli::InterruptMessage.build
264
+ rescue StandardError => e
265
+ puts RubyCode::Views::Cli::ErrorDisplay.build(error: e)
266
+ end
267
+
268
+ client = RubyCode::Client.new(tty_prompt: prompt)
269
+
270
+ puts RubyCode::Views::Cli::ReadyMessage.build
271
+
272
+ debug_mode = false # Debug mode not yet implemented
273
+ context = ChatContext.new(prompt, client, adapter, model, full_path, debug_mode)
274
+ run_chat_loop(context)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyCode
4
- VERSION = "0.1.4"
4
+ VERSION = "0.1.5"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubycode
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonas Medeiros
@@ -224,7 +224,8 @@ description: Ruby-native AI coding agent with pluggable LLM adapters, persistent
224
224
  history, and user approval workflows for file modifications.
225
225
  email:
226
226
  - jonas.g.medeiros@gmail.com
227
- executables: []
227
+ executables:
228
+ - rubycode_client
228
229
  extensions: []
229
230
  extra_rdoc_files: []
230
231
  files:
@@ -246,6 +247,7 @@ files:
246
247
  - config/tools/websearch.json
247
248
  - config/tools/write.json
248
249
  - docs/images/demo.png
250
+ - exe/rubycode_client
249
251
  - lib/rubycode.rb
250
252
  - lib/rubycode/adapters/base.rb
251
253
  - lib/rubycode/adapters/concerns/debugging.rb