elelem 0.2.1 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +49 -0
- data/README.md +74 -11
- data/Rakefile +1 -3
- data/lib/elelem/agent.rb +247 -31
- data/lib/elelem/application.rb +8 -28
- data/lib/elelem/conversation.rb +17 -2
- data/lib/elelem/system_prompt.erb +4 -17
- data/lib/elelem/version.rb +1 -1
- data/lib/elelem.rb +3 -17
- metadata +1 -40
- data/exe/llm-ollama +0 -358
- data/exe/llm-openai +0 -339
- data/lib/elelem/api.rb +0 -48
- data/lib/elelem/configuration.rb +0 -84
- data/lib/elelem/mcp_client.rb +0 -136
- data/lib/elelem/states/idle.rb +0 -23
- data/lib/elelem/states/working/error.rb +0 -19
- data/lib/elelem/states/working/executing.rb +0 -19
- data/lib/elelem/states/working/state.rb +0 -26
- data/lib/elelem/states/working/talking.rb +0 -19
- data/lib/elelem/states/working/thinking.rb +0 -18
- data/lib/elelem/states/working/waiting.rb +0 -29
- data/lib/elelem/states/working.rb +0 -55
- data/lib/elelem/tool.rb +0 -32
- data/lib/elelem/toolbox/exec.rb +0 -61
- data/lib/elelem/toolbox/file.rb +0 -66
- data/lib/elelem/toolbox/mcp.rb +0 -37
- data/lib/elelem/toolbox/memory.rb +0 -164
- data/lib/elelem/toolbox/prompt.rb +0 -25
- data/lib/elelem/toolbox/web.rb +0 -126
- data/lib/elelem/toolbox.rb +0 -8
- data/lib/elelem/tools.rb +0 -41
- data/lib/elelem/tui.rb +0 -66
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bcd68905e2c0dd4aa7e6c22865d0353ab2fa0dff0ce8bfde3e319b10b7d4e548
|
|
4
|
+
data.tar.gz: ed59f2ca17e210adbd7b371556d7994589e75e7a515d688bb6751d8bbd3380c5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 400ae6e321309f3bcb6503942a2a636f8c782c086b17afd9a7be048df98215b248d0bbf4858b35de578ac13d19ad15dd19b2edef4e774ca99ec8af2901fb4006
|
|
7
|
+
data.tar.gz: 5f138cea24ee1faffdb173c2bed070bead948a7f9dbf17fdea6b44486998aca5517de3f3e7a99f7807fe8c5e969ab6783f09673f7158382742bb1056406d1cd7
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,54 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.3.0] - 2025-11-05
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **Mode System**: Control agent capabilities with workflow modes
|
|
7
|
+
- `/mode plan` - Read-only mode (grep, list, read)
|
|
8
|
+
- `/mode build` - Read + Write mode (grep, list, read, patch, write)
|
|
9
|
+
- `/mode verify` - Read + Execute mode (grep, list, read, execute)
|
|
10
|
+
- `/mode auto` - All tools enabled
|
|
11
|
+
- Each mode adapts system prompt to guide appropriate behavior
|
|
12
|
+
- Improved output formatting
|
|
13
|
+
- Suppressed verbose thinking/reasoning output
|
|
14
|
+
- Clean tool call display (e.g., `date` instead of full JSON hash)
|
|
15
|
+
- Mode switch confirmation messages
|
|
16
|
+
- Clear command feedback
|
|
17
|
+
- Design philosophy documentation in README
|
|
18
|
+
- Mode system documentation
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- **BREAKING**: Removed `llm-ollama` and `llm-openai` standalone executables (use main `elelem chat` command)
|
|
22
|
+
- **BREAKING**: Simplified architecture - consolidated all logic into Agent class
|
|
23
|
+
- Removed Configuration class
|
|
24
|
+
- Removed Toolbox system
|
|
25
|
+
- Removed MCP client infrastructure
|
|
26
|
+
- Removed Tool and Tools classes
|
|
27
|
+
- Removed TUI abstraction layer (direct puts/Reline usage)
|
|
28
|
+
- Removed API wrapper class
|
|
29
|
+
- Removed state machine
|
|
30
|
+
- Improved execute tool description to guide LLM toward direct command execution
|
|
31
|
+
- Extracted tool definitions from long inline strings to readable private methods
|
|
32
|
+
- Updated README with clear philosophy and usage examples
|
|
33
|
+
- Reduced total codebase from 417 to 395 lines (-5%)
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
- Working directory handling for execute tool (handles empty string cwd)
|
|
37
|
+
- REPL EOF handling (graceful exit when input stream ends)
|
|
38
|
+
- Tool call formatting now shows clean, readable commands
|
|
39
|
+
|
|
40
|
+
### Removed
|
|
41
|
+
- `exe/llm-ollama` (359 lines)
|
|
42
|
+
- `exe/llm-openai` (340 lines)
|
|
43
|
+
- `lib/elelem/configuration.rb`
|
|
44
|
+
- `lib/elelem/toolbox.rb` and toolbox/* files
|
|
45
|
+
- `lib/elelem/mcp_client.rb`
|
|
46
|
+
- `lib/elelem/tool.rb` and `lib/elelem/tools.rb`
|
|
47
|
+
- `lib/elelem/tui.rb`
|
|
48
|
+
- `lib/elelem/api.rb`
|
|
49
|
+
- `lib/elelem/states/*` (state machine infrastructure)
|
|
50
|
+
- Removed ~750 lines of unused/redundant code
|
|
51
|
+
|
|
3
52
|
## [0.2.1] - 2025-10-15
|
|
4
53
|
|
|
5
54
|
### Fixed
|
data/README.md
CHANGED
|
@@ -1,6 +1,64 @@
|
|
|
1
1
|
# Elelem
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Fast, correct, autonomous - Pick two
|
|
4
|
+
|
|
5
|
+
PURPOSE:
|
|
6
|
+
|
|
7
|
+
Elelem is a minimal coding agent written in Ruby. It is intended to
|
|
8
|
+
assist me (a software engineer and computer science student) with writing,
|
|
9
|
+
editing, and managing code and text files from the command line. It acts
|
|
10
|
+
as a direct interface to an LLM, providing it with a simple text-based
|
|
11
|
+
UI and access to the local filesystem.
|
|
12
|
+
|
|
13
|
+
DESIGN PRINCIPLES:
|
|
14
|
+
|
|
15
|
+
- Follows the Unix philosophy: simple, composable, minimal.
|
|
16
|
+
- Convention over configuration.
|
|
17
|
+
- Avoids unnecessary defensive checks, or complexity.
|
|
18
|
+
- Assumes a mature and responsible LLM that behaves like a capable engineer.
|
|
19
|
+
- Designed for my workflow and preferences.
|
|
20
|
+
- Efficient and minimal like aider - https://aider.chat/
|
|
21
|
+
- UX like Claude Code - https://docs.claude.com/en/docs/claude-code/overview
|
|
22
|
+
|
|
23
|
+
SYSTEM ASSUMPTIONS:
|
|
24
|
+
|
|
25
|
+
- This script is used on a Linux system with the following tools: Alacritty, tmux, Bash, and Vim.
|
|
26
|
+
- It is always run inside a Git repository.
|
|
27
|
+
- All project work is assumed to be version-controlled with Git.
|
|
28
|
+
- Git is expected to be available and working; no checks are necessary.
|
|
29
|
+
|
|
30
|
+
SCOPE:
|
|
31
|
+
|
|
32
|
+
- This program operates only on code and plain-text files.
|
|
33
|
+
- It does not need to support binary files.
|
|
34
|
+
- The LLM has full access to execute system commands.
|
|
35
|
+
- There are no sandboxing, permission, or validation layers.
|
|
36
|
+
- Execution is not restricted or monitored - responsibility is delegated to the LLM.
|
|
37
|
+
|
|
38
|
+
CONFIGURATION:
|
|
39
|
+
|
|
40
|
+
- Avoid adding configuration options unless absolutely necessary.
|
|
41
|
+
- Prefer hard-coded values that can be changed later if needed.
|
|
42
|
+
- Only introduce environment variables after repeated usage proves them worthwhile.
|
|
43
|
+
|
|
44
|
+
UI EXPECTATIONS:
|
|
45
|
+
|
|
46
|
+
- The TUI must remain simple, fast, and predictable.
|
|
47
|
+
- No mouse support or complex UI components are required.
|
|
48
|
+
- Interaction is strictly keyboard-driven.
|
|
49
|
+
|
|
50
|
+
CODING STANDARDS FOR LLM:
|
|
51
|
+
|
|
52
|
+
- Do not add error handling or logging unless it is essential for functionality.
|
|
53
|
+
- Keep methods short and single-purpose.
|
|
54
|
+
- Use descriptive, conventional names.
|
|
55
|
+
- Stick to Ruby's standard library whenever possible.
|
|
56
|
+
|
|
57
|
+
HELPFUL LINKS:
|
|
58
|
+
|
|
59
|
+
- https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents
|
|
60
|
+
- https://www.anthropic.com/engineering/writing-tools-for-agents
|
|
61
|
+
- https://simonwillison.net/2025/Sep/30/designing-agentic-loops/
|
|
4
62
|
|
|
5
63
|
## Installation
|
|
6
64
|
|
|
@@ -29,7 +87,6 @@ elelem chat
|
|
|
29
87
|
- `--host`: Specify Ollama host (default: localhost:11434)
|
|
30
88
|
- `--model`: Specify Ollama model (default: gpt-oss, currently only tested with gpt-oss)
|
|
31
89
|
- `--token`: Provide authentication token
|
|
32
|
-
- `--debug`: Enable debug logging
|
|
33
90
|
|
|
34
91
|
### Examples
|
|
35
92
|
|
|
@@ -39,29 +96,35 @@ elelem chat
|
|
|
39
96
|
|
|
40
97
|
# Chat with specific model and host
|
|
41
98
|
elelem chat --model llama2 --host remote-host:11434
|
|
42
|
-
|
|
43
|
-
# Enable debug mode
|
|
44
|
-
elelem chat --debug
|
|
45
99
|
```
|
|
46
100
|
|
|
47
101
|
### Features
|
|
48
102
|
|
|
49
103
|
- **Interactive REPL**: Clean command-line interface for chatting
|
|
50
|
-
- **
|
|
104
|
+
- **Mode System**: Control agent capabilities with workflow modes (plan, build, verify, auto)
|
|
105
|
+
- **Tool Execution**: Execute shell commands, read/write files, search code
|
|
51
106
|
- **Streaming Responses**: Real-time streaming of AI responses
|
|
52
|
-
- **State Machine**: Robust state management for different interaction modes
|
|
53
107
|
- **Conversation History**: Maintains context across the session
|
|
54
108
|
|
|
109
|
+
### Mode System
|
|
110
|
+
|
|
111
|
+
Control what tools the agent can access:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
/mode plan # Read-only (grep, list, read)
|
|
115
|
+
/mode build # Read + Write (grep, list, read, patch, write)
|
|
116
|
+
/mode verify # Read + Execute (grep, list, read, execute)
|
|
117
|
+
/mode auto # All tools enabled
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Each mode adapts the system prompt to guide appropriate behavior.
|
|
121
|
+
|
|
55
122
|
## Development
|
|
56
123
|
|
|
57
124
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
58
125
|
|
|
59
126
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
60
127
|
|
|
61
|
-
## Contributing
|
|
62
|
-
|
|
63
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/xlgmokha/elelem.
|
|
64
|
-
|
|
65
128
|
## License
|
|
66
129
|
|
|
67
130
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
data/lib/elelem/agent.rb
CHANGED
|
@@ -2,56 +2,272 @@
|
|
|
2
2
|
|
|
3
3
|
module Elelem
|
|
4
4
|
class Agent
|
|
5
|
-
attr_reader :
|
|
5
|
+
attr_reader :conversation, :client, :tools
|
|
6
6
|
|
|
7
|
-
def initialize(
|
|
8
|
-
@
|
|
9
|
-
@
|
|
10
|
-
@
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
def initialize(client)
|
|
8
|
+
@conversation = Conversation.new
|
|
9
|
+
@client = client
|
|
10
|
+
@tools = {
|
|
11
|
+
read: [grep_tool, list_tool, read_tool],
|
|
12
|
+
write: [patch_tool, write_tool],
|
|
13
|
+
execute: [exec_tool]
|
|
14
|
+
}
|
|
15
|
+
end
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
def repl
|
|
18
|
+
mode = Set.new([:read])
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
loop do
|
|
21
|
+
input = ask?("User> ")
|
|
22
|
+
break if input.nil?
|
|
23
|
+
if input.start_with?("/")
|
|
24
|
+
case input
|
|
25
|
+
when "/mode auto"
|
|
26
|
+
mode = Set[:read, :write, :execute]
|
|
27
|
+
puts " → Mode: auto (all tools enabled)"
|
|
28
|
+
when "/mode build"
|
|
29
|
+
mode = Set[:read, :write]
|
|
30
|
+
puts " → Mode: build (read + write)"
|
|
31
|
+
when "/mode plan"
|
|
32
|
+
mode = Set[:read]
|
|
33
|
+
puts " → Mode: plan (read-only)"
|
|
34
|
+
when "/mode verify"
|
|
35
|
+
mode = Set[:read, :execute]
|
|
36
|
+
puts " → Mode: verify (read + execute)"
|
|
37
|
+
when "/mode"
|
|
38
|
+
puts " Mode: #{mode.to_a.inspect}"
|
|
39
|
+
puts " Tools: #{tools_for(mode).map { |t| t.dig(:function, :name) }}"
|
|
40
|
+
when "/exit" then exit
|
|
41
|
+
when "/clear"
|
|
42
|
+
conversation.clear
|
|
43
|
+
puts " → Conversation cleared"
|
|
44
|
+
when "/context" then puts conversation.dump
|
|
45
|
+
else
|
|
46
|
+
puts help_banner
|
|
47
|
+
end
|
|
48
|
+
else
|
|
49
|
+
conversation.set_system_prompt(system_prompt_for(mode))
|
|
50
|
+
conversation.add(role: :user, content: input)
|
|
51
|
+
result = execute_turn(conversation.history, tools: tools_for(mode))
|
|
52
|
+
conversation.add(role: result[:role], content: result[:content])
|
|
53
|
+
end
|
|
54
|
+
end
|
|
18
55
|
end
|
|
19
56
|
|
|
20
|
-
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def ask?(text)
|
|
60
|
+
Reline.readline(text, true)&.strip
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def help_banner
|
|
64
|
+
<<~HELP
|
|
65
|
+
/mode auto build plan verify
|
|
66
|
+
/clear
|
|
67
|
+
/context
|
|
68
|
+
/exit
|
|
69
|
+
/help
|
|
70
|
+
HELP
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def tools_for(modes)
|
|
74
|
+
modes.map { |mode| tools[mode] }.flatten
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def system_prompt_for(mode)
|
|
78
|
+
base = "You are a reasoning coding and system agent."
|
|
79
|
+
|
|
80
|
+
case mode.to_a.sort
|
|
81
|
+
when [:read]
|
|
82
|
+
"#{base}\n\nRead and analyze. Understand before suggesting action."
|
|
83
|
+
when [:write]
|
|
84
|
+
"#{base}\n\nWrite clean, thoughtful code."
|
|
85
|
+
when [:execute]
|
|
86
|
+
"#{base}\n\nUse shell commands creatively to understand and manipulate the system."
|
|
87
|
+
when [:read, :write]
|
|
88
|
+
"#{base}\n\nFirst understand, then build solutions that integrate well."
|
|
89
|
+
when [:read, :execute]
|
|
90
|
+
"#{base}\n\nUse commands to deeply understand the system."
|
|
91
|
+
when [:write, :execute]
|
|
92
|
+
"#{base}\n\nCreate and execute freely. Have fun. Be kind."
|
|
93
|
+
when [:read, :write, :execute]
|
|
94
|
+
"#{base}\n\nYou have all tools. Use them wisely."
|
|
95
|
+
else
|
|
96
|
+
base
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def format_tool_call(name, args)
|
|
101
|
+
case name
|
|
102
|
+
when "execute"
|
|
103
|
+
cmd = args["cmd"]
|
|
104
|
+
cmd_args = args["args"] || []
|
|
105
|
+
cmd_args.empty? ? cmd : "#{cmd} #{cmd_args.join(' ')}"
|
|
106
|
+
when "grep" then "grep(#{args["query"]})"
|
|
107
|
+
when "list" then "list(#{args["path"] || "."})"
|
|
108
|
+
when "patch" then "patch(#{args["diff"]&.lines&.count || 0} lines)"
|
|
109
|
+
when "read" then "read(#{args["path"]})"
|
|
110
|
+
when "write" then "write(#{args["path"]})"
|
|
111
|
+
else
|
|
112
|
+
"#{name}(#{args.to_s[0...50]})"
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def execute_turn(messages, tools:)
|
|
117
|
+
turn_context = []
|
|
118
|
+
|
|
21
119
|
loop do
|
|
22
|
-
|
|
23
|
-
|
|
120
|
+
content = ""
|
|
121
|
+
tool_calls = []
|
|
122
|
+
|
|
123
|
+
print "Thinking..."
|
|
124
|
+
client.chat(messages + turn_context, tools) do |chunk|
|
|
125
|
+
msg = chunk["message"]
|
|
126
|
+
if msg
|
|
127
|
+
if msg["content"] && !msg["content"].empty?
|
|
128
|
+
print "\r\e[K" if content.empty?
|
|
129
|
+
print msg["content"]
|
|
130
|
+
content += msg["content"]
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
tool_calls += msg["tool_calls"] if msg["tool_calls"]
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
puts
|
|
138
|
+
turn_context << { role: "assistant", content: content, tool_calls: tool_calls }.compact
|
|
139
|
+
|
|
140
|
+
if tool_calls.any?
|
|
141
|
+
tool_calls.each do |call|
|
|
142
|
+
name = call.dig("function", "name")
|
|
143
|
+
args = call.dig("function", "arguments")
|
|
144
|
+
|
|
145
|
+
puts "Tool> #{format_tool_call(name, args)}"
|
|
146
|
+
result = run_tool(name, args)
|
|
147
|
+
turn_context << { role: "tool", content: JSON.dump(result) }
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
tool_calls = []
|
|
151
|
+
next
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
return { role: "assistant", content: content }
|
|
24
155
|
end
|
|
25
156
|
end
|
|
26
157
|
|
|
27
|
-
def
|
|
28
|
-
|
|
29
|
-
|
|
158
|
+
def run_exec(command, args: [], env: {}, cwd: Dir.pwd, stdin: nil)
|
|
159
|
+
cmd = command.is_a?(Array) ? command.first : command
|
|
160
|
+
cmd_args = command.is_a?(Array) ? command[1..] + args : args
|
|
161
|
+
stdout, stderr, status = Open3.capture3(env, cmd, *cmd_args, chdir: cwd, stdin_data: stdin)
|
|
162
|
+
{
|
|
163
|
+
"exit_status" => status.exitstatus,
|
|
164
|
+
"stdout" => stdout.to_s,
|
|
165
|
+
"stderr" => stderr.to_s
|
|
166
|
+
}
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def expand_path(path)
|
|
170
|
+
Pathname.new(path).expand_path
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def read_file(path)
|
|
174
|
+
full_path = expand_path(path)
|
|
175
|
+
full_path.exist? ? { content: full_path.read } : { error: "File not found: #{path}" }
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def write_file(path, content)
|
|
179
|
+
full_path = expand_path(path)
|
|
180
|
+
FileUtils.mkdir_p(full_path.dirname)
|
|
181
|
+
{ bytes_written: full_path.write(content) }
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def run_tool(name, args)
|
|
185
|
+
case name
|
|
186
|
+
when "execute" then run_exec(args["cmd"], args: args["args"] || [], env: args["env"] || {}, cwd: args["cwd"].to_s.empty? ? Dir.pwd : args["cwd"], stdin: args["stdin"])
|
|
187
|
+
when "grep" then run_exec("git", args: ["grep", "-nI", args["query"]])
|
|
188
|
+
when "list" then run_exec("git", args: args["path"] ? ["ls-files", "--", args["path"]] : ["ls-files"])
|
|
189
|
+
when "patch" then run_exec("git", args: ["apply", "--index", "--whitespace=nowarn", "-p1"], stdin: args["diff"])
|
|
190
|
+
when "read" then read_file(args["path"])
|
|
191
|
+
when "write" then write_file(args["path"], args["content"])
|
|
30
192
|
else
|
|
31
|
-
|
|
193
|
+
{ error: "Unknown tool", name: name, args: args }
|
|
32
194
|
end
|
|
33
|
-
|
|
195
|
+
rescue => error
|
|
196
|
+
{ error: error.message, name: name, args: args }
|
|
34
197
|
end
|
|
35
198
|
|
|
36
|
-
def
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
199
|
+
def exec_tool
|
|
200
|
+
build_tool(
|
|
201
|
+
"execute",
|
|
202
|
+
"Execute shell commands directly. Commands run in a shell context. Examples: 'date', 'git status'.",
|
|
203
|
+
{
|
|
204
|
+
cmd: { type: "string" },
|
|
205
|
+
args: { type: "array", items: { type: "string" } },
|
|
206
|
+
env: { type: "object", additionalProperties: { type: "string" } },
|
|
207
|
+
cwd: { type: "string", description: "Working directory (defaults to current)" },
|
|
208
|
+
stdin: { type: "string" }
|
|
209
|
+
},
|
|
210
|
+
["cmd"]
|
|
211
|
+
)
|
|
42
212
|
end
|
|
43
213
|
|
|
44
|
-
def
|
|
45
|
-
|
|
46
|
-
|
|
214
|
+
def grep_tool
|
|
215
|
+
build_tool(
|
|
216
|
+
"grep",
|
|
217
|
+
"Search all git-tracked files using git grep. Returns file paths with matching line numbers.",
|
|
218
|
+
{ query: { type: "string" } },
|
|
219
|
+
["query"]
|
|
220
|
+
)
|
|
47
221
|
end
|
|
48
222
|
|
|
49
|
-
def
|
|
50
|
-
|
|
223
|
+
def list_tool
|
|
224
|
+
build_tool(
|
|
225
|
+
"list",
|
|
226
|
+
"List all git-tracked files in the repository, optionally filtered by path.",
|
|
227
|
+
{ path: { type: "string" } }
|
|
228
|
+
)
|
|
51
229
|
end
|
|
52
230
|
|
|
53
|
-
|
|
231
|
+
def patch_tool
|
|
232
|
+
build_tool(
|
|
233
|
+
"patch",
|
|
234
|
+
"Apply a unified diff patch via 'git apply'. Use for surgical edits to existing files.",
|
|
235
|
+
{ diff: { type: "string" } },
|
|
236
|
+
["diff"]
|
|
237
|
+
)
|
|
238
|
+
end
|
|
54
239
|
|
|
55
|
-
|
|
240
|
+
def read_tool
|
|
241
|
+
build_tool(
|
|
242
|
+
"read",
|
|
243
|
+
"Read complete contents of a file. Requires exact file path.",
|
|
244
|
+
{ path: { type: "string" } },
|
|
245
|
+
["path"]
|
|
246
|
+
)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def write_tool
|
|
250
|
+
build_tool(
|
|
251
|
+
"write",
|
|
252
|
+
"Write complete file contents (overwrites existing files). Creates parent directories automatically.",
|
|
253
|
+
{ path: { type: "string" }, content: { type: "string" } },
|
|
254
|
+
["path", "content"]
|
|
255
|
+
)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def build_tool(name, description, properties, required = [])
|
|
259
|
+
{
|
|
260
|
+
type: "function",
|
|
261
|
+
function: {
|
|
262
|
+
name: name,
|
|
263
|
+
description: description,
|
|
264
|
+
parameters: {
|
|
265
|
+
type: "object",
|
|
266
|
+
properties: properties,
|
|
267
|
+
required: required
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
end
|
|
56
272
|
end
|
|
57
273
|
end
|
data/lib/elelem/application.rb
CHANGED
|
@@ -3,10 +3,6 @@
|
|
|
3
3
|
module Elelem
|
|
4
4
|
class Application < Thor
|
|
5
5
|
desc "chat", "Start the REPL"
|
|
6
|
-
method_option :help,
|
|
7
|
-
aliases: "-h",
|
|
8
|
-
type: :boolean,
|
|
9
|
-
desc: "Display usage information"
|
|
10
6
|
method_option :host,
|
|
11
7
|
aliases: "--host",
|
|
12
8
|
type: :string,
|
|
@@ -17,32 +13,16 @@ module Elelem
|
|
|
17
13
|
type: :string,
|
|
18
14
|
desc: "Ollama model",
|
|
19
15
|
default: ENV.fetch("OLLAMA_MODEL", "gpt-oss")
|
|
20
|
-
|
|
21
|
-
aliases: "--token",
|
|
22
|
-
type: :string,
|
|
23
|
-
desc: "Ollama token",
|
|
24
|
-
default: ENV.fetch("OLLAMA_API_KEY", nil)
|
|
25
|
-
method_option :debug,
|
|
26
|
-
aliases: "--debug",
|
|
27
|
-
type: :boolean,
|
|
28
|
-
desc: "Debug mode",
|
|
29
|
-
default: false
|
|
16
|
+
|
|
30
17
|
def chat(*)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
token: options[:token],
|
|
38
|
-
debug: options[:debug]
|
|
39
|
-
)
|
|
40
|
-
say "Agent (#{configuration.model})", :green
|
|
41
|
-
say configuration.tools.banner.to_s, :green
|
|
18
|
+
client = Net::Llm::Ollama.new(
|
|
19
|
+
host: options[:host],
|
|
20
|
+
model: options[:model],
|
|
21
|
+
)
|
|
22
|
+
say "Agent (#{options[:model]})", :green
|
|
23
|
+
agent = Agent.new(client)
|
|
42
24
|
|
|
43
|
-
|
|
44
|
-
agent.repl
|
|
45
|
-
end
|
|
25
|
+
agent.repl
|
|
46
26
|
end
|
|
47
27
|
|
|
48
28
|
desc "version", "The version of this CLI"
|
data/lib/elelem/conversation.rb
CHANGED
|
@@ -4,7 +4,7 @@ module Elelem
|
|
|
4
4
|
class Conversation
|
|
5
5
|
ROLES = %i[system assistant user tool].freeze
|
|
6
6
|
|
|
7
|
-
def initialize(items =
|
|
7
|
+
def initialize(items = default_context)
|
|
8
8
|
@items = items
|
|
9
9
|
end
|
|
10
10
|
|
|
@@ -12,7 +12,6 @@ module Elelem
|
|
|
12
12
|
@items
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
# :TODO truncate conversation history
|
|
16
15
|
def add(role: :user, content: "")
|
|
17
16
|
role = role.to_sym
|
|
18
17
|
raise "unknown role: #{role}" unless ROLES.include?(role)
|
|
@@ -25,8 +24,24 @@ module Elelem
|
|
|
25
24
|
end
|
|
26
25
|
end
|
|
27
26
|
|
|
27
|
+
def clear
|
|
28
|
+
@items = default_context
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def set_system_prompt(prompt)
|
|
32
|
+
@items[0] = { role: :system, content: prompt }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def dump
|
|
36
|
+
JSON.pretty_generate(@items)
|
|
37
|
+
end
|
|
38
|
+
|
|
28
39
|
private
|
|
29
40
|
|
|
41
|
+
def default_context
|
|
42
|
+
[{ role: "system", content: system_prompt }]
|
|
43
|
+
end
|
|
44
|
+
|
|
30
45
|
def system_prompt
|
|
31
46
|
ERB.new(Pathname.new(__dir__).join("system_prompt.erb").read).result(binding)
|
|
32
47
|
end
|
|
@@ -1,18 +1,5 @@
|
|
|
1
|
-
You are a
|
|
1
|
+
You are a reasoning coding and system agent.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
**Guidance**: Read `COMMANDMENTS.md` for self-improvement principles using `file action=read path=COMMANDMENTS.md`
|
|
8
|
-
|
|
9
|
-
**Tools**:
|
|
10
|
-
- `exec command="shell command"` - Run commands/tests
|
|
11
|
-
- `file action=read|write path=filepath content=...` - Read/write files
|
|
12
|
-
- `web action=search|fetch query=... url=...` - Internet access
|
|
13
|
-
- `memory action=store|retrieve|search key=... content=...` - Persistent memory
|
|
14
|
-
- `prompt question="..."` - Ask user questions
|
|
15
|
-
|
|
16
|
-
Context: <%= Time.now.strftime("%Y-%m-%d %H:%M:%S") %> | <%= Dir.pwd %> | <%= `uname -a`.strip %>
|
|
17
|
-
|
|
18
|
-
Focus on the user's request and continuously improve your capabilities.
|
|
3
|
+
- Less is more
|
|
4
|
+
- No code comments
|
|
5
|
+
- No trailing whitespace
|
data/lib/elelem/version.rb
CHANGED
data/lib/elelem.rb
CHANGED
|
@@ -1,37 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "cli/ui"
|
|
4
3
|
require "erb"
|
|
4
|
+
require "fileutils"
|
|
5
5
|
require "json"
|
|
6
6
|
require "json-schema"
|
|
7
7
|
require "logger"
|
|
8
8
|
require "net/llm"
|
|
9
9
|
require "open3"
|
|
10
|
+
require "pathname"
|
|
10
11
|
require "reline"
|
|
12
|
+
require "set"
|
|
11
13
|
require "thor"
|
|
12
14
|
require "timeout"
|
|
13
15
|
|
|
14
16
|
require_relative "elelem/agent"
|
|
15
|
-
require_relative "elelem/api"
|
|
16
17
|
require_relative "elelem/application"
|
|
17
|
-
require_relative "elelem/configuration"
|
|
18
18
|
require_relative "elelem/conversation"
|
|
19
|
-
require_relative "elelem/mcp_client"
|
|
20
|
-
require_relative "elelem/states/idle"
|
|
21
|
-
require_relative "elelem/states/working"
|
|
22
|
-
require_relative "elelem/states/working/state"
|
|
23
|
-
require_relative "elelem/states/working/error"
|
|
24
|
-
require_relative "elelem/states/working/executing"
|
|
25
|
-
require_relative "elelem/states/working/talking"
|
|
26
|
-
require_relative "elelem/states/working/thinking"
|
|
27
|
-
require_relative "elelem/states/working/waiting"
|
|
28
|
-
require_relative "elelem/tool"
|
|
29
|
-
require_relative "elelem/toolbox"
|
|
30
|
-
require_relative "elelem/tools"
|
|
31
|
-
require_relative "elelem/tui"
|
|
32
19
|
require_relative "elelem/version"
|
|
33
20
|
|
|
34
|
-
CLI::UI::StdoutRouter.enable
|
|
35
21
|
Reline.input = $stdin
|
|
36
22
|
Reline.output = $stdout
|
|
37
23
|
|