elelem 0.1.1 → 0.1.2

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: 139cfa01fc1cb7c7a7d7c7b86fa72d49027dfeead68754dfd1be3d864228dbff
4
- data.tar.gz: ee500832246d4a8c6e9c5c6aab4daadbbfe9b4c1724afe771de310a3dc402eb3
3
+ metadata.gz: 8a5469d2d253c6e0d09f60de69dafffdffb305da3f5864a4f6aa9bbac5ae7da6
4
+ data.tar.gz: 60d9d35b759e0722b557cefbb4fb6d6b1713661de432efb8283ae1cd7f951e05
5
5
  SHA512:
6
- metadata.gz: 920741eb6faf5bcfe67b78c62e65496012919a65d52b1919eefea0bedafd11f65de399c16d9bd6b3d812f2d21fd47303c8145267b9368d15cdba741a92dd50ef
7
- data.tar.gz: e95155995fe0e21242bef5cd574699a47025b79064857183617b8d99c6d309885a226851db714ab2c8cb4b0a053055821b1f6b013df8e33b97034b0cd05bc270
6
+ metadata.gz: 46073936ca9abcf83897e83355b7f979ebf2866d16351ccced55d70a8c311d8068acb3a78126b85b7b6622b45e6b2d51dc86f35914a25fdec937ad60214e4da9
7
+ data.tar.gz: d7233c12ed3b2359c5c7a1d47e908951e0651f19fb8d07777e34aa0fb5ddfc51f4368a7a192136a743d1009a9a26239da572555dd5d3d90e99e1a4e2668c2d47
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.2] - 2025-08-14
4
+
5
+ ### Fixed
6
+ - Fixed critical bug where bash tool had nested parameters schema causing tool calls to fail with "no implicit conversion of nil into String" error
7
+
3
8
  ## [0.1.1] - 2025-08-12
4
9
 
5
10
  ### Fixed
data/exe/elelem CHANGED
@@ -3,6 +3,9 @@
3
3
 
4
4
  require "elelem"
5
5
 
6
+ Reline.input = $stdin
7
+ Reline.output = $stdout
8
+
6
9
  Signal.trap("INT") do
7
10
  exit(1)
8
11
  end
data/lib/elelem/agent.rb CHANGED
@@ -2,11 +2,12 @@
2
2
 
3
3
  module Elelem
4
4
  class Agent
5
- attr_reader :api, :conversation, :logger
5
+ attr_reader :api, :conversation, :logger, :model
6
6
 
7
7
  def initialize(configuration)
8
8
  @api = configuration.api
9
9
  @configuration = configuration
10
+ @model = configuration.model
10
11
  @conversation = configuration.conversation
11
12
  @logger = configuration.logger
12
13
  transition_to(Idle.new)
@@ -36,6 +37,18 @@ module Elelem
36
37
  configuration.tools.execute(tool_call)
37
38
  end
38
39
 
40
+ def show_progress(message, prefix = "[.]", colour: :gray)
41
+ configuration.tui.show_progress(message, prefix, colour: colour)
42
+ end
43
+
44
+ def clear_line
45
+ configuration.tui.clear_line
46
+ end
47
+
48
+ def complete_progress(message = "Completed")
49
+ configuration.tui.complete_progress(message)
50
+ end
51
+
39
52
  def quit
40
53
  logger.debug("Exiting...")
41
54
  exit
data/lib/elelem/api.rb CHANGED
@@ -8,7 +8,7 @@ module Elelem
8
8
  @configuration = configuration
9
9
  end
10
10
 
