Commit 98c664e
Changed files (11)
lib
elelem
sig
lib/elelem/states/working.rb
@@ -5,14 +5,12 @@ module Elelem
module Working
class << self
def run(agent)
- done = false
state = Waiting.new(agent)
loop do
agent.api.chat(agent.conversation.history) do |chunk|
response = JSON.parse(chunk)
message = normalize(response["message"] || {})
- done = response["done"]
agent.logger.debug("#{state.display_name}: #{message}")
state = state.run(message)
lib/elelem/toolbox/file.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module Elelem
+ module Toolbox
+ class File < ::Elelem::Tool
+ attr_reader :tui
+
+ def initialize(configuration)
+ @tui = configuration.tui
+ super("file", "Read/write files in project directory", {
+ type: "object",
+ properties: {
+ action: {
+ type: "string",
+ enum: ["read", "write", "append"],
+ description: "File operation to perform"
+ },
+ path: {
+ type: "string",
+ description: "Relative path to file from project root"
+ },
+ content: {
+ type: "string",
+ description: "Content to write/apppend (only for write/append actions)"
+ }
+ },
+ required: ["action", "path"]
+ })
+ end
+
+ def call(args)
+ path = Pathname.pwd.join(args["path"])
+ case args["action"]
+ when "read"
+ path.read
+ when "write"
+ path.write(args["content"])
+ "File written successfully"
+ when "append"
+ path.open("a") { |f| f << args["content"] }
+ "Content appended successfully"
+ end
+ rescue => e
+ e.message
+ end
+ end
+ end
+end
lib/elelem/toolbox/git.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Elelem
+ module Toolbox
+ class Git < ::Elelem::Tool
+ def initialize(configuration)
+ @configuration = configuration
+ super("git", "Perform git operations on repository", {
+ type: "object",
+ properties: {
+ action: {
+ type: "string",
+ enum: ["commit", "diff", "log"],
+ description: "Git operation to perform"
+ },
+ message: {
+ type: "string",
+ description: "Commit message (required for commit action)"
+ }
+ },
+ required: ["action"]
+ })
+ end
+
+ def call(args)
+ case args["action"]
+ when "commit"
+ `git add . && git commit -m "#{args["message"]}"`
+ "Committed changes: #{args["message"]}"
+ when "diff"
+ `git diff HEAD`
+ when "log"
+ `git log --oneline -n 10`
+ end
+ rescue => e
+ e.message
+ end
+ end
+ end
+end
lib/elelem/toolbox/prompt.rb
@@ -3,7 +3,7 @@
module Elelem
module Toolbox
class Prompt < Tool
- def initialize
+ def initialize(configuration)
super(
"prompt",
"Ask the user a question and get their response.",
lib/elelem/toolbox/search.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Elelem
+ module Toolbox
+ class Search < ::Elelem::Tool
+ def initialize(configuration)
+ @configuration = configuration
+ super("search", "Search files in project directory", {
+ type: "object",
+ properties: {
+ pattern: {
+ type: "string",
+ description: "Search pattern (grep compatible)"
+ },
+ path: {
+ type: "string",
+ description: "Directory path to search from (default: project root)"
+ }
+ },
+ required: ["pattern"]
+ })
+ end
+
+ def call(args)
+ path = args["path"] || "."
+ `grep -rnw '#{args["pattern"]}' #{path}`
+ rescue => e
+ e.message
+ end
+ end
+ end
+end
lib/elelem/configuration.rb
@@ -37,7 +37,15 @@ module Elelem
end
def tools
- @tools ||= Tools.new(self, [Toolbox::Bash.new(self), Toolbox::Prompt.new] + mcp_tools)
+ @tools ||= Tools.new(self,
+ [
+ Toolbox::Bash.new(self),
+ Toolbox::File.new(self),
+ Toolbox::Git.new(self),
+ Toolbox::Prompt.new(self),
+ Toolbox::Search.new(self),
+ ] + mcp_tools
+ )
end
def cleanup
lib/elelem/toolbox.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+require_relative "toolbox/bash"
+require_relative "toolbox/file"
+require_relative "toolbox/git"
+require_relative "toolbox/mcp"
+require_relative "toolbox/prompt"
+require_relative "toolbox/search"
lib/elelem.rb
@@ -1,14 +1,11 @@
# frozen_string_literal: true
-# require "base64"
require "cli/ui"
-# require "ed25519"
require "erb"
require "json"
require "json-schema"
require "logger"
require "net/http"
-# require "net/ssh"
require "open3"
require "reline"
require "thor"
@@ -30,9 +27,7 @@ require_relative "elelem/states/working/talking"
require_relative "elelem/states/working/thinking"
require_relative "elelem/states/working/waiting"
require_relative "elelem/tool"
-require_relative "elelem/toolbox/bash"
-require_relative "elelem/toolbox/mcp"
-require_relative "elelem/toolbox/prompt"
+require_relative "elelem/toolbox"
require_relative "elelem/tools"
require_relative "elelem/tui"
require_relative "elelem/version"
sig/elelem.rbs
@@ -1,4 +0,0 @@
-module Elelem
- VERSION: String
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
-end
elelem.gemspec
@@ -50,27 +50,27 @@ Gem::Specification.new do |spec|
"lib/elelem/states/working/waiting.rb",
"lib/elelem/system_prompt.erb",
"lib/elelem/tool.rb",
+ "lib/elelem/toolbox.rb",
"lib/elelem/toolbox/bash.rb",
+ "lib/elelem/toolbox/file.rb",
+ "lib/elelem/toolbox/git.rb",
"lib/elelem/toolbox/mcp.rb",
+ "lib/elelem/toolbox/prompt.rb",
+ "lib/elelem/toolbox/search.rb",
"lib/elelem/tools.rb",
"lib/elelem/tui.rb",
"lib/elelem/version.rb",
- "sig/elelem.rbs"
]
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]
- # spec.add_dependency "base64"
- # spec.add_dependency "bcrypt_pbkdf"
spec.add_dependency "cli-ui"
- # spec.add_dependency "ed25519"
spec.add_dependency "erb"
spec.add_dependency "json"
spec.add_dependency "json-schema"
spec.add_dependency "logger"
spec.add_dependency "net-http"
- # spec.add_dependency "net-ssh"
spec.add_dependency "open3"
spec.add_dependency "reline"
spec.add_dependency "thor"
README.md
@@ -58,100 +58,6 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
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).
-REPL State Diagram
-
-```
- ┌─────────────────┐
- │ START/INIT │
- └─────────┬───────┘
- │
- v
- ┌─────────────────┐
- ┌────▶│ IDLE (Prompt) │◄────┐
- │ │ Shows "> " │ │
- │ └─────────┬───────┘ │
- │ │ │
- │ │ User input │
- │ v │
- │ ┌─────────────────┐ │
- │ │ PROCESSING │ │
- │ │ INPUT │ │
- │ └─────────┬───────┘ │
- │ │ │
- │ │ API call │
- │ v │
- │ ┌─────────────────┐ │
- │ │ STREAMING │ │
- │ ┌──▶│ RESPONSE │─────┤
- │ │ └─────────┬───────┘ │
- │ │ │ │ done=true
- │ │ │ Parse chunk │
- │ │ v │
- │ │ ┌─────────────────┐ │
- │ │ │ MESSAGE TYPE │ │
- │ │ │ ROUTING │ │
- │ │ └─────┬─┬─┬───────┘ │
- │ │ │ │ │ │
- ┌────────┴─┴─────────┘ │ └─────────────┴──────────┐
- │ │ │
- v v v
- ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
- │ THINKING │ │ TOOL │ │ CONTENT │
- │ STATE │ │ EXECUTION │ │ OUTPUT │
- │ │ │ STATE │ │ STATE │
- └─────────────┘ └─────┬───────┘ └─────────────┘
- │ │ │
- │ │ done=false │
- └───────────────────┼──────────────────────────┘
- │
- v
- ┌─────────────────┐
- │ CONTINUE │
- │ STREAMING │
- └─────────────────┘
- │
- └─────────────────┐
- │
- ┌─────────────────┐ │
- │ ERROR STATE │ │
- │ (Exception) │ │
- └─────────────────┘ │
- ▲ │
- │ Invalid response │
- └────────────────────────────┘
-
- EXIT CONDITIONS:
- ┌─────────────────────────┐
- │ • User enters "" │
- │ • User enters "exit" │
- │ • EOF (Ctrl+D) │
- │ • nil input │
- └─────────────────────────┘
- │
- v
- ┌─────────────────────────┐
- │ TERMINATE │
- └─────────────────────────┘
-```
-
-Key Transitions:
-
-1. IDLE → PROCESSING: User enters any non-empty, non-"exit" input
-2. PROCESSING → STREAMING: API call initiated to Ollama
-3. STREAMING → MESSAGE ROUTING: Each chunk received is parsed
-4. MESSAGE ROUTING → States: Based on message content:
- - thinking → THINKING STATE
- - tool_calls → TOOL EXECUTION STATE
- - content → CONTENT OUTPUT STATE
- - Invalid format → ERROR STATE
-5. All States → IDLE: When done=true from API response
-6. TOOL EXECUTION → STREAMING: Sets done=false to continue conversation
-7. Any State → TERMINATE: On exit conditions
-
-The REPL operates as a continuous loop where the primary flow is IDLE → PROCESSING → STREAMING →
-back to IDLE, with the streaming phase potentially cycling through multiple message types before
-completion.
-
## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/xlgmokha/elelem.