genie_cli 0.1.1 → 0.2.1

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: 4582658de3ff1e9145b81a2975b973f7aebbc1762f8d7a21bb6237c211225558
4
- data.tar.gz: 79b2870a69918c5c7eba6a72ca1a8d5e5284392304c39833cf61f74e19686eae
3
+ metadata.gz: f9431dda686ce68a45532465d2d0f75baabb62e7dee4dc5278d51cdbd0cf00b7
4
+ data.tar.gz: 86a77baeb167e1bdf058d020368a9069599f44c69fee4d290830bc02fb83cf96
5
5
  SHA512:
6
- metadata.gz: eaf263a55c387b7e021afbc3dd161d47e38307ccf09a6ec5bfd952237f739bb74720fe23efcfd5873030d7bcc1af11540198c760f99438254562aabb39002f3f
7
- data.tar.gz: 5e1cac00335cc946e3bbcbe04f2b6017861033186810523ac33e40ab56f763d28ba73d6b1cedbdaca755a3737aeb0048140d789ab8e43fac34ec243cab8093dc
6
+ metadata.gz: 8cc795dbe839e79b4e2d859051f2bb7c7fdcbaad70c50d17ded96d581aca5fe15cf2482c6d13fd39657f35872e0f997deb7a1bda4f54ad9b89c619600508cffa
7
+ data.tar.gz: d9a87bb13bfd5e679baa1934904c348e6e957b7737211c06992d85439e1740cc52e2be77cd5820fd47279d5c04ae5c4582266e6df29afa906e812b8206f3879d
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # Genie CLI
2
2
 
3
- Genie CLI is a command-line tool that brings Test Driven Development (TDD)
4
- principles to life by integrating with a large language model (LLM) through
5
- the `ruby_llm` library. It provides an interactive session where you can ask
6
- the AI assistant to write tests, implement code, and manage your codebase—all
3
+ Genie CLI is a command-line tool that brings Test Driven Development (TDD)
4
+ principles to life by integrating with a large language model (LLM) through
5
+ the `ruby_llm` library. It provides an interactive session where you can ask
6
+ the AI assistant to write tests, implement code, and manage your codebase—all
7
7
  while enforcing a strict TDD workflow.
8
8
 
9
9
  ## Features
@@ -23,33 +23,33 @@ while enforcing a strict TDD workflow.
23
23
 
24
24
  ## Installation
25
25
 
26
- 1. Clone this repository:
27
- ```bash
28
- git clone https://github.com/jeffmcfadden/genie_cli.git
29
- cd genie_cli
30
- ```
26
+ 1. Install the gem:
27
+ ```bash
28
+ gem install genie_cli
29
+ ```
31
30
 
32
- 2. Install dependencies using Bundler:
33
- ```bash
34
- bundle install
35
- ```
31
+ 2. Set your OpenAI API key (or other LLM provider keys) in your environment:
32
+ ```bash
33
+ export OPENAI_API_KEY="your_api_key_here"
34
+ ```
35
+
36
+ `dotenv` is also supported:
36
37
 
37
- 3. Set your OpenAI API key (or other LLM provider keys) in your environment:
38
- ```bash
39
- export OPENAI_API_KEY="your_api_key_here"
40
- ```
41
-
42
- 4. Make the CLI executable:
43
- ```bash
44
- chmod +x bin/genie
45
- ```
38
+ ```
39
+ # .env
40
+ OPENAI_API_KEY="your_api_key_here"
41
+ ```
46
42
 
47
43
  ## Usage
48
44
 
49
45
  Start a Genie session by running the `genie` command from the root of your project:
50
46
 
51
47
  ```bash
52
- ./bin/genie ["initial prompt or command"]
48
+ genie "initial prompt or command"
49
+ ```
50
+ If you are using Bundler, you can run:
51
+ ```bash
52
+ bundle exec genie "initial prompt or command"
53
53
  ```
54
54
 
55
55
  - If you provide an initial prompt, the assistant will immediately respond. Otherwise, you'll enter an interactive prompt where you can type your questions or commands.
@@ -58,7 +58,7 @@ Start a Genie session by running the `genie` command from the root of your proje
58
58
  Example session:
59
59
 