11
- def chat(messages)
11
+ def chat(messages, &block)
12
12
  body = {
13
13
  messages: messages,
14
14
  model: configuration.model,
@@ -28,9 +28,7 @@ module Elelem
28
28
  configuration.http.request(req) do |response|
29
29
  raise response.inspect unless response.code == "200"
30
30
 
31
- response.read_body do |chunk|
32
- yield(chunk)
33
- end
31
+ response.read_body(&block)
34
32
  end
35
33
  end
36
34
  end
@@ -4,29 +4,29 @@ module Elelem
4
4
  class Application < Thor
5
5
  desc "chat", "Start the REPL"
6
6
  method_option :help,
7
- aliases: "-h",
8
- type: :boolean,
9
- desc: "Display usage information"
7
+ aliases: "-h",
8
+ type: :boolean,
9
+ desc: "Display usage information"
10
10
  method_option :host,
11
- aliases: "--host",
12
- type: :string,
13
- desc: "Ollama host",
14
- default: ENV.fetch("OLLAMA_HOST", "localhost:11434")
11
+ aliases: "--host",
12
+ type: :string,
13
+ desc: "Ollama host",
14
+ default: ENV.fetch("OLLAMA_HOST", "localhost:11434")
15
15
  method_option :model,
16
- aliases: "--model",
17
- type: :string,
18
- desc: "Ollama model",
19
- default: ENV.fetch("OLLAMA_MODEL", "gpt-oss")
16
+ aliases: "--model",
17
+ type: :string,
18
+ desc: "Ollama model",
19
+ default: ENV.fetch("OLLAMA_MODEL", "gpt-oss")
20
20
  method_option :token,
21
- aliases: "--token",
22
- type: :string,
23
- desc: "Ollama token",
24
- default: ENV.fetch("OLLAMA_API_KEY", nil)
21
+ aliases: "--token",
22
+ type: :string,
23
+ desc: "Ollama token",
24
+ default: ENV.fetch("OLLAMA_API_KEY", nil)
25
25
  method_option :debug,
26
- aliases: "--debug",
27
- type: :boolean,
28
- desc: "Debug mode",
29
- default: false
26
+ aliases: "--debug",
27
+ type: :boolean,
28
+ desc: "Debug mode",
29
+ default: false
30
30
  def chat(*)
31
31
  if options[:help]
32
32
  invoke :help, ["chat"]
@@ -37,8 +37,8 @@ module Elelem
37
37
  token: options[:token],
38
38
  debug: options[:debug]
39
39
  )
40
- say "Ollama Agent (#{configuration.model})", :green
41
- say "Tools:\n #{configuration.tools.banner}", :green
40
+ say "Agent (#{configuration.model})", :green
41
+ say configuration.tools.banner.to_s, :green
42
42
 
43
43
  agent = Agent.new(configuration)
44
44
  agent.repl
@@ -28,7 +28,7 @@ module Elelem
28
28
 
29
29
  def logger
30
30
  @logger ||= Logger.new(debug ? "elelem.log" : "/dev/null").tap do |logger|
31
- logger.formatter = ->(_, _, _, message) { message.strip + "\n" }
31
+ logger.formatter = ->(_, _, _, message) { "#{message.to_s.strip}\n" }
32
32
  end
33
33
  end
34
34
 
@@ -41,7 +41,7 @@ module Elelem
41
41
  end
42
42
 
43
43
  def tools
44
- @tools ||= Tools.new
44
+ @tools ||= Tools.new(self, [BashTool.new(self)] + mcp_tools)
45
45
  end
46
46
 
47
47
  private
@@ -49,5 +49,24 @@ module Elelem
49
49
  def scheme
