gemlings 0.3.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.
@@ -0,0 +1,245 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "lipgloss"
5
+ rescue LoadError
6
+ # lipgloss unavailable (e.g. JRuby) -- NullStyle handles fallback below
7
+ end
8
+
9
+ require "rouge"
10
+
11
+ module Gemlings
12
+ LIPGLOSS_AVAILABLE = defined?(Lipgloss)
13
+
14
+ module UI
15
+ SPINNER_FRAMES = %w[⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏].freeze
16
+
17
+ # Passthrough style used when lipgloss is unavailable
18
+ class NullStyle
19
+ def render(text)
20
+ text
21
+ end
22
+ end
23
+
24
+ module Styles
25
+ if LIPGLOSS_AVAILABLE
26
+ def self.label
27
+ @label ||= Lipgloss::Style.new.faint(true)
28
+ end
29
+
30
+ def self.plan_label
31
+ @plan_label ||= Lipgloss::Style.new
32
+ .bold(true)
33
+ .foreground("#011627")
34
+ .background("#FF9F1C")
35
+ .padding(0, 1)
36
+ end
37
+
38
+ def self.plan_box
39
+ @plan_box ||= Lipgloss::Style.new
40
+ .border(:rounded)
41
+ .border_foreground("#FF9F1C")
42
+ .padding(0, 2)
43
+ end
44
+
45
+ def self.final_answer
46
+ @final_answer ||= Lipgloss::Style.new
47
+ .bold(true)
48
+ .foreground("#2EC4B6")
49
+ end
50
+
51
+ def self.error
52
+ @error ||= Lipgloss::Style.new
53
+ .bold(true)
54
+ .foreground("#FF0000")
55
+ end
56
+
57
+ def self.dim
58
+ @dim ||= Lipgloss::Style.new.faint(true)
59
+ end
60
+
61
+ def self.spinner_style
62
+ @spinner_style ||= Lipgloss::Style.new
63
+ .foreground("#7B61FF")
64
+ end
65
+
66
+ def self.success_dot
67
+ @success_dot ||= Lipgloss::Style.new.foreground("#00CC00")
68
+ end
69
+
70
+ def self.error_dot
71
+ @error_dot ||= Lipgloss::Style.new.foreground("#FF0000")
72
+ end
73
+ else
74
+ def self.label; NullStyle.new; end
75
+ def self.plan_label; NullStyle.new; end
76
+ def self.plan_box; NullStyle.new; end
77
+ def self.final_answer; NullStyle.new; end
78
+ def self.error; NullStyle.new; end
79
+ def self.dim; NullStyle.new; end
80
+ def self.spinner_style; NullStyle.new; end
81
+ def self.success_dot; NullStyle.new; end
82
+ def self.error_dot; NullStyle.new; end
83
+ end
84
+ end
85
+
86
+ class Spinner
87
+ def initialize(message)
88
+ @message = message
89
+ @running = false
90
+ @frame = 0
91
+ end
92
+
93
+ def start
94
+ @running = true
95
+ @thread = Thread.new do
96
+ while @running
97
+ char = SPINNER_FRAMES[@frame % SPINNER_FRAMES.size]
98
+ frame = Styles.spinner_style.render(char)
99
+ $stderr.print "\r\e[K#{frame} #{@message}"
100
+ @frame += 1
101
+ sleep 0.08
102
+ end
103
+ $stderr.print "\r\e[K"
104
+ end
105
+ end
106
+
107
+ def stop
108
+ @running = false
109
+ @thread&.join
110
+ end
111
+ end
112
+
113
+ class StatusLine
114
+ def initialize(message)
115
+ @message = message
116
+ @running = false
117
+ @frame = 0
118
+ end
119
+
120
+ def start
121
+ @running = true
122
+ @thread = Thread.new do
123
+ while @running
124
+ char = SPINNER_FRAMES[@frame % SPINNER_FRAMES.size]
125
+ styled = Styles.spinner_style.render(char)
126
+ print "\r\e[K#{styled} #{Styles.dim.render(@message)}"
127
+ $stdout.flush
128
+ @frame += 1
129
+ sleep 0.08
130
+ end
131
+ end
132
+ self
133
+ end
134
+
135
+ def success!(text)
136
+ finish!
137
+ display = text.length > 200 ? text[0...200] + Styles.dim.render("... (truncated)") : text
138
+ puts Styles.success_dot.render("● ") + Styles.label.render("Result: ") + display
139
+ end
140
+
141
+ def error!(text)
142
+ finish!
143
+ puts Styles.error_dot.render("● ") + Styles.error.render("Error: ") + text
144
+ end
145
+
146
+ private
147
+
148
+ def finish!
149
+ @running = false
150
+ @thread&.join
151
+ print "\r\e[K"
152
+ $stdout.flush
153
+ end
154
+ end
155
+
156
+ class << self
157
+ def thought(text)
158
+ puts Styles.label.render("Thought: ") + text
159
+ end
160
+
161
+ def code(source)
162
+ puts
163
+ highlighted = rouge_formatter.format(rouge_lexer.lex(source))
164
+ highlighted.each_line { |line| puts " #{line.rstrip}" }
165
+ puts
166
+ end
167
+
168
+ def observation(text)
169
+ puts Styles.label.render("Result: ") + truncate(text, 200)
170
+ end
171
+
172
+ def error(text)
173
+ puts Styles.error.render("Error: ") + text
174
+ end
175
+
176
+ def status(message)
177
+ StatusLine.new(message)
178
+ end
179
+
180
+ def plan(text)
181
+ label = Styles.plan_label.render(" Plan ")
182
+ body = Styles.plan_box.render(text)
183
+ puts "\n#{label}\n#{body}\n"
184
+ end
185
+
186
+ def step_metrics(duration:, token_usage:)
187
+ parts = []
188
+ parts << format("%.1fs", duration) if duration > 0
189
+ parts << token_usage.to_s if token_usage
190
+ return if parts.empty?
191
+
192
+ puts Styles.dim.render(parts.join(" | "))
193
+ puts
194
+ end
195
+
196
+ def run_summary(total_steps:, total_duration:, total_tokens:)
197
+ parts = ["#{total_steps} #{total_steps == 1 ? "step" : "steps"}", format("%.1fs total", total_duration)]
198
+ parts << total_tokens.to_s if total_tokens.total_tokens > 0
199
+ puts Styles.dim.render(parts.join(" | "))
200
+ end
201
+
202
+ def final_answer(text)
203
+ puts
204
+ puts Styles.final_answer.render("Final answer: ") + text.to_s
205
+ end
206
+
207
+ def spinner(message)
208
+ Spinner.new(message)
209
+ end
210
+
211
+ def welcome
212
+ if LIPGLOSS_AVAILABLE
213
+ title = Lipgloss::Style.new
214
+ .bold(true)
215
+ .foreground("#7B61FF")
216
+ .render("gemlings")
217
+
218
+ version = Styles.dim.render("v#{VERSION}")
219
+ puts "#{title} #{version}"
220
+ puts Styles.dim.render("Code-first AI agents for Ruby")
221
+ else
222
+ puts "gemlings v#{VERSION}"
223
+ puts "Code-first AI agents for Ruby"
224
+ end
225
+ puts
226
+ end
227
+
228
+ private
229
+
230
+ def rouge_lexer
231
+ @rouge_lexer ||= Rouge::Lexers::Ruby.new
232
+ end
233
+
234
+ def rouge_formatter
235
+ @rouge_formatter ||= Rouge::Formatters::Terminal256.new(Rouge::Themes::Monokai.new)
236
+ end
237
+
238
+ def truncate(text, max)
239
+ return text if text.length <= max
240
+ text[0...max] + Styles.dim.render("... (truncated)")
241
+ end
242
+
243
+ end
244
+ end
245
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gemlings
4
+ VERSION = "0.3.0"
5
+ end
data/lib/gemlings.rb ADDED
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "gemlings/version"
4
+ require_relative "gemlings/errors"
5
+
6
+ module Gemlings
7
+ end
8
+
9
+ require_relative "gemlings/tool"
10
+ require_relative "gemlings/model"
11
+ require_relative "gemlings/models/ruby_llm_adapter"
12
+ require_relative "gemlings/memory"
13
+ require_relative "gemlings/prompt"
14
+ require_relative "gemlings/sandbox"
15
+ require_relative "gemlings/ui"
16
+ require_relative "gemlings/callback"
17
+ require_relative "gemlings/agent"
18
+ require_relative "gemlings/code_agent"
19
+ require_relative "gemlings/tool_calling_agent"
20
+ require_relative "gemlings/mcp"
21
+ require_relative "gemlings/cli"
metadata ADDED
@@ -0,0 +1,219 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gemlings
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Chris Hasiński
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: lipgloss
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.2'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '0.2'
26
+ - !ruby/object:Gem::Dependency
27
+ name: reverse_markdown
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rouge
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '4.0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '4.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: json-schema
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '4.0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '4.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: bigdecimal
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: mcp
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '0.7'
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '0.7'
96
+ - !ruby/object:Gem::Dependency
97
+ name: ruby_llm
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '1.1'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '1.1'
110
+ - !ruby/object:Gem::Dependency
111
+ name: rake
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '13.0'
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '13.0'
124
+ - !ruby/object:Gem::Dependency
125
+ name: rspec
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: '3.0'
131
+ type: :development
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: '3.0'
138
+ - !ruby/object:Gem::Dependency
139
+ name: rubocop
140
+ requirement: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: '1.0'
145
+ type: :development
146
+ prerelease: false
147
+ version_requirements: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - "~>"
150
+ - !ruby/object:Gem::Version
151
+ version: '1.0'
152
+ description: Agents that write and execute Ruby code. Inspired by smolagents. LLMs
153
+ write executable code, not JSON blobs.
154
+ email:
155
+ - krzysztof.hasinski@gmail.com
156
+ executables:
157
+ - gemlings
158
+ extensions: []
159
+ extra_rdoc_files: []
160
+ files:
161
+ - ".ruby-version"
162
+ - CHANGELOG.md
163
+ - LICENSE
164
+ - README.md
165
+ - ROADMAP.md
166
+ - Rakefile
167
+ - demo.gif
168
+ - examples/custom_tool.rb
169
+ - examples/fibonacci.rb
170
+ - examples/with_tools.rb
171
+ - exe/gemlings
172
+ - gemlings.gemspec
173
+ - lib/gemlings.rb
174
+ - lib/gemlings/agent.rb
175
+ - lib/gemlings/callback.rb
176
+ - lib/gemlings/cli.rb
177
+ - lib/gemlings/code_agent.rb
178
+ - lib/gemlings/errors.rb
179
+ - lib/gemlings/mcp.rb
180
+ - lib/gemlings/memory.rb
181
+ - lib/gemlings/model.rb
182
+ - lib/gemlings/models/ruby_llm_adapter.rb
183
+ - lib/gemlings/prompt.rb
184
+ - lib/gemlings/sandbox.rb
185
+ - lib/gemlings/tool.rb
186
+ - lib/gemlings/tool_calling_agent.rb
187
+ - lib/gemlings/tools/file_read.rb
188
+ - lib/gemlings/tools/file_write.rb
189
+ - lib/gemlings/tools/list_gems.rb
190
+ - lib/gemlings/tools/user_input.rb
191
+ - lib/gemlings/tools/visit_webpage.rb
192
+ - lib/gemlings/tools/web_search.rb
193
+ - lib/gemlings/ui.rb
194
+ - lib/gemlings/version.rb
195
+ homepage: https://github.com/khasinski/gemlings
196
+ licenses:
197
+ - MIT
198
+ metadata:
199
+ homepage_uri: https://github.com/khasinski/gemlings
200
+ source_code_uri: https://github.com/khasinski/gemlings
201
+ changelog_uri: https://github.com/khasinski/gemlings/blob/main/CHANGELOG.md
202
+ rdoc_options: []
203
+ require_paths:
204
+ - lib
205
+ required_ruby_version: !ruby/object:Gem::Requirement
206
+ requirements:
207
+ - - ">="
208
+ - !ruby/object:Gem::Version
209
+ version: 3.2.0
210
+ required_rubygems_version: !ruby/object:Gem::Requirement
211
+ requirements:
212
+ - - ">="
213
+ - !ruby/object:Gem::Version
214
+ version: '0'
215
+ requirements: []
216
+ rubygems_version: 4.0.2
217
+ specification_version: 4
218
+ summary: A radically simple, code-first AI agent framework for Ruby
219
+ test_files: []