60
60
  ```bash
61
- $ ./bin/genie
61
+ $ genie
62
62
  Starting a new session with:
63
63
  base_path: /Users/you/projects/genie_cli
64
64
 
@@ -81,14 +81,14 @@ Total Conversation Tokens: 1234
81
81
 
82
82
  ## Logging
83
83
 
84
- The output of `genie` to the terminal includes "essential" output, but not
85
- _all_ output. To aid in debugging, the full RubyLLM debug log is saved to
86
- `ruby_llm.log`. This can be useful for auditing what's happened during a
84
+ The output of `genie` to the terminal includes "essential" output, but not
85
+ _all_ output. To aid in debugging, the full RubyLLM debug log is saved to
86
+ `ruby_llm.log`. This can be useful for auditing what's happened during a
87
87
  session in great detail.
88
88
 
89
89
  ## Configuration
90
90
 
91
- Configuration is available via a `genie.yml` file in the project root.
91
+ Configuration is available via a `genie.yml`.
92
92
 
93
93
  ## Testing
94
94
 
@@ -105,3 +105,14 @@ Contributions are welcome! Please fork the repository and open pull requests for
105
105
  ## License
106
106
 
107
107
  This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
108
+
109
+ ## Why "Genie"?
110
+
111
+ [Kent Beck](https://tidyfirst.substack.com) has been using this term to
112
+ describe LLMs, especially coding agents, because it grants your wishes, but
113
+ rarely in the way you actually wanted it to. It's a great idea, so I'm
114
+ shamelessly stealing it.
115
+
116
+ ## The Lamp
117
+
118
+ From https://www.asciiart.website/index.php?art=movies%2Faladdin
data/lib/genie/session.rb CHANGED
@@ -15,7 +15,11 @@ module Genie
15
15
  def initialize(config:)
16
16
  @config = config
17
17
 
18
- Genie.output "Starting a new session with:\n base_path: #{base_path}\n model: #{model}\n", color: :green
18
+ Genie.output genie_lamp, color: :yellow, include_genie_icon: false
19
+
20
+ Genie.output "Your wish is my command!", color: :magenta
21
+ Genie.output " base_path: #{base_path}", color: :cyan, include_genie_icon: false
22
+ Genie.output " model: #{model}", color: :cyan, include_genie_icon: false
19
23
 
20
24
  # Initialize the LLM chat with the specified model
21
25
  @chat = RubyLLM.chat(model: model)
@@ -79,5 +83,31 @@ module Genie
79
83
  exit
80
84
  end
81
85
 
86
+ private
87
+
88
+ def genie_lamp
89
+ <<-LAMP
90
+ ..
91
+ dP/$.
92
+ $4$$%
93
+ .ee$$ee.
94
+ .eF3??????$C$r. .d$$$$$$$$$$$e.
95
+ .zeez$$$$$be.. JP3F$5'$5K$?K?Je$. d$$$FCLze.CC?$$$e
96
+ """??$$$$$$$$ee.. .e$$$e$CC$???$$CC3e$$$$. $$$/$$$$$$$$$.$$$$
97
+ `"?$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$b $$"$$$$P?CCe$$$$$F
98
+ "?$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$b$$J?bd$$$$$$$$$F"
99
+ "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$d$$F"
100
+ "?$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$"...
101
+ "?$$$$$$$$$$$$$$$$$$$$$$$$$F "$$"$$$$b
102
+ "?$$$$$$$$$$$$$$$$$$F" ?$$$$$F
103
+ ""????????C"
104
+ e$$$$$$$$$$$$.
105
+ .$b CC$????$$F3eF
106
+ 4$bC/%$bdd$b@$Pd??Jbbr
107
+ ""?$$$$eeee$$$$F?"
108
+
109
+ LAMP
110
+ end
111
+
82
112
  end
83
113
  end
@@ -1,10 +1,8 @@
1
- require 'yaml'
2
-
3
1
  module Genie
4
2
  # Handles loading of session configuration such as run_tests_cmd
5
3
  class SessionConfig
6
4
  # Read-only attributes
7
- attr_reader :base_path, :run_tests_cmd, :model, :first_question, :instructions
5
+ attr_reader :base_path, :run_tests_cmd, :model, :first_question, :instructions, :ignore_paths
8
6
 
9
7
  DEFAULT_INSTRUCTIONS = <<~INSTRUCTIONS
10
8
  # Genie Instructions
@@ -24,7 +22,8 @@ module Genie
24
22
  run_tests_cmd: 'rake test',
25
23
  model: 'gpt-4o-mini',
26
24
  first_question: nil,
27
- instructions: DEFAULT_INSTRUCTIONS
25
+ instructions: DEFAULT_INSTRUCTIONS,
26
+ ignore_paths: ['tmp', '/tmp'],
28
27
  }
29
28
 
30
29
  def self.from_argv(argv)
@@ -54,6 +53,10 @@ module Genie
54
53
  cli_options[:instructions] = text
55
54
  end
56
55
 
56
+ opts.on("--ignore-paths LIST", "Comma-separated paths to ignore") do |list|
57
+ cli_options[:ignore_paths] = list.split(',').map(&:strip)
58
+ end
59
+
57
60
  opts.on("-v", "--[no-]verbose", "Enable verbose mode") do |v|
58
61
  cli_options['verbose'] = v
59
62
  end
@@ -73,10 +76,10 @@ module Genie
73
76
  # We always preface the instructions with context
74
77
  final_config[:instructions] = <<~PREFACE
75
78
  # Context
76
- Current Date and Time: #{Time.now.iso8601}
77
- We are working in a codebase located at '#{final_config[:base_path]}'.
79
+ Current Date and Time: \\#{Time.now.iso8601}
80
+ We are working in a codebase located at '\\#{final_config[:base_path]}'.
78
81
 
79
- #{final_config[:instructions]}
82
+ \\#{final_config[:instructions]}
80
83
  PREFACE
81
84
 
82
85
  new(
@@ -84,7 +87,8 @@ module Genie
84
87
  run_tests_cmd: final_config[:run_tests_cmd],
85
88
  model: final_config[:model],
86
89
  first_question: final_config[:first_question],
87
- instructions: final_config[:instructions]
90
+ instructions: final_config[:instructions],
91
+ ignore_paths: final_config[:ignore_paths]
88
92
  )
89
93
  end
90
94
 
@@ -94,17 +98,18 @@ module Genie
94
98
  run_tests_cmd: DEFAULTS[:run_tests_cmd],
95
99
  model: DEFAULTS[:model],
96
100
  first_question: DEFAULTS[:first_question],
97
- instructions: DEFAULTS[:instructions]
101
+ instructions: DEFAULTS[:instructions],
102
+ ignore_paths: DEFAULTS[:ignore_paths],
98
103
  )
99
104
  end
100
105
 
101
- def initialize(base_path:, run_tests_cmd:, model:, first_question:, instructions:) # Requires both base_path and run_tests_cmd
106
+ def initialize(base_path:, run_tests_cmd:, model:, first_question:, instructions:, ignore_paths:)
102
107
  @base_path = File.expand_path(base_path)
103
108
  @run_tests_cmd = run_tests_cmd
104
109
  @model = model
105
110
  @first_question = first_question
106
111
  @instructions = instructions
112
+ @ignore_paths = ignore_paths
107
113
  end
108
-
109
114
  end
110
115
  end
data/lib/genie/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Genie
2
- VERSION = '0.1.1'
2
+ VERSION = '0.2.1'
3
3
  end
data/lib/genie.rb CHANGED
@@ -32,9 +32,11 @@ RubyLLM.configure do |config|
32
32
  end
33
33
 
34
34
  module Genie
35
- def self.output(s, color: :white)
35
+ def self.output(s, color: :white, include_genie_icon: true)
36
36
  return if quiet?
37
37
 
38
+ s = "🧞‍♂️ #{s}" if include_genie_icon
39
+
38
40
  # This method is used to output messages in a consistent format.
39
41
  # You can customize the color or format as needed.
40
42
  puts "\e[32m#{s}\e[0m" if color == :green
@@ -8,6 +8,13 @@ module Genie
8
8
  param :recursive, desc: "Whether to list files recursively (default: false)"
9
9
  param :filter, desc: "Filter string to include only paths that include this substring (Optional)"
10
10
 
11
+ def initialize(base_path:, ignore_paths: [])
12
+ @base_path = base_path
13
+ @base_path.freeze
14
+
15
+ @ignore_paths = ignore_paths
16
+ end
17
+
11
18
  def execute(directory:, recursive: false, filter: nil)
12
19
  directory = File.expand_path(directory, @base_path)
13
20
 
