├── dev.yml ├── lib ├── clag │ ├── version.rb │ ├── entry_point.rb │ ├── commands.rb │ ├── commands │ │ ├── help.rb │ │ └── generate.rb │ └── generators │ │ └── command_line_command_generator.rb └── clag.rb ├── .gitignore ├── Gemfile ├── exe └── clag ├── bin └── testunit ├── test ├── test_helper.rb └── example_test.rb ├── LICENSE ├── clag.gemspec └── README.md /dev.yml: -------------------------------------------------------------------------------- 1 | up: 2 | - ruby: 2.5.0 3 | - bundler 4 | -------------------------------------------------------------------------------- /lib/clag/version.rb: -------------------------------------------------------------------------------- 1 | module Clag 2 | VERSION = '0.0.7' 3 | end 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 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 17 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 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 10 | -------------------------------------------------------------------------------- /lib/clag/entry_point.rb: -------------------------------------------------------------------------------- 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 12 | -------------------------------------------------------------------------------- /lib/clag/commands.rb: -------------------------------------------------------------------------------- 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 16 | -------------------------------------------------------------------------------- /exe/clag: -------------------------------------------------------------------------------- 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 | 12 | require 'bundler/setup' 13 | require 'clag' 14 | 15 | exit(Clag::ErrorHandler.call do 16 | Clag::EntryPoint.call(ARGV.dup) 17 | end) 18 | -------------------------------------------------------------------------------- /bin/testunit: -------------------------------------------------------------------------------- 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 24 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 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' 23 | -------------------------------------------------------------------------------- /test/example_test.rb: -------------------------------------------------------------------------------- 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 18 | -------------------------------------------------------------------------------- /lib/clag/commands/help.rb: -------------------------------------------------------------------------------- 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 24 | -------------------------------------------------------------------------------- /lib/clag/generators/command_line_command_generator.rb: -------------------------------------------------------------------------------- 1 | module Clag 2 | class CommandLineCommandGenerator < Sublayer::Generators::Base 3 | llm_output_adapter type: :single_string, 4 | name: "command", 5 | description: "The command line command for the user to run or 'unknown'" 6 | 7 | def initialize(description:) 8 | @description = description 9 | end 10 | 11 | def generate 12 | super 13 | end 14 | 15 | def prompt 16 | <<-PROMPT 17 | You are an expert in command line operations. 18 | 19 | You are tasked with finding or crafting a command line command to achieve the following: 20 | 21 | #{@description} 22 | 23 | Considering best practices, what should be run on the command line to achieve this. 24 | 25 | If no command is possible, respond with 'unknown' 26 | PROMPT 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 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. 22 | -------------------------------------------------------------------------------- /lib/clag/commands/generate.rb: -------------------------------------------------------------------------------- 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 = Clag::CommandLineCommandGenerator.new(description: input).generate 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 37 | -------------------------------------------------------------------------------- /clag.gemspec: -------------------------------------------------------------------------------- 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 'httparty', '~> 0.21' 24 | spec.add_dependency 'clipboard', '~> 1.3' 25 | spec.add_dependency 'activesupport' 26 | spec.add_dependency "pry" 27 | spec.add_dependency "nokogiri" 28 | spec.add_dependency "sublayer", '> 0.0.1' 29 | 30 | spec.add_development_dependency 'rake', '> 12.3.3' 31 | end 32 | -------------------------------------------------------------------------------- /lib/clag.rb: -------------------------------------------------------------------------------- 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 | load 'clag/generators/command_line_command_generator.rb' 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 | 27 | case ENV["CLAG_LLM"] 28 | when "gemini" 29 | Sublayer.configuration.ai_provider = Sublayer::Providers::Gemini 30 | Sublayer.configuration.ai_model = "gemini-pro" 31 | when "claude" 32 | Sublayer.configuration.ai_provider = Sublayer::Providers::Claude 33 | Sublayer.configuration.ai_model ="claude-3-opus-20240229" 34 | when "groq" 35 | Sublayer.configuration.ai_provider = Sublayer::Providers::Groq 36 | Sublayer.configuration.ai_model = "mixtral-8x7b-32768" 37 | when "local" 38 | Sublayer.configuration.ai_provider = Sublayer::Providers::Local 39 | Sublayer.configuration.ai_model = "LLaMA_CPP" 40 | else 41 | Sublayer.configuration.ai_provider = Sublayer::Providers::OpenAI 42 | Sublayer.configuration.ai_model = "gpt-4-turbo-preview" 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 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 | * Install the gem 12 | `gem install clag` 13 | 14 | * Generate commands 15 | `clag g "create a new ruby on rails project using postgres and tailwindcss"` 16 | 17 | ### Using OpenAI's GPT-4 18 | 19 | * Get an API key from OpenAI for gpt4-turbo: https://platform.openai.com/ 20 | 21 | * Set your API key as OPENAI\_API\_KEY in your environment 22 | 23 | 24 | ### Using Google's Gemini 1.0 25 | 26 | * Get an API key from Google's AI Studio at https://ai.google.dev/ 27 | 28 | * Set your API key as GEMINI\_API\_KEY in your environment 29 | 30 | * Select Gemini as your preferred LLM by setting CLAG\_LLM=gemini in your 31 | environment 32 | 33 | ### Using Anthropic's Claude 3 Opus 34 | 35 | * Get an API key from Anthropic at https://www.anthropic.com/ 36 | 37 | * Set your API key as ANTHROPIC\_API\_KEY in your environment 38 | 39 | * Select Claude 3 Opus as your preferred LLM by setting CLAG\_LLM=claude in 40 | your environment 41 | 42 | ### Using Groq on Mixtral 43 | 44 | * Get an API key from https://console.groq.com/ 45 | 46 | * Set your API key as GROQ\_API\_KEY in your environment 47 | 48 | * Select Groq as your preferred LLM by setting CLAG\_LLM=groq in your environment 49 | 50 | ### Using a Local Model 51 | 52 | * Have a model locally from either Ollama or Llamafile with an OpenAI compatible 53 | API 54 | 55 | * Have the API server running on port 8080 56 | 57 | * Select local as your preferred LLM by setting CLAG\_LLM=local in your environment 58 | 59 | ## Usage 60 | 61 | Currently support one command: "g". 62 | 63 | `clag g "the command you'd like to generate"` 64 | 65 | ## Contributing 66 | 67 | Bug reports and pull requests are welcome on Github at 68 | https://github.com/sublayerapp/clag 69 | 70 | ## Community 71 | 72 | Like what you see, or looking for more people working on the future of 73 | programming with LLMs? Come join us in the [Promptable Architecture 74 | Discord](https://discord.gg/sjTJszPwXt) 75 | --------------------------------------------------------------------------------