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