@@ -22,6 +29,9 @@ module Genie
22
29
  listing = listing.select { |entry| entry[:name].include?(filter) }
23
30
  end
24
31
 
32
+ # Apply ignore paths
33
+ listing.reject! { |entry| @ignore_paths.any? { |ignore_path| entry[:name].gsub(@base_path, '').start_with?(ignore_path) } }
34
+
25
35
  Genie.output listing.map { |e| e[:name] }.join("\n") + "\n", color: :green
26
36
 
27
37
  listing
@@ -1,14 +1,11 @@
1
- require_relative "test_helper"
2
- require 'tmpdir'
3
-
4
- class SessionConfigTest < TLDR
5
1
  def test_basic_session_config
6
2
  config = Genie::SessionConfig.new(
7
3
  base_path: "/my/cool/path",
8
4
  run_tests_cmd: "bundle exec testit",
9
5
  model: "gpt-4",
10
6
  first_question: "What is the meaning of life?",
11
- instructions: "Default instructions"
7
+ instructions: "Default instructions",
8
+ ignore_paths: ['tmp']
12
9
  )
13
10
 
14
11
  assert_equal "/my/cool/path", config.base_path
@@ -16,40 +13,5 @@ class SessionConfigTest < TLDR
16
13
  assert_equal "gpt-4", config.model
17
14
  assert_equal "What is the meaning of life?", config.first_question
18
15
  assert config.instructions.include?("Default instructions")
16
+ assert_equal ['tmp'], config.ignore_paths
19
17
  end
20
-
21
- def test_session_from_argv
22
- argv = ["-c", "asdf.yml", "--base-path", "/tmp", "--run-tests", "rake test", "--model", "gpt-4o", "--instructions", "Command line instructions", "What is the meaning of life?"]
23
- config = Genie::SessionConfig.from_argv(argv)
24
-
25
- assert_equal "/tmp", config.base_path
26
- assert_equal "rake test", config.run_tests_cmd
27
- assert_equal "gpt-4o", config.model
28
- assert_equal "What is the meaning of life?", config.first_question
29
- assert config.instructions.include?("Command line instructions")
30
- end
31
-
32
- def test_session_from_config_file
33
- argv = ["-c", "./test/data/sample_config.yml"]
34
- config = Genie::SessionConfig.from_argv(argv)
35
- expected_base_path = "/tmp/myapp/from_config"
36
-
37
- assert_equal File.realpath(expected_base_path), File.realpath(config.base_path)
38
- assert_equal "bundle exec tests_from_config_ex", config.run_tests_cmd
39
- assert_equal "test_model_from_config", config.model
40
- assert_equal nil, config.first_question
41
- assert config.instructions.include?("Instructions from config file")
42
- end
43
-
44
- def test_default_instructions
45
- config = Genie::SessionConfig.default
46
- default_instructions = config.instructions
47
- assert_includes default_instructions, "Genie coding assistant"
48
- assert_includes default_instructions, "Test Driven Development"
49
- assert_includes default_instructions, "tools available"
50
- assert_includes default_instructions, "write tests first"
51
- assert_includes default_instructions, "do not have access to any files outside"
52
- assert_includes default_instructions, "do not have access to the internet"
53
- end
54
-
55
- end
@@ -28,4 +28,17 @@ class TestListFiles < TLDR
28
28
 
29
29
  assert_equal expected, actual
30
30
  end
31
+
32
+ def test_list_files_with_ignore_paths
33
+ base_path = File.expand_path("../../", __dir__)
34
+ t = Genie::ListFiles.new(base_path: base_path, ignore_paths: ["a_dir"])
35
+
36
+ actual = t.execute(directory: "./test/data/sample_files")
37
+
38
+ expected = [{ name: "read_file_test.txt", type: "file" },
39
+ { name: "one.txt", type: "file" }]
40
+
41
+ assert_equal expected, actual
42
+ end
43
+
31
44
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: genie_cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeff McFadden
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-06-16 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: ruby_llm
@@ -173,7 +173,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
173
173
  - !ruby/object:Gem::Version
174
174
  version: '0'
175
175
  requirements: []
176
- rubygems_version: 3.6.2
176
+ rubygems_version: 3.6.9
177
177
  specification_version: 4
178
178
  summary: CLI Coding Agent written in Ruby.
179
179
  test_files: []