clag 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []