clag 0.0.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.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/Gemfile +9 -0
  4. data/README.md +30 -0
  5. data/bin/testunit +23 -0
  6. data/clag.gemspec +27 -0
  7. data/dev.yml +3 -0
  8. data/exe/clag +19 -0
  9. data/lib/clag/commands/generate.rb +36 -0
  10. data/lib/clag/commands/help.rb +23 -0
  11. data/lib/clag/commands.rb +15 -0
  12. data/lib/clag/entry_point.rb +11 -0
  13. data/lib/clag/version.rb +3 -0
  14. data/lib/clag.rb +26 -0
  15. data/test/example_test.rb +17 -0
  16. data/test/test_helper.rb +22 -0
  17. data/vendor/gems/sublayer/.gitignore +16 -0
  18. data/vendor/gems/sublayer/Gemfile +6 -0
  19. data/vendor/gems/sublayer/LICENSE +21 -0
  20. data/vendor/gems/sublayer/README.md +52 -0
  21. data/vendor/gems/sublayer/Rakefile +8 -0
  22. data/vendor/gems/sublayer/lib/sublayer/agents/generate_code_given_description_agent.rb +35 -0
  23. data/vendor/gems/sublayer/lib/sublayer/agents/generate_command_agent.rb +37 -0
  24. data/vendor/gems/sublayer/lib/sublayer/agents/json_fixing_agent.rb +32 -0
  25. data/vendor/gems/sublayer/lib/sublayer/agents/modify_file_contents_agent.rb +38 -0
  26. data/vendor/gems/sublayer/lib/sublayer/agents/save_file_contents_agent.rb +18 -0
  27. data/vendor/gems/sublayer/lib/sublayer/capabilities/human_assistance.rb +23 -0
  28. data/vendor/gems/sublayer/lib/sublayer/capabilities/llm_assistance.rb +45 -0
  29. data/vendor/gems/sublayer/lib/sublayer/components/output_function.rb +23 -0
  30. data/vendor/gems/sublayer/lib/sublayer/components/output_function_formats/list_of_objects.rb +30 -0
  31. data/vendor/gems/sublayer/lib/sublayer/components/output_function_formats/single_string.rb +26 -0
  32. data/vendor/gems/sublayer/lib/sublayer/enhancements/json.rb +9 -0
  33. data/vendor/gems/sublayer/lib/sublayer/version.rb +5 -0
  34. data/vendor/gems/sublayer/lib/sublayer.rb +47 -0
  35. data/vendor/gems/sublayer/spec/components/output_function_spec.rb +73 -0
  36. data/vendor/gems/sublayer/spec/spec_helper.rb +15 -0
  37. data/vendor/gems/sublayer/sublayer.gemspec +38 -0
  38. metadata +165 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 29a6ab190bdd6accfd67782655a99b3f255c0d5b217a18fa41ddd2bf1f837dea
4
+ data.tar.gz: 6ad8f31dcebbcc91a8e17d1cb4a90de81ce93f91799de6a7e7e3abd721f15017
5
+ SHA512:
6
+ metadata.gz: b7751ed524c187b0925610419caaedce08605caaea43df5066def060dfd8fcb7ee112afa0c8185ef684103d9924f6b06d24475fe81d2ee552ebe9b698fad9336
7
+ data.tar.gz: d6ba64383b5500c849179203b4aac5f4d88ecd048319b0a9ce2a87946cb8a8c7e558316b5763d660a329eb1a201fd6052278429fbb5a657f7cce0782df5295a2
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ *.gem
2
+ .rvmrc
3
+ .ruby-version
4
+ .ruby-gemset
5
+ .bundle
6
+ Gemfile.lock
7
+ pkg/*
8
+ coverage.data
9
+ coverage/*
10
+ .yardoc
11
+ doc/*
12
+ tmp
13
+ vendor/bundle
14
+ *.swp
15
+ *.swo
16
+ *.DS_Store
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'mocha', '~> 1.5.0', require: false
7
+ gem 'minitest', '>= 5.0.0', require: false
8
+ gem 'minitest-reporters', require: false
9
+ end
data/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # clag - Command Line AI Gem
2
+
3
+ Tired of trying to remember the exact flags to use or digging through
4
+ documentation or googling to find how to do the thing you're trying to do?
5
+
6
+ Suffer no more! Simply describe what you're trying to do and generate the
7
+ command with the help of an LLM!
8
+
9
+ ## Installation
10
+
11
+ * Get an API key from OpenAI for gpt4-turbo: https://platform.openai.com/
12
+
13
+ * Set your API key as OPENAI\_API\_KEY in your environment
14
+
15
+ * Install the gem
16
+ `gem install clag`
17
+
18
+ * Generate commands
19
+ `clag g "create a new ruby on rails project using postgres and tailwindcss"`
20
+
21
+ ## Usage
22
+
23
+ Currently support one command: "g".
24
+
25
+ `clag g "the command you'd like to generate"`
26
+
27
+ ## Contributing
28
+
29
+ Bug reports and pull requests are welcome on Github at
30
+ https://github.com/sublayerapp/clag
data/bin/testunit ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'bundler/setup'
5
+
6
+ root = File.expand_path('../..', __FILE__)
7
+ TEST_ROOT = root + '/test'
8
+
9
+ $LOAD_PATH.unshift(TEST_ROOT)
10
+
11
+ def test_files
12
+ Dir.glob(TEST_ROOT + "/**/*_test.rb")
13
+ end
14
+
15
+ if ARGV.empty?
16
+ test_files.each { |f| require(f) }
17
+ exit 0
18
+ end
19
+
20
+ # A list of files is presumed to be specified
21
+ ARGV.each do |a|
22
+ require a.sub(%r{^test/}, '')
23
+ end
data/clag.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ require_relative "lib/clag/version"
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'clag'
5
+ spec.version = Clag::VERSION
6
+ spec.authors = ['Scott Werner']
7
+ spec.email = ['scott@sublayer.com']
8
+ spec.homepage = 'https://github.com/sublayerapp/clag'
9
+ spec.summary = 'Generate command line commands in your terminal using an LLM'
10
+ spec.description = 'Clag is a command line tool that generates command line commands right in your terminal and puts it into your clipboard for you to paste into your terminal.'
11
+ spec.license = 'MIT'
12
+
13
+ spec.files = `git ls-files`.split("\n")
14
+ spec.bindir = 'exe'
15
+ spec.executables << 'clag'
16
+ spec.require_paths = ['lib']
17
+
18
+ spec.required_ruby_version = '>= 3'
19
+
20
+ spec.add_dependency 'cli-kit', '~> 5'
21
+ spec.add_dependency 'cli-ui', '~> 2.2.3'
22
+ spec.add_dependency 'ruby-openai', '~> 6'
23
+ spec.add_dependency 'clipboard', '~> 1.3'
24
+ spec.add_dependency 'activesupport'
25
+
26
+ spec.add_development_dependency 'rake', '~> 10.0'
27
+ end
data/dev.yml ADDED
@@ -0,0 +1,3 @@
1
+ up:
2
+ - ruby: 2.5.0
3
+ - bundler
data/exe/clag ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ Encoding.default_external = Encoding::UTF_8
4
+ Encoding.default_internal = Encoding::UTF_8
5
+
6
+ unshift_path = ->(path) {
7
+ p = File.expand_path("../../#{path}", __FILE__)
8
+ $LOAD_PATH.unshift(p) unless $LOAD_PATH.include?(p)
9
+ }
10
+ unshift_path.call('lib')
11
+ require 'bundler/setup'
12
+
13
+ unshift_path.call('vendor/gems/sublayer/lib')
14
+
15
+ require 'clag'
16
+
17
+ exit(Clag::ErrorHandler.call do
18
+ Clag::EntryPoint.call(ARGV.dup)
19
+ end)
@@ -0,0 +1,36 @@
1
+ require 'clag'
2
+ require "clipboard"
3
+
4
+ module Clag
5
+ module Commands
6
+ class Generate < Clag::Command
7
+ def call(args, _name)
8
+ input = args.join(" ")
9
+
10
+ if ENV['OPENAI_API_KEY'].nil?
11
+ puts CLI::UI.fmt("{{red:OPENAI_API_KEY is not set. Please set it before continuing.}}")
12
+ return
13
+ end
14
+
15
+ if input.nil?
16
+ puts "Please provide input to generate options."
17
+ return
18
+ end
19
+
20
+ results = Sublayer::Agents::GenerateCommandLineCommandAgent.new(description: input).execute
21
+
22
+ if results == 'unknown'
23
+ puts CLI::UI.fmt("{{yellow:Unable to generate command. Please try again or provide more information.}}")
24
+ return
25
+ end
26
+
27
+ Clipboard.copy(results)
28
+ puts "\e[1;32m#{results}\e[0m\nCopied to clipboard."
29
+ end
30
+
31
+ def self.help
32
+ "Generate a command-line command and store it in the clipboard. \nUsage: {{command:#{Clag::TOOL_NAME} g \"the command you want to generate\"}}"
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,23 @@
1
+ require 'clag'
2
+
3
+ module Clag
4
+ module Commands
5
+ class Help < Clag::Command
6
+ def call(args, _name)
7
+ puts CLI::UI.fmt("{{bold:Available commands}}")
8
+ puts ""
9
+
10
+ Clag::Commands::Registry.resolved_commands.each do |name, klass|
11
+ next if name == "help"
12
+ puts CLI::UI.fmt("{{command:#{Clag::TOOL_NAME} #{name}}}")
13
+
14
+ if help = klass.help
15
+ puts CLI::UI.fmt(help)
16
+ end
17
+
18
+ puts ""
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ require 'clag'
2
+
3
+ module Clag
4
+ module Commands
5
+ Registry = CLI::Kit::CommandRegistry.new(default: 'help')
6
+
7
+ def self.register(const, cmd, path)
8
+ autoload(const, path)
9
+ Registry.add(->() { const_get(const) }, cmd)
10
+ end
11
+
12
+ register :Generate, 'g', 'clag/commands/generate'
13
+ register :Help, 'help', 'clag/commands/help'
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ require 'clag'
2
+
3
+ module Clag
4
+ module EntryPoint
5
+ def self.call(args)
6
+ cmd, command_name, args = Clag::Resolver.call(args)
7
+
8
+ Clag::Executor.call(cmd, command_name, args)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module Clag
2
+ VERSION = '0.0.1'
3
+ end
data/lib/clag.rb ADDED
@@ -0,0 +1,26 @@
1
+ require 'cli/ui'
2
+ require 'cli/kit'
3
+ require "sublayer"
4
+
5
+ CLI::UI::StdoutRouter.enable
6
+
7
+ module Clag
8
+ TOOL_NAME = 'clag'
9
+ ROOT = File.expand_path('../..', __FILE__)
10
+ LOG_FILE = '/tmp/clag.log'
11
+
12
+ autoload(:EntryPoint, 'clag/entry_point')
13
+ autoload(:Commands, 'clag/commands')
14
+
15
+
16
+ Config = CLI::Kit::Config.new(tool_name: TOOL_NAME)
17
+ Command = CLI::Kit::BaseCommand
18
+
19
+ Executor = CLI::Kit::Executor.new(log_file: LOG_FILE)
20
+ Resolver = CLI::Kit::Resolver.new(
21
+ tool_name: TOOL_NAME,
22
+ command_registry: Clag::Commands::Registry
23
+ )
24
+
25
+ ErrorHandler = CLI::Kit::ErrorHandler.new(log_file: LOG_FILE)
26
+ end
@@ -0,0 +1,17 @@
1
+ require 'test_helper'
2
+
3
+ module Clag
4
+ class ExampleTest < Minitest::Test
5
+ include CLI::Kit::Support::TestHelper
6
+
7
+ def test_example
8
+ CLI::Kit::System.fake("ls -al", stdout: "a\nb", success: true)
9
+
10
+ out, = CLI::Kit::System.capture2('ls', '-al')
11
+ assert_equal %w(a b), out.split("\n")
12
+
13
+ errors = assert_all_commands_run(should_raise: false)
14
+ assert_nil errors, "expected command to run successfully"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,22 @@
1
+ begin
2
+ addpath = lambda do |p|
3
+ path = File.expand_path("../../#{p}", __FILE__)
4
+ $LOAD_PATH.unshift(path) unless $LOAD_PATH.include?(path)
5
+ end
6
+ addpath.call("lib")
7
+ end
8
+
9
+ require 'cli/kit'
10
+
11
+ require 'fileutils'
12
+ require 'tmpdir'
13
+ require 'tempfile'
14
+
15
+ require 'rubygems'
16
+ require 'bundler/setup'
17
+
18
+ CLI::UI::StdoutRouter.enable
19
+
20
+ require 'minitest/autorun'
21
+ require "minitest/unit"
22
+ require 'mocha/minitest'
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ /.sublayer/
11
+
12
+ .rspec_status
13
+ .idea/
14
+
15
+ *.log
16
+ *.gem
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in sublayer_ruby.gemspec
6
+ gemspec
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Sublayer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,52 @@
1
+ # Sublayer
2
+
3
+ An AI agent framework
4
+
5
+ ## Installation
6
+
7
+ Install the gem by running the following commands:
8
+
9
+ $ bundle
10
+ $ gem build sublayer.gemspec
11
+ $ gem install sublayer-0.0.1.gem
12
+
13
+ Your OpenAI API key needs to be accessible in your environment at OPENAI\_API\_KEY
14
+
15
+ Your default editor for your environment is also used.
16
+
17
+ ## Usage
18
+
19
+ ### * Interactive CLI
20
+
21
+ You can use the gem by running the command `sublayer` in any project directory.
22
+ This will open an interactive shell where all file operations are run relative
23
+ to that root project directory.
24
+
25
+ In the interactive shell, you're able to create new agents specific to your
26
+ project, generate code, modify existing files, and save the resulting code from
27
+ those agent commands.
28
+
29
+ ### * Simple TDD Pair (experimental / dangerous)
30
+
31
+ **Warning:** This will generate code from GPT4 and run it as called from your
32
+ tests. Use at your own risk.
33
+
34
+ Usage: `sublayer_simple_tdd_pair "TEST_RUN_COMMAND" "FILE_UNDER_TEST"`
35
+
36
+ This command will run the TEST_RUN_COMMAND, send the test output, the tests, and
37
+ the FILE\_UNDER\_TEST to GPT4 and will attempt to edit FILE\_UNDER\_TEST and
38
+ rerun the tests until they pass.
39
+
40
+ To do use it like in [this
41
+ loom](https://www.loom.com/share/6970b51856b04a91b792f14e848e9b6d) you'll need
42
+ to install entr: `brew install entr`
43
+
44
+ The command I'm running there is: `ls ./day3/*.rb | entr sublayer_simple_tdd_pair "rspec ./day3/santa_spec.rb" "./day3/santa.rb"`
45
+
46
+ ## Development
47
+
48
+ TBD
49
+
50
+ ## Contributing
51
+
52
+ TBD
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,35 @@
1
+ module Sublayer
2
+ module Agents
3
+ class GenerateCodeGivenDescriptionAgent
4
+ include Sublayer::Capabilities::LLMAssistance
5
+ include Sublayer::Capabilities::HumanAssistance
6
+
7
+ attr_reader :description, :technologies, :results
8
+
9
+ llm_result_format type: :single_string,
10
+ name: "generated_code",
11
+ description: "The generated code in the requested language"
12
+
13
+ def initialize(description:, technologies:)
14
+ @description = description
15
+ @technologies = technologies
16
+ end
17
+
18
+ def execute
19
+ @results = human_assistance_with(llm_generate)
20
+ end
21
+
22
+ def prompt
23
+ <<-PROMPT
24
+ You are an expert programmer in #{technologies.join(", ")}.
25
+
26
+ You are tasked with writing code using the following technologies: #{technologies.join(", ")}.
27
+
28
+ The description of the task is #{description}
29
+
30
+ Take a deep breath and think step by step before you start coding.
31
+ PROMPT
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,37 @@
1
+ module Sublayer
2
+ module Agents
3
+ class GenerateCommandLineCommandAgent
4
+ include Sublayer::Capabilities::LLMAssistance
5
+ include Sublayer::Capabilities::HumanAssistance
6
+
7
+ attr_reader :description, :results
8
+
9
+ llm_result_format type: :single_string,
10
+ name: "command",
11
+ description: "The command line command for the user to run or 'unknown'"
12
+
13
+ def initialize(description:)
14
+ @description = description
15
+ end
16
+
17
+ def execute
18
+ @results = llm_generate
19
+ end
20
+
21
+ def prompt
22
+ <<-PROMPT
23
+ You are an expert in command line operations.
24
+
25
+ You are tasked with finding or crafting a command line command to achieve the following:
26
+
27
+ #{description}
28
+
29
+ Considering best practices, what should be run on the command line to achieve this.
30
+
31
+ If no command is possible, respond with 'unknown'
32
+
33
+ PROMPT
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,32 @@
1
+ module Sublayer
2
+ module Agents
3
+ class JSONFixingAgent
4
+ include Sublayer::Capabilities::LLMAssistance
5
+ include Sublayer::Capabilities::HumanAssistance
6
+
7
+ attr_reader :invalid_json, :results
8
+
9
+ llm_result_format type: :single_string,
10
+ name: "valid_json",
11
+ description: "The valid JSON string"
12
+
13
+ def initialize(invalid_json:)
14
+ @invalid_json = invalid_json
15
+ end
16
+
17
+ def execute
18
+ @results = llm_generate
19
+ end
20
+
21
+ def prompt
22
+ <<-PROMPT
23
+ You are an expert in JSON parsing.
24
+
25
+ The given string is not a valid JSON: #{invalid_json}
26
+
27
+ Please fix this and produce a valid JSON.
28
+ PROMPT
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,38 @@
1
+ module Sublayer
2
+ module Agents
3
+ class ModifyFileContentsAgent
4
+ include Sublayer::Capabilities::LLMAssistance
5
+ include Sublayer::Capabilities::HumanAssistance
6
+
7
+ attr_reader :file_path, :description, :technologies, :results
8
+
9
+ llm_result_format type: :single_string,
10
+ name: "modified_file_contents",
11
+ description: "The modified file contents"
12
+
13
+ def initialize(file_path:, description:, technologies:)
14
+ @file_path = file_path
15
+ @description = description
16
+ @technologies = technologies
17
+ end
18
+
19
+ def execute
20
+ @results = human_assistance_with(llm_generate)
21
+ end
22
+
23
+ def prompt
24
+ <<-PROMPT
25
+ You are an expert programmer in #{technologies.join(", ")}.
26
+
27
+ Here are the original file contents:
28
+
29
+ #{File.read(@file_path)}
30
+
31
+ The description of the changes to make is: #{description}
32
+
33
+ Please make the necessary changes to this file.
34
+ PROMPT
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,18 @@
1
+ module Sublayer
2
+ module Agents
3
+ class SaveFileContentsAgent
4
+ attr_reader :file_contents, :file_path
5
+
6
+ def initialize(file_contents:, file_path:)
7
+ @file_contents = file_contents
8
+ @file_path = file_path
9
+ end
10
+
11
+ def execute
12
+ File.open(file_path, "w") do |file|
13
+ file.write(file_contents)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,23 @@
1
+ require "tempfile"
2
+
3
+ module Sublayer
4
+ module Capabilities
5
+ module HumanAssistance
6
+ def human_assistance_with(string_to_assist_with)
7
+ tempfile = Tempfile.new("some_tempfile_to_assist_with")
8
+ tempfile.write(string_to_assist_with)
9
+ tempfile.close
10
+
11
+ system("$EDITOR #{tempfile.path}")
12
+
13
+ tempfile.open
14
+ results = tempfile.read
15
+
16
+ tempfile.close
17
+ tempfile.unlink
18
+
19
+ results.chomp
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,45 @@
1
+ require "openai"
2
+
3
+ module Sublayer
4
+ module Capabilities
5
+ module LLMAssistance
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+ def llm_result_format(type:, name:, description:)
12
+ output_function = Sublayer::Components::OutputFunction.create(type: type, name: name, description: description)
13
+ const_set(:OUTPUT_FUNCTION, output_function)
14
+ end
15
+ end
16
+
17
+ def llm_generate
18
+ client = OpenAI::Client.new(access_token: ENV["OPENAI_API_KEY"])
19
+
20
+ response = client.chat(
21
+ parameters: {
22
+ model: "gpt-4-turbo-preview",
23
+ messages: [
24
+ {
25
+ "role": "user",
26
+ "content": prompt
27
+ }
28
+ ],
29
+ function_call: { name: self.class::OUTPUT_FUNCTION.name },
30
+ functions: [
31
+ self.class::OUTPUT_FUNCTION.to_hash
32
+ ]
33
+ }
34
+ )
35
+
36
+ message = response.dig("choices", 0, "message")
37
+ raise "No function called" unless message["function_call"]
38
+
39
+ function_name = message.dig("function_call", self.class::OUTPUT_FUNCTION.name)
40
+ args_from_llm = message.dig("function_call", "arguments")
41
+ JSON.parse(args_from_llm)[self.class::OUTPUT_FUNCTION.name]
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,23 @@
1
+ module Sublayer
2
+ module Components
3
+ class OutputFunction
4
+ include Sublayer::Components
5
+
6
+ attr_reader :name
7
+
8
+ def self.create(options)
9
+ ("Sublayer::Components::"+options[:type].to_s.camelize).constantize.new(options)
10
+ end
11
+
12
+ def to_hash
13
+ # Raise not implemented error
14
+ raise NotImplementedError
15
+ end
16
+
17
+ private
18
+
19
+ def list_of_objects_hash
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,30 @@
1
+ module Sublayer
2
+ module Components
3
+ class ListOfObjects < OutputFunction
4
+ def initialize(options)
5
+ @name = options[:name]
6
+ @description = options[:description]
7
+ @structure = options[:structure]
8
+ end
9
+
10
+ def to_hash
11
+ {
12
+ name: @name,
13
+ description: @description,
14
+ parameters: {
15
+ type: "object",
16
+ properties: {
17
+ @name.to_sym => {
18
+ type: "array",
19
+ items: {
20
+ type: "object",
21
+ properties: @structure.transform_values { |desc| { type: "string", description: desc.capitalize } }
22
+ }
23
+ }
24
+ }
25
+ }
26
+ }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,26 @@
1
+ module Sublayer
2
+ module Components
3
+ class SingleString < OutputFunction
4
+ def initialize(options)
5
+ @name = options[:name]
6
+ @description = options[:description]
7
+ end
8
+
9
+ def to_hash
10
+ {
11
+ name: @name,
12
+ description: @description,
13
+ parameters: {
14
+ type: "object",
15
+ properties: {
16
+ @name => {
17
+ type: "string",
18
+ description: @description
19
+ }
20
+ }
21
+ }
22
+ }
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,9 @@
1
+ module JSON
2
+ class << self
3
+ alias_method :original_parse, :parse
4
+
5
+ def parse(json, options = {})
6
+ original_parse(json, options).with_indifferent_access
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sublayer
4
+ VERSION = "0.0.1"
5
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'active_support/core_ext/hash/indifferent_access'
5
+ require 'active_support/inflector'
6
+ require_relative "sublayer/version"
7
+
8
+ if !File.directory?(File.join(Dir.pwd, ".sublayer", "agents"))
9
+ Dir.mkdir(File.join(Dir.pwd, ".sublayer"))
10
+ Dir.mkdir(File.join(Dir.pwd, ".sublayer", "agents"))
11
+ end
12
+
13
+ # List of directories to load files from
14
+ LOAD_PATHS = [
15
+ File.join(__dir__, 'sublayer', 'components'),
16
+ File.join(__dir__, 'sublayer', 'components', 'output_function_formats'),
17
+ File.join(__dir__, 'sublayer', 'capabilities'),
18
+ File.join(__dir__, 'sublayer', 'agents'),
19
+ File.join(Dir.pwd, '.sublayer', 'agents')
20
+ ]
21
+
22
+
23
+ # Load files from each directory in the list
24
+ LOAD_PATHS.each do |load_path|
25
+ Dir.glob(File.join(load_path, '*.rb')).each do |file|
26
+ require_relative file
27
+ end
28
+ end
29
+
30
+ require_relative "sublayer/enhancements/json.rb"
31
+
32
+ module Sublayer
33
+ class Error < StandardError; end
34
+
35
+ # Defines a method to reload all the agents
36
+ def self.reload_agents
37
+ LOAD_PATHS.each do |load_path|
38
+ Dir.glob(File.join(load_path, '*.rb')) do |file|
39
+ load file
40
+ end
41
+ end
42
+ end
43
+
44
+ def self.cwd
45
+ Dir.pwd
46
+ end
47
+ end
@@ -0,0 +1,73 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe Sublayer::Components::OutputFunction do
4
+ describe "#to_hash" do
5
+ context "type: :single_string" do
6
+ it "formats the hash output correctly" do
7
+ output_function = Sublayer::Components::OutputFunction.create(type: :single_string, name: "modified_file_contents", description: "The modified file contents")
8
+ expect(output_function.to_hash).to eq(
9
+ {
10
+ name: "modified_file_contents",
11
+ description: "The modified file contents",
12
+ parameters: {
13
+ type: "object",
14
+ properties: {
15
+ "modified_file_contents" => {
16
+ type: "string",
17
+ description: "The modified file contents"
18
+ }
19
+ }
20
+ }
21
+ }
22
+ )
23
+ end
24
+ end
25
+
26
+ context "type: :list_of_objects" do
27
+ it "formats the hash output correctly" do
28
+ output_function = Sublayer::Components::OutputFunction.create(
29
+ type: :list_of_objects,
30
+ name: "retrieved_steps",
31
+ description: "The retrieved steps for performing the given coding task",
32
+ structure: {
33
+ category: "The category of the step",
34
+ command: "The command to run on the command line",
35
+ description: "The description of what the command is for"
36
+ }
37
+ )
38
+
39
+ expect(output_function.to_hash).to eq(
40
+ {
41
+ name: "retrieved_steps",
42
+ description: "The retrieved steps for performing the given coding task",
43
+ parameters: {
44
+ type: "object",
45
+ properties: {
46
+ retrieved_steps: {
47
+ type: "array",
48
+ items: {
49
+ type: "object",
50
+ properties: {
51
+ category: {
52
+ type: "string",
53
+ description: "The category of the step"
54
+ },
55
+ command: {
56
+ type: "string",
57
+ description: "The command to run on the command line"
58
+ },
59
+ description: {
60
+ type: "string",
61
+ description: "The description of what the command is for"
62
+ }
63
+ }
64
+ }
65
+ }
66
+ }
67
+ }
68
+ }
69
+ )
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sublayer"
4
+
5
+ RSpec.configure do |config|
6
+ # Enable flags like --only-failures and --next-failure
7
+ config.example_status_persistence_file_path = ".rspec_status"
8
+
9
+ # Disable RSpec exposing methods globally on `Module` and `main`
10
+ config.disable_monkey_patching!
11
+
12
+ config.expect_with :rspec do |c|
13
+ c.syntax = :expect
14
+ end
15
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/sublayer/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "sublayer"
7
+ spec.version = Sublayer::VERSION
8
+ spec.authors = ["Scott Werner"]
9
+ spec.email = ["scott@sublayer.com"]
10
+ spec.license = "MIT"
11
+
12
+ spec.summary = "A ruby library for interacting with and orchestrating AI agents"
13
+ spec.description = "A command line framework for building, generating, and orchestrating AI agents."
14
+ spec.homepage = "https://www.sublayer.com"
15
+ spec.required_ruby_version = ">= 2.6.0"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(__dir__) do
22
+ `git ls-files -z`.split("\x0").reject do |f|
23
+ (File.expand_path(f) == __FILE__) ||
24
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile])
25
+ end
26
+ end
27
+
28
+ spec.executables << "sublayer"
29
+ spec.executables << "sublayer_simple_tdd_pair"
30
+ spec.require_paths = ["lib"]
31
+
32
+ spec.add_dependency "ruby-openai"
33
+ spec.add_dependency "colorize"
34
+ spec.add_dependency "activesupport"
35
+
36
+ spec.add_development_dependency "rspec", "~> 3.12"
37
+ spec.add_development_dependency "pry", " ~> 0.14"
38
+ end
metadata ADDED
@@ -0,0 +1,165 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: clag
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Scott Werner
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-01-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: cli-kit
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: cli-ui
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 2.2.3
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 2.2.3
41
+ - !ruby/object:Gem::Dependency
42
+ name: ruby-openai
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '6'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: clipboard
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.3'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.3'
69
+ - !ruby/object:Gem::Dependency
70
+ name: activesupport
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '10.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '10.0'
97
+ description: Clag is a command line tool that generates command line commands right
98
+ in your terminal and puts it into your clipboard for you to paste into your terminal.
99
+ email:
100
+ - scott@sublayer.com
101
+ executables:
102
+ - clag
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - ".gitignore"
107
+ - Gemfile
108
+ - README.md
109
+ - bin/testunit
110
+ - clag.gemspec
111
+ - dev.yml
112
+ - exe/clag
113
+ - lib/clag.rb
114
+ - lib/clag/commands.rb
115
+ - lib/clag/commands/generate.rb
116
+ - lib/clag/commands/help.rb
117
+ - lib/clag/entry_point.rb
118
+ - lib/clag/version.rb
119
+ - test/example_test.rb
120
+ - test/test_helper.rb
121
+ - vendor/gems/sublayer/.gitignore
122
+ - vendor/gems/sublayer/Gemfile
123
+ - vendor/gems/sublayer/LICENSE
124
+ - vendor/gems/sublayer/README.md
125
+ - vendor/gems/sublayer/Rakefile
126
+ - vendor/gems/sublayer/lib/sublayer.rb
127
+ - vendor/gems/sublayer/lib/sublayer/agents/generate_code_given_description_agent.rb
128
+ - vendor/gems/sublayer/lib/sublayer/agents/generate_command_agent.rb
129
+ - vendor/gems/sublayer/lib/sublayer/agents/json_fixing_agent.rb
130
+ - vendor/gems/sublayer/lib/sublayer/agents/modify_file_contents_agent.rb
131
+ - vendor/gems/sublayer/lib/sublayer/agents/save_file_contents_agent.rb
132
+ - vendor/gems/sublayer/lib/sublayer/capabilities/human_assistance.rb
133
+ - vendor/gems/sublayer/lib/sublayer/capabilities/llm_assistance.rb
134
+ - vendor/gems/sublayer/lib/sublayer/components/output_function.rb
135
+ - vendor/gems/sublayer/lib/sublayer/components/output_function_formats/list_of_objects.rb
136
+ - vendor/gems/sublayer/lib/sublayer/components/output_function_formats/single_string.rb
137
+ - vendor/gems/sublayer/lib/sublayer/enhancements/json.rb
138
+ - vendor/gems/sublayer/lib/sublayer/version.rb
139
+ - vendor/gems/sublayer/spec/components/output_function_spec.rb
140
+ - vendor/gems/sublayer/spec/spec_helper.rb
141
+ - vendor/gems/sublayer/sublayer.gemspec
142
+ homepage: https://github.com/sublayerapp/clag
143
+ licenses:
144
+ - MIT
145
+ metadata: {}
146
+ post_install_message:
147
+ rdoc_options: []
148
+ require_paths:
149
+ - lib
150
+ required_ruby_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: '3'
155
+ required_rubygems_version: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ requirements: []
161
+ rubygems_version: 3.3.26
162
+ signing_key:
163
+ specification_version: 4
164
+ summary: Generate command line commands in your terminal using an LLM
165
+ test_files: []