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.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/Gemfile +9 -0
- data/README.md +30 -0
- data/bin/testunit +23 -0
- data/clag.gemspec +27 -0
- data/dev.yml +3 -0
- data/exe/clag +19 -0
- data/lib/clag/commands/generate.rb +36 -0
- data/lib/clag/commands/help.rb +23 -0
- data/lib/clag/commands.rb +15 -0
- data/lib/clag/entry_point.rb +11 -0
- data/lib/clag/version.rb +3 -0
- data/lib/clag.rb +26 -0
- data/test/example_test.rb +17 -0
- data/test/test_helper.rb +22 -0
- data/vendor/gems/sublayer/.gitignore +16 -0
- data/vendor/gems/sublayer/Gemfile +6 -0
- data/vendor/gems/sublayer/LICENSE +21 -0
- data/vendor/gems/sublayer/README.md +52 -0
- data/vendor/gems/sublayer/Rakefile +8 -0
- data/vendor/gems/sublayer/lib/sublayer/agents/generate_code_given_description_agent.rb +35 -0
- data/vendor/gems/sublayer/lib/sublayer/agents/generate_command_agent.rb +37 -0
- data/vendor/gems/sublayer/lib/sublayer/agents/json_fixing_agent.rb +32 -0
- data/vendor/gems/sublayer/lib/sublayer/agents/modify_file_contents_agent.rb +38 -0
- data/vendor/gems/sublayer/lib/sublayer/agents/save_file_contents_agent.rb +18 -0
- data/vendor/gems/sublayer/lib/sublayer/capabilities/human_assistance.rb +23 -0
- data/vendor/gems/sublayer/lib/sublayer/capabilities/llm_assistance.rb +45 -0
- data/vendor/gems/sublayer/lib/sublayer/components/output_function.rb +23 -0
- data/vendor/gems/sublayer/lib/sublayer/components/output_function_formats/list_of_objects.rb +30 -0
- data/vendor/gems/sublayer/lib/sublayer/components/output_function_formats/single_string.rb +26 -0
- data/vendor/gems/sublayer/lib/sublayer/enhancements/json.rb +9 -0
- data/vendor/gems/sublayer/lib/sublayer/version.rb +5 -0
- data/vendor/gems/sublayer/lib/sublayer.rb +47 -0
- data/vendor/gems/sublayer/spec/components/output_function_spec.rb +73 -0
- data/vendor/gems/sublayer/spec/spec_helper.rb +15 -0
- data/vendor/gems/sublayer/sublayer.gemspec +38 -0
- 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
data/Gemfile
ADDED
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
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
|
data/lib/clag/version.rb
ADDED
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
|
data/test/test_helper.rb
ADDED
@@ -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,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,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,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: []
|