50
50
  host.match?(/\A(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(:\d+)?\z/) ? "http" : "https"
51
51
  end
52
+
53
+ def mcp_tools(clients = [serena_client])
54
+ return [] if ENV["SMALL"]
55
+
56
+ @mcp_tools ||= clients.map { |client| client.tools.map { |tool| MCPTool.new(client, tui, tool) } }.flatten
57
+ end
58
+
59
+ def serena_client
60
+ MCPClient.new(self, [
61
+ "uvx",
62
+ "--from",
63
+ "git+https://github.com/oraios/serena",
64
+ "serena",
65
+ "start-mcp-server",
66
+ "--transport", "stdio",
67
+ "--context", "ide-assistant",
68
+ "--project", Dir.pwd
69
+ ])
70
+ end
52
71
  end
53
72
  end
@@ -2,16 +2,9 @@
2
2
 
3
3
  module Elelem
4
4
  class Conversation
5
- SYSTEM_MESSAGE = <<~SYS
6
- You are ChatGPT, a helpful assistant with reasoning capabilities.
7
- Current date: #{Time.now.strftime("%Y-%m-%d")}.
8
- System info: `uname -a` output: #{`uname -a`.strip}
9
- Reasoning: high
10
- SYS
5
+ ROLES = %i[system assistant user tool].freeze
11
6
 
12
- ROLES = [:system, :assistant, :user, :tool].freeze
13
-
14
- def initialize(items = [{ role: "system", content: SYSTEM_MESSAGE }])
7
+ def initialize(items = [{ role: "system", content: system_prompt }])
15
8
  @items = items
16
9
  end
17
10
 
@@ -23,7 +16,7 @@ module Elelem
23
16
  def add(role: :user, content: "")
24
17
  role = role.to_sym
25
18
  raise "unknown role: #{role}" unless ROLES.include?(role)
26
- return if content.empty?
19
+ return if content.nil? || content.empty?
27
20
 
28
21
  if @items.last && @items.last[:role] == role
29
22
  @items.last[:content] += content
@@ -31,5 +24,11 @@ module Elelem
31
24
  @items.push({ role: role, content: content })
32
25
  end
33
26
  end
27
+
28
+ private
29
+
30
+ def system_prompt
31
+ ERB.new(Pathname.new(__dir__).join("system_prompt.erb").read).result(binding)
32
+ end
34
33
  end
35
34
  end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Elelem
4
+ class MCPClient
5
+ attr_reader :tools
6
+
7
+ def initialize(configuration, command = [])
8
+ @configuration = configuration
9
+ @stdin, @stdout, @stderr, @worker = Open3.popen3(*command, pgroup: true)
10
+
11
+ # 1. Send initialize request
12
+ send_request(
13
+ method: "initialize",
14
+ params: {
15
+ protocolVersion: "2024-11-05",
16
+ capabilities: {
17
+ tools: {}
18
+ },
19
+ clientInfo: {
20
+ name: "Elelem",
21
+ version: Elelem::VERSION
22
+ }
23
+ }
24
+ )
25
+
26
+ # 2. Send initialized notification (required by MCP protocol)
27
+ send_notification(method: "notifications/initialized")
28
+
29
+ # 3. Now we can request tools
30
+ @tools = send_request(method: "tools/list")&.dig("tools") || []
31
+ end
32
+
33
+ def connected?
34
+ return false unless @worker&.alive?
35
+ return false unless @stdin && !@stdin.closed?
36
+ return false unless @stdout && !@stdout.closed?
37
+
38
+ begin
39
+ Process.getpgid(@worker.pid)
40
+ true
41
+ rescue Errno::ESRCH
42
+ false
43
+ end
44
+ end
45
+
46
+ def call(name, arguments = {})
47
+ send_request(
48
+ method: "tools/call",
49
+ params: {
50
+ name: name,
51
+ arguments: arguments
52
+ }
53
+ )
54
+ end
55
+
56
+ private
57
+
58
+ attr_reader :stdin, :stdout, :stderr, :worker, :configuration
59
+
60
+ def send_request(method:, params: {})
61
+ return {} unless connected?
62
+
63
+ request = {
64
+ jsonrpc: "2.0",
65
+ id: Time.now.to_i,
66
+ method: method
67
+ }
68
+ request[:params] = params unless params.empty?
69
+ configuration.logger.debug(JSON.pretty_generate(request))
70
+
71
+ @stdin.puts(JSON.generate(request))
72
+ @stdin.flush
73
+
74
+ response_line = @stdout.gets&.strip
75
+ return {} if response_line.nil? || response_line.empty?
76
+
77
+ response = JSON.parse(response_line)
78
+ configuration.logger.debug(JSON.pretty_generate(response))
79
+
80
+ if response["error"]
81
+ configuration.logger.error(response["error"]["message"])
82
+ { error: response["error"]["message"] }
83
+ else
84
+ response["result"]
85
+ end
86
+ end
87
+
88
+ def send_notification(method:, params: {})
89
+ notification = {
90
+ jsonrpc: "2.0",
91
+ method: method
92
+ }
93
+ notification[:params] = params unless params.empty?
94
+ configuration.logger.debug("Sending notification: #{JSON.pretty_generate(notification)}")
95
+ @stdin.puts(JSON.generate(notification))
96
+ @stdin.flush
97
+ end
98
+ end
99
+ end
data/lib/elelem/state.rb CHANGED
@@ -4,12 +4,19 @@ module Elelem
4
4
  class Idle
5
5
  def run(agent)
6
6
  agent.logger.debug("Idling...")
7
- input = agent.prompt("\n> ")
8
- agent.quit if input.nil? || input.empty? || input == "exit"
7
+ agent.say("#{Dir.pwd} (#{agent.model}) [#{git_branch}]", colour: :magenta, newline: true)
8
+ input = agent.prompt("モ ")
9
+ agent.quit if input.nil? || input.empty? || input == "exit" || input == "quit"
9
10
 
10
11
  agent.conversation.add(role: :user, content: input)
11
12
  agent.transition_to(Working.new)
12
13
  end
14
+
15
+ private
16
+
17
+ def git_branch
18
+ `git branch --no-color --show-current --no-abbrev`.strip
19
+ end
13
20
  end
14
21
 
15
22
  class Working
@@ -27,29 +34,39 @@ module Elelem
27
34
 
28
35
  class Waiting < State
29
36
  def process(message)
30
- state = self
37
+ state_for(message)&.process(message)
38
+ end
39
+
40
+ private
31
41
 
42
+ def state_for(message)
32
43
  if message["thinking"] && !message["thinking"].empty?
33
- state = Thinking.new(agent)
44
+ Thinking.new(agent)
34
45
  elsif message["tool_calls"]&.any?
35
- state = Executing.new(agent)
46
+ Executing.new(agent)
36
47
  elsif message["content"] && !message["content"].empty?
37
- state = Talking.new(agent)
38
- else
39
- state = nil
48
+ Talking.new(agent)
40
49
  end
41
-
42
- state&.process(message)
43
50
  end
44
51
  end
45
52
 
46
53
  class Thinking < State
54
+ def initialize(agent)
55
+ super(agent)
56
+ @progress_shown = false
57
+ end
58
+
47
59
  def process(message)
48
60
  if message["thinking"] && !message["thinking"]&.empty?
61
+ unless @progress_shown
62
+ agent.show_progress("Thinking...", "[*]", colour: :yellow)
63
+ agent.say("\n\n", newline: false)
64
+ @progress_shown = true
65
+ end
49
66
  agent.say(message["thinking"], colour: :gray, newline: false)
50
67
  self
51
68
  else
52
- agent.say("", newline: true)
69
+ agent.say("\n\n", newline: false)
53
70
  Waiting.new(agent).process(message)
54
71
  end
55
72
  end
@@ -59,7 +76,15 @@ module Elelem
59
76
  def process(message)
60
77
  if message["tool_calls"]&.any?
61
78
  message["tool_calls"].each do |tool_call|
62
- agent.conversation.add(role: :tool, content: agent.execute(tool_call))
79
+ tool_name = tool_call.dig("function", "name") || "unknown"
80
+ agent.show_progress(tool_name, "[>]", colour: :magenta)
81
+ agent.say("\n\n", newline: false)
82
+
83
+ output = agent.execute(tool_call)
84
+ agent.conversation.add(role: :tool, content: output)
85
+
86
+ agent.say("\n", newline: false)
87
+ agent.complete_progress("#{tool_name} completed")
63
88
  end
64
89
  end
65
90
 
@@ -67,14 +92,37 @@ module Elelem
67
92
  end
68
93
  end
69
94
 
95
+ class Error < State
96
+ def initialize(agent, error_message)
97
+ super(agent)
98
+ @error_message = error_message
99
+ end
100
+
101
+ def process(_message)
102
+ agent.say("\nTool execution failed: #{@error_message}", colour: :red)
103
+ agent.say("Returning to idle state.\n\n", colour: :yellow)
104
+ Waiting.new(agent)
105
+ end
106
+ end
107
+
70
108
  class Talking < State
109
+ def initialize(agent)
110
+ super(agent)
111
+ @progress_shown = false
112
+ end
113
+
71
114
  def process(message)
72
115
  if message["content"] && !message["content"]&.empty?
116
+ unless @progress_shown
117
+ agent.show_progress("Responding...", "[~]", colour: :white)
118
+ agent.say("\n", newline: false)
119
+ @progress_shown = true
120
+ end
73
121
  agent.conversation.add(role: message["role"], content: message["content"])
74
122
  agent.say(message["content"], colour: :default, newline: false)
75
123
  self
76
124
  else
77
- agent.say("", newline: true)
125
+ agent.say("\n\n", newline: false)
78
126
  Waiting.new(agent).process(message)
79
127
  end
80
128
  end
@@ -82,6 +130,9 @@ module Elelem
82
130
 
83
131
  def run(agent)
84
132
  agent.logger.debug("Working...")
133
+ agent.show_progress("Processing...", "[.]", colour: :cyan)
134
+ agent.say("\n\n", newline: false)
135
+
85
136
  state = Waiting.new(agent)
86
137
  done = false
87
138
 
@@ -0,0 +1,7 @@
1
+ **Del — AI** — Direct/no fluff; prose unless bullets; concise/simple, thorough/complex; critical>agree; honest always; AI≠human. TDD→SOLID→SRP/encapsulation/composition>inheritance; patterns only if needed; self-doc names; simple>complex; no cleverness. Unix: small tools, 1 job, pipe; prefer built-ins; cite man(1); note POSIX≠GNU; stdin/stdout streams.
2
+
3
+ Time: `<%= Time.now.strftime("%Y-%m-%d %H:%M:%S") %>`
4
+ Project Directory: `<%= Dir.pwd %>`
5
+ System Info: `<%= `uname -a`.strip %>`
6
+
7
+ Del is now being connected with a person.
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Elelem
4
+ class Tool
5
+ attr_reader :name, :description, :parameters
6
+
7
+ def initialize(name, description, parameters)
8
+ @name = name
9
+ @description = description
10
+ @parameters = parameters
11
+ end
12
+
13
+ def banner
14
+ [name, parameters].join(": ")
15
+ end
16
+
17
+ def valid?(args)
18
+ JSON::Validator.validate(parameters, args, insert_defaults: true)
19
+ end
20
+
21
+ def to_h
22
+ {
23
+ type: "function",
24
+ function: {
25
+ name: name,
26
+ description: description,
27
+ parameters: parameters
28
+ }
29
+ }
30
+ end
31
+ end
32
+
33
+ class BashTool < Tool
34
+ attr_reader :tui
35
+
36
+ def initialize(configuration)
37
+ @tui = configuration.tui
38
+ super("bash", "Execute a shell command.", {
39
+ type: "object",
40
+ properties: {
41
+ command: { type: "string" }
42
+ },
43
+ required: ["command"]
44
+ })
45
+ end
46
+
47
+ def call(args)
48
+ command = args["command"]
49
+ output_buffer = []
50
+
51
+ Open3.popen3("/bin/sh", "-c", command) do |stdin, stdout, stderr, wait_thread|
52
+ stdin.close
53
+ streams = [stdout, stderr]
54
+
55
+ until streams.empty?
56
+ ready = IO.select(streams, nil, nil, 0.1)
57
+
58
+ if ready
59
+ ready[0].each do |io|
60
+ data = io.read_nonblock(4096)
61
+ output_buffer << data
62
+
63
+ if io == stderr
64
+ tui.say(data, colour: :red, newline: false)
65
+ else
66
+ tui.say(data, newline: false)
67
+ end
68
+ rescue IO::WaitReadable
69
+ next
70
+ rescue EOFError
71
+ streams.delete(io)
72
+ end
73
+ elsif !wait_thread.alive?
74
+ break
75
+ end
76
+ end
77
+
78
+ wait_thread.value
79
+ end
80
+
81
+ output_buffer.join
82
+ end
83
+ end
84
+
85
+ class MCPTool < Tool
86
+ attr_reader :client, :tui
87
+
88
+ def initialize(client, tui, tool)
89
+ @client = client
90
+ @tui = tui
91
+ super(tool["name"], tool["description"], tool["inputSchema"] || {})
92
+ end
93
+
94
+ def call(args)
95
+ unless client.connected?
96
+ tui.say("MCP connection lost", colour: :red)
97
+ return ""
98
+ end
99
+
100
+ result = client.call(name, args)
101
+ tui.say(result)
102
+
103
+ if result.nil? || result.empty?
104
+ tui.say("Tool call failed: no response from MCP server", colour: :red)
105
+ return result
106
+ end
107
+
108
+ if result["error"]
109
+ tui.say(result["error"], colour: :red)
110
+ return result
111
+ end
112
+
113
+ result.dig("content", 0, "text") || result.to_s
114
+ end
115
+ end
116
+ end
data/lib/elelem/tools.rb CHANGED
@@ -2,61 +2,32 @@
2
2
 
3
3
  module Elelem
4
4
  class Tools
5
- DEFAULT_TOOLS = [
6
- {
7
- type: "function",
8
- function: {
9
- name: "execute_command",
10
- description: "Execute a shell command.",
11
- parameters: {
12
- type: "object",
13
- properties: {
14
- command: { type: "string" },
15
- },
16
- required: ["command"]
17
- }
18
- },
19
- handler: lambda { |args|
20
- stdout, stderr, _status = Open3.capture3("/bin/sh", "-c", args["command"])
21
- stdout + stderr
22
- }
23
- },
24
- ]
25
-
26
- def initialize(tools = DEFAULT_TOOLS)
5
+ def initialize(configuration, tools)
6
+ @configuration = configuration
27
7
  @tools = tools
28
8
  end
29
9
 
30
10
  def banner
31
- @tools.map do |h|
32
- [
33
- h.dig(:function, :name),
34
- h.dig(:function, :description)
35
- ].join(": ")
36
- end.sort.join("\n ")
11
+ tools.map(&:banner).sort.join("\n ")
37
12
  end
38
13
 
39
14
  def execute(tool_call)
40
15
  name = tool_call.dig("function", "name")
41
16
  args = tool_call.dig("function", "arguments")
42
17
 
43
- tool = @tools.find do |tool|
44
- tool.dig(:function, :name) == name
45
- end
46
- tool&.fetch(:handler)&.call(args)
18
+ tool = tools.find { |tool| tool.name == name }
19
+ return "Invalid function name: #{name}" if tool.nil?
20
+ return "Invalid function arguments: #{args}" unless tool.valid?(args)
21
+
22
+ tool.call(args)
47
23
  end
48
24
 
49
25
  def to_h
50
- @tools.map do |tool|
51
- {
52
- type: tool[:type],
53
- function: {
54
- name: tool.dig(:function, :name),
55
- description: tool.dig(:function, :description),
56
- parameters: tool.dig(:function, :parameters)
57
- }
58
- }
59
- end
26
+ tools.map(&:to_h)
60
27
  end
28
+
29
+ private
30
+
31
+ attr_reader :configuration, :tools
61
32
  end
62
33
  end
data/lib/elelem/tui.rb CHANGED
@@ -10,8 +10,7 @@ module Elelem
10
10
  end
11
11
 
12
12
  def prompt(message)
13
- say(message)
14
- stdin.gets&.chomp
13
+ Reline.readline(message, true)
15
14
  end
16
15
 
17
16
  def say(message, colour: :default, newline: false)
@@ -24,10 +23,50 @@ module Elelem
24
23
  stdout.flush
25
24
  end
26
25
 
26
+ def show_progress(message, prefix = "[.]", colour: :gray)
27
+ timestamp = current_time_string
28
+ formatted_message = colourize("#{prefix} #{timestamp} #{message}", colour: colour)
29
+ stdout.print(formatted_message)
30
+ stdout.flush
31
+ end
32
+
33
+ def clear_line
34
+ stdout.print("\r#{" " * 80}\r")
35
+ stdout.flush
36
+ end
37
+
38
+ def complete_progress(message = "Completed")
39
+ clear_line
40
+ timestamp = current_time_string
41
+ formatted_message = colourize("[✓] #{timestamp} #{message}", colour: :green)
42
+ stdout.puts(formatted_message)
43
+ stdout.flush
44
+ end
45
+
27
46
  private
28
47
 
48
+ def current_time_string
49
+ Time.now.strftime("%H:%M:%S")
50
+ end
51
+
29
52
  def colourize(text, colour: :default)
30
53
  case colour
54
+ when :black
55
+ "\e[30m#{text}\e[0m"
56
+ when :red
57
+ "\e[31m#{text}\e[0m"
58
+ when :green
59
+ "\e[32m#{text}\e[0m"
60
+ when :yellow
61
+ "\e[33m#{text}\e[0m"
62
+ when :blue
63
+ "\e[34m#{text}\e[0m"
64
+ when :magenta
65
+ "\e[35m#{text}\e[0m"
66
+ when :cyan
67
+ "\e[36m#{text}\e[0m"
68
+ when :white
69
+ "\e[37m#{text}\e[0m"
31
70
  when :gray
32
71
  "\e[90m#{text}\e[0m"
33
72
  else
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Elelem
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.2"
5
5
  end
data/lib/elelem.rb CHANGED
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "erb"
3
4
  require "json"
5
+ require "json-schema"
4
6
  require "logger"
5
7
  require "net/http"
6
8
  require "open3"
9
+ require "reline"
7
10
  require "thor"
8
11
  require "uri"
9
12
 
@@ -12,7 +15,9 @@ require_relative "elelem/api"
12
15
  require_relative "elelem/application"
13
16
  require_relative "elelem/configuration"
14
17
  require_relative "elelem/conversation"
18
+ require_relative "elelem/mcp_client"
15
19
  require_relative "elelem/state"
20
+ require_relative "elelem/tool"
16
21
  require_relative "elelem/tools"
17
22
  require_relative "elelem/tui"
18
23
  require_relative "elelem/version"
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.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - mo khan
@@ -9,6 +9,20 @@ bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: erb
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: json
14
28
  requirement: !ruby/object:Gem::Requirement
@@ -23,6 +37,20 @@ dependencies:
23
37
  - - ">="
24
38
  - !ruby/object:Gem::Version
25
39
  version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: json-schema
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
26
54
  - !ruby/object:Gem::Dependency
27
55
  name: logger
28
56
  requirement: !ruby/object:Gem::Requirement
@@ -65,6 +93,20 @@ dependencies:
65
93
  - - ">="
66
94
  - !ruby/object:Gem::Version
67
95
  version: '0'
96
+ - !ruby/object:Gem::Dependency
97
+ name: reline
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
68
110
  - !ruby/object:Gem::Dependency
69
111
  name: thor
70
112
  requirement: !ruby/object:Gem::Requirement
@@ -101,8 +143,6 @@ executables:
101
143
  extensions: []
102
144
  extra_rdoc_files: []
103
145
  files:
104
- - ".rspec"
105
- - ".rubocop.yml"
106
146
  - CHANGELOG.md
107
147
  - LICENSE.txt
108
148
  - README.md
@@ -114,11 +154,13 @@ files:
114
154
  - lib/elelem/application.rb
115
155
  - lib/elelem/configuration.rb
116
156
  - lib/elelem/conversation.rb
157
+ - lib/elelem/mcp_client.rb
117
158
  - lib/elelem/state.rb
159
+ - lib/elelem/system_prompt.erb
160
+ - lib/elelem/tool.rb
118
161
  - lib/elelem/tools.rb
119
162
  - lib/elelem/tui.rb
120
163
  - lib/elelem/version.rb
121
- - mise.toml
122
164
  - sig/elelem.rbs
123
165
  homepage: https://www.mokhan.ca
124
166
  licenses:
@@ -135,7 +177,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
135
177
  requirements:
136
178
  - - ">="
137
179
  - !ruby/object:Gem::Version
138
- version: 3.1.0
180
+ version: 3.4.0
139
181
  required_rubygems_version: !ruby/object:Gem::Requirement
140
182
  requirements:
141
183
  - - ">="
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
data/.rubocop.yml DELETED
@@ -1,8 +0,0 @@
1
- AllCops:
2
- TargetRubyVersion: 3.1
3
-
4
- Style/StringLiterals:
5
- EnforcedStyle: double_quotes
6
-
7
- Style/StringLiteralsInInterpolation:
8
- EnforcedStyle: double_quotes
data/mise.toml DELETED
@@ -1,2 +0,0 @@
1
- [tools]
2
- ruby = "latest"