elelem 0.7.0 → 0.9.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +79 -3
- data/README.md +31 -46
- data/exe/elelem +78 -3
- data/lib/elelem/agent.rb +135 -228
- data/lib/elelem/mcp.rb +96 -0
- data/lib/elelem/net/claude.rb +200 -0
- data/lib/elelem/net/ollama.rb +78 -0
- data/lib/elelem/net/openai.rb +86 -0
- data/lib/elelem/net.rb +16 -0
- data/lib/elelem/plugins/confirm.rb +12 -0
- data/lib/elelem/plugins/edit.rb +15 -0
- data/lib/elelem/plugins/eval.rb +20 -0
- data/lib/elelem/plugins/execute.rb +18 -0
- data/lib/elelem/plugins/mcp.rb +14 -0
- data/lib/elelem/plugins/read.rb +21 -0
- data/lib/elelem/plugins/verify.rb +47 -0
- data/lib/elelem/plugins/write.rb +23 -0
- data/lib/elelem/plugins.rb +43 -0
- data/lib/elelem/system_prompt.rb +65 -0
- data/lib/elelem/templates/system_prompt.erb +53 -0
- data/lib/elelem/terminal.rb +55 -65
- data/lib/elelem/tool.rb +30 -32
- data/lib/elelem/toolbox.rb +36 -75
- data/lib/elelem/version.rb +1 -1
- data/lib/elelem.rb +28 -34
- metadata +40 -34
- data/lib/elelem/application.rb +0 -45
- data/lib/elelem/conversation.rb +0 -78
- data/lib/elelem/git_context.rb +0 -79
- data/lib/elelem/system_prompt.erb +0 -16
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: elelem
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- mo khan
|
|
@@ -10,19 +10,19 @@ cert_chain: []
|
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
|
-
name:
|
|
13
|
+
name: date
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '
|
|
18
|
+
version: '3.0'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '
|
|
25
|
+
version: '3.0'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: erb
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -66,21 +66,21 @@ dependencies:
|
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
67
|
version: '2.0'
|
|
68
68
|
- !ruby/object:Gem::Dependency
|
|
69
|
-
name:
|
|
69
|
+
name: json_schemer
|
|
70
70
|
requirement: !ruby/object:Gem::Requirement
|
|
71
71
|
requirements:
|
|
72
72
|
- - "~>"
|
|
73
73
|
- !ruby/object:Gem::Version
|
|
74
|
-
version: '
|
|
74
|
+
version: '2.0'
|
|
75
75
|
type: :runtime
|
|
76
76
|
prerelease: false
|
|
77
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
78
78
|
requirements:
|
|
79
79
|
- - "~>"
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
|
-
version: '
|
|
81
|
+
version: '2.0'
|
|
82
82
|
- !ruby/object:Gem::Dependency
|
|
83
|
-
name:
|
|
83
|
+
name: net-hippie
|
|
84
84
|
requirement: !ruby/object:Gem::Requirement
|
|
85
85
|
requirements:
|
|
86
86
|
- - "~>"
|
|
@@ -94,27 +94,21 @@ dependencies:
|
|
|
94
94
|
- !ruby/object:Gem::Version
|
|
95
95
|
version: '1.0'
|
|
96
96
|
- !ruby/object:Gem::Dependency
|
|
97
|
-
name:
|
|
97
|
+
name: open3
|
|
98
98
|
requirement: !ruby/object:Gem::Requirement
|
|
99
99
|
requirements:
|
|
100
100
|
- - "~>"
|
|
101
101
|
- !ruby/object:Gem::Version
|
|
102
|
-
version: '0.
|
|
103
|
-
- - ">="
|
|
104
|
-
- !ruby/object:Gem::Version
|
|
105
|
-
version: 0.5.0
|
|
102
|
+
version: '0.1'
|
|
106
103
|
type: :runtime
|
|
107
104
|
prerelease: false
|
|
108
105
|
version_requirements: !ruby/object:Gem::Requirement
|
|
109
106
|
requirements:
|
|
110
107
|
- - "~>"
|
|
111
108
|
- !ruby/object:Gem::Version
|
|
112
|
-
version: '0.
|
|
113
|
-
- - ">="
|
|
114
|
-
- !ruby/object:Gem::Version
|
|
115
|
-
version: 0.5.0
|
|
109
|
+
version: '0.1'
|
|
116
110
|
- !ruby/object:Gem::Dependency
|
|
117
|
-
name:
|
|
111
|
+
name: optparse
|
|
118
112
|
requirement: !ruby/object:Gem::Requirement
|
|
119
113
|
requirements:
|
|
120
114
|
- - "~>"
|
|
@@ -156,47 +150,47 @@ dependencies:
|
|
|
156
150
|
- !ruby/object:Gem::Version
|
|
157
151
|
version: '0.6'
|
|
158
152
|
- !ruby/object:Gem::Dependency
|
|
159
|
-
name:
|
|
153
|
+
name: stringio
|
|
160
154
|
requirement: !ruby/object:Gem::Requirement
|
|
161
155
|
requirements:
|
|
162
156
|
- - "~>"
|
|
163
157
|
- !ruby/object:Gem::Version
|
|
164
|
-
version: '
|
|
158
|
+
version: '3.0'
|
|
165
159
|
type: :runtime
|
|
166
160
|
prerelease: false
|
|
167
161
|
version_requirements: !ruby/object:Gem::Requirement
|
|
168
162
|
requirements:
|
|
169
163
|
- - "~>"
|
|
170
164
|
- !ruby/object:Gem::Version
|
|
171
|
-
version: '
|
|
165
|
+
version: '3.0'
|
|
172
166
|
- !ruby/object:Gem::Dependency
|
|
173
|
-
name:
|
|
167
|
+
name: tempfile
|
|
174
168
|
requirement: !ruby/object:Gem::Requirement
|
|
175
169
|
requirements:
|
|
176
170
|
- - "~>"
|
|
177
171
|
- !ruby/object:Gem::Version
|
|
178
|
-
version: '
|
|
172
|
+
version: '0.3'
|
|
179
173
|
type: :runtime
|
|
180
174
|
prerelease: false
|
|
181
175
|
version_requirements: !ruby/object:Gem::Requirement
|
|
182
176
|
requirements:
|
|
183
177
|
- - "~>"
|
|
184
178
|
- !ruby/object:Gem::Version
|
|
185
|
-
version: '
|
|
179
|
+
version: '0.3'
|
|
186
180
|
- !ruby/object:Gem::Dependency
|
|
187
|
-
name:
|
|
181
|
+
name: uri
|
|
188
182
|
requirement: !ruby/object:Gem::Requirement
|
|
189
183
|
requirements:
|
|
190
184
|
- - "~>"
|
|
191
185
|
- !ruby/object:Gem::Version
|
|
192
|
-
version: '0
|
|
186
|
+
version: '1.0'
|
|
193
187
|
type: :runtime
|
|
194
188
|
prerelease: false
|
|
195
189
|
version_requirements: !ruby/object:Gem::Requirement
|
|
196
190
|
requirements:
|
|
197
191
|
- - "~>"
|
|
198
192
|
- !ruby/object:Gem::Version
|
|
199
|
-
version: '0
|
|
193
|
+
version: '1.0'
|
|
200
194
|
description: A minimal coding agent supporting Ollama, Anthropic, OpenAI, and VertexAI.
|
|
201
195
|
email:
|
|
202
196
|
- mo@mokhan.ca
|
|
@@ -212,10 +206,22 @@ files:
|
|
|
212
206
|
- exe/elelem
|
|
213
207
|
- lib/elelem.rb
|
|
214
208
|
- lib/elelem/agent.rb
|
|
215
|
-
- lib/elelem/
|
|
216
|
-
- lib/elelem/
|
|
217
|
-
- lib/elelem/
|
|
218
|
-
- lib/elelem/
|
|
209
|
+
- lib/elelem/mcp.rb
|
|
210
|
+
- lib/elelem/net.rb
|
|
211
|
+
- lib/elelem/net/claude.rb
|
|
212
|
+
- lib/elelem/net/ollama.rb
|
|
213
|
+
- lib/elelem/net/openai.rb
|
|
214
|
+
- lib/elelem/plugins.rb
|
|
215
|
+
- lib/elelem/plugins/confirm.rb
|
|
216
|
+
- lib/elelem/plugins/edit.rb
|
|
217
|
+
- lib/elelem/plugins/eval.rb
|
|
218
|
+
- lib/elelem/plugins/execute.rb
|
|
219
|
+
- lib/elelem/plugins/mcp.rb
|
|
220
|
+
- lib/elelem/plugins/read.rb
|
|
221
|
+
- lib/elelem/plugins/verify.rb
|
|
222
|
+
- lib/elelem/plugins/write.rb
|
|
223
|
+
- lib/elelem/system_prompt.rb
|
|
224
|
+
- lib/elelem/templates/system_prompt.erb
|
|
219
225
|
- lib/elelem/terminal.rb
|
|
220
226
|
- lib/elelem/tool.rb
|
|
221
227
|
- lib/elelem/toolbox.rb
|
|
@@ -235,14 +241,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
235
241
|
requirements:
|
|
236
242
|
- - ">="
|
|
237
243
|
- !ruby/object:Gem::Version
|
|
238
|
-
version:
|
|
244
|
+
version: 4.0.0
|
|
239
245
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
240
246
|
requirements:
|
|
241
247
|
- - ">="
|
|
242
248
|
- !ruby/object:Gem::Version
|
|
243
|
-
version:
|
|
249
|
+
version: 4.0.0
|
|
244
250
|
requirements: []
|
|
245
|
-
rubygems_version:
|
|
251
|
+
rubygems_version: 4.0.4
|
|
246
252
|
specification_version: 4
|
|
247
253
|
summary: A minimal coding agent for LLMs.
|
|
248
254
|
test_files: []
|
data/lib/elelem/application.rb
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Elelem
|
|
4
|
-
class Application < Thor
|
|
5
|
-
PROVIDERS = %w[ollama anthropic openai vertex-ai].freeze
|
|
6
|
-
|
|
7
|
-
desc "chat", "Start the REPL"
|
|
8
|
-
method_option :provider,
|
|
9
|
-
aliases: "-p",
|
|
10
|
-
type: :string,
|
|
11
|
-
desc: "LLM provider (#{PROVIDERS.join(', ')})",
|
|
12
|
-
default: ENV.fetch("ELELEM_PROVIDER", "ollama")
|
|
13
|
-
method_option :model,
|
|
14
|
-
aliases: "-m",
|
|
15
|
-
type: :string,
|
|
16
|
-
desc: "Model name (uses provider default if not specified)"
|
|
17
|
-
def chat(*)
|
|
18
|
-
provider = options[:provider]
|
|
19
|
-
model = options[:model]
|
|
20
|
-
say "Agent (#{provider})", :green
|
|
21
|
-
agent = Agent.new(provider, model, Toolbox.new)
|
|
22
|
-
agent.repl
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
desc "files", "Generate CXML of the files"
|
|
26
|
-
def files
|
|
27
|
-
puts '<documents>'
|
|
28
|
-
$stdin.read.split("\n").map(&:strip).reject(&:empty?).each_with_index do |file, i|
|
|
29
|
-
next unless File.file?(file)
|
|
30
|
-
|
|
31
|
-
puts " <document index=\"#{i + 1}\">"
|
|
32
|
-
puts " <source><![CDATA[#{file}]]></source>"
|
|
33
|
-
puts " <document_content><![CDATA[#{File.read(file)}]]></document_content>"
|
|
34
|
-
puts " </document>"
|
|
35
|
-
end
|
|
36
|
-
puts '</documents>'
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
desc "version", "The version of this CLI"
|
|
40
|
-
def version
|
|
41
|
-
say "v#{Elelem::VERSION}"
|
|
42
|
-
end
|
|
43
|
-
map %w[--version -v] => :version
|
|
44
|
-
end
|
|
45
|
-
end
|
data/lib/elelem/conversation.rb
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Elelem
|
|
4
|
-
class Conversation
|
|
5
|
-
ROLES = %i[system assistant user tool].freeze
|
|
6
|
-
|
|
7
|
-
def initialize(items = default_context)
|
|
8
|
-
@items = items
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def history_for(permissions)
|
|
12
|
-
history = @items.dup
|
|
13
|
-
history[0] = { role: "system", content: system_prompt_for(permissions) }
|
|
14
|
-
history
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def add(role: :user, content: "")
|
|
18
|
-
role = role.to_sym
|
|
19
|
-
raise "unknown role: #{role}" unless ROLES.include?(role)
|
|
20
|
-
return if content.nil? || content.empty?
|
|
21
|
-
|
|
22
|
-
if @items.last && @items.last[:role] == role
|
|
23
|
-
@items.last[:content] += content
|
|
24
|
-
else
|
|
25
|
-
@items.push({ role: role, content: normalize(content) })
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def clear
|
|
30
|
-
@items = default_context
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def dump(permissions)
|
|
34
|
-
JSON.pretty_generate(history_for(permissions))
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
private
|
|
38
|
-
|
|
39
|
-
def default_context(prompt = system_prompt_for([]))
|
|
40
|
-
[{ role: "system", content: prompt }]
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def system_prompt_for(permissions)
|
|
44
|
-
base = system_prompt
|
|
45
|
-
|
|
46
|
-
case permissions.sort
|
|
47
|
-
when [:read]
|
|
48
|
-
"#{base}\n\nYou may read files on the system."
|
|
49
|
-
when [:write]
|
|
50
|
-
"#{base}\n\nYou may write files on the system."
|
|
51
|
-
when [:execute]
|
|
52
|
-
"#{base}\n\nYou may execute shell commands on the system."
|
|
53
|
-
when [:read, :write]
|
|
54
|
-
"#{base}\n\nYou may read and write files on the system."
|
|
55
|
-
when [:execute, :read]
|
|
56
|
-
"#{base}\n\nYou may execute shell commands and read files on the system."
|
|
57
|
-
when [:execute, :write]
|
|
58
|
-
"#{base}\n\nYou may execute shell commands and write files on the system."
|
|
59
|
-
when [:execute, :read, :write]
|
|
60
|
-
"#{base}\n\nYou may read files, write files and execute shell commands on the system."
|
|
61
|
-
else
|
|
62
|
-
base
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def system_prompt
|
|
67
|
-
ERB.new(Pathname.new(__dir__).join("system_prompt.erb").read).result(binding)
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def normalize(content)
|
|
71
|
-
if content.is_a?(Array)
|
|
72
|
-
content.join(", ")
|
|
73
|
-
else
|
|
74
|
-
content.to_s
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|
data/lib/elelem/git_context.rb
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Elelem
|
|
4
|
-
class GitContext
|
|
5
|
-
MAX_DIFF_LINES = 100
|
|
6
|
-
|
|
7
|
-
def initialize(shell = Elelem.shell)
|
|
8
|
-
@shell = shell
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def to_s
|
|
12
|
-
return "" unless git_repo?
|
|
13
|
-
|
|
14
|
-
parts = []
|
|
15
|
-
parts << "Branch: #{branch}" if branch
|
|
16
|
-
parts << status_section if status.any?
|
|
17
|
-
parts << diff_section if staged_diff.any? || unstaged_diff.any?
|
|
18
|
-
parts << recent_commits_section if recent_commits.any?
|
|
19
|
-
parts.join("\n\n")
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
private
|
|
23
|
-
|
|
24
|
-
def git_repo?
|
|
25
|
-
@shell.execute("git", args: ["rev-parse", "--git-dir"])["exit_status"].zero?
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def branch
|
|
29
|
-
@branch ||= @shell.execute("git", args: ["branch", "--show-current"])["stdout"].strip.then { |b| b.empty? ? nil : b }
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def status
|
|
33
|
-
@status ||= @shell.execute("git", args: ["status", "--porcelain"])["stdout"].lines.map(&:chomp)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def staged_diff
|
|
37
|
-
@staged_diff ||= @shell.execute("git", args: ["diff", "--cached", "--stat"])["stdout"].lines
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def unstaged_diff
|
|
41
|
-
@unstaged_diff ||= @shell.execute("git", args: ["diff", "--stat"])["stdout"].lines
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def recent_commits
|
|
45
|
-
@recent_commits ||= @shell.execute("git", args: ["log", "--oneline", "-5"])["stdout"].lines.map(&:strip)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def status_section
|
|
49
|
-
modified = status.select { |l| l[0] == "M" || l[1] == "M" }.map { |l| l[3..] }
|
|
50
|
-
added = status.select { |l| l[0] == "A" || l.start_with?("??") }.map { |l| l[3..] }
|
|
51
|
-
deleted = status.select { |l| l[0] == "D" || l[1] == "D" }.map { |l| l[3..] }
|
|
52
|
-
|
|
53
|
-
lines = []
|
|
54
|
-
lines << "Modified: #{modified.join(', ')}" if modified.any?
|
|
55
|
-
lines << "Added: #{added.join(', ')}" if added.any?
|
|
56
|
-
lines << "Deleted: #{deleted.join(', ')}" if deleted.any?
|
|
57
|
-
lines.any? ? "Working tree:\n#{lines.join("\n")}" : nil
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def diff_section
|
|
61
|
-
lines = []
|
|
62
|
-
lines << "Staged:\n#{truncate(staged_diff)}" if staged_diff.any?
|
|
63
|
-
lines << "Unstaged:\n#{truncate(unstaged_diff)}" if unstaged_diff.any?
|
|
64
|
-
lines.join("\n\n")
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def recent_commits_section
|
|
68
|
-
"Recent commits:\n#{recent_commits.join("\n")}"
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def truncate(lines)
|
|
72
|
-
if lines.size > MAX_DIFF_LINES
|
|
73
|
-
lines.first(MAX_DIFF_LINES).join + "\n... (#{lines.size - MAX_DIFF_LINES} more lines)"
|
|
74
|
-
else
|
|
75
|
-
lines.join
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
end
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
You are a trusted terminal agent. You act on behalf of the user - executing tasks directly through bash, files, and git. Be capable, be direct, be done.
|
|
2
|
-
|
|
3
|
-
## Principles
|
|
4
|
-
|
|
5
|
-
- Act, don't explain. Execute the task.
|
|
6
|
-
- Read before write. Understand existing code first.
|
|
7
|
-
- Small focused changes. One thing at a time.
|
|
8
|
-
- Verify your work. Run tests, check output.
|
|
9
|
-
|
|
10
|
-
## System
|
|
11
|
-
|
|
12
|
-
<%= `uname -s`.strip %> · <%= ENV['PWD'] %>
|
|
13
|
-
|
|
14
|
-
## Git State
|
|
15
|
-
|
|
16
|
-
<%= Elelem::GitContext.new.to_s %>
|