sublayer 0.2.4 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/bin/sublayer +1 -1
  3. data/lib/sublayer/cli/commands/action.rb +65 -0
  4. data/lib/sublayer/cli/commands/agent.rb +79 -0
  5. data/lib/sublayer/cli/commands/generator.rb +68 -0
  6. data/lib/sublayer/cli/commands/generators/example_action_api_call.rb +23 -0
  7. data/lib/sublayer/cli/commands/generators/example_action_file_manipulation.rb +12 -0
  8. data/lib/sublayer/cli/commands/generators/example_agent.rb +33 -0
  9. data/lib/sublayer/cli/commands/generators/example_generator.rb +26 -0
  10. data/lib/sublayer/cli/commands/generators/sublayer_action_generator.rb +55 -0
  11. data/lib/sublayer/cli/commands/generators/sublayer_agent_generator.rb +61 -0
  12. data/lib/sublayer/cli/commands/generators/sublayer_generator_generator.rb +96 -0
  13. data/lib/sublayer/cli/commands/new_project.rb +116 -0
  14. data/lib/sublayer/cli/commands/subcommand_base.rb +13 -0
  15. data/lib/sublayer/{templates/cli/PROJECT_NAME.gemspec → cli/templates/cli/%project_name%.gemspec.tt} +7 -7
  16. data/lib/sublayer/{templates/cli/README.md → cli/templates/cli/README.md.tt} +2 -2
  17. data/lib/sublayer/cli/templates/cli/bin/%project_name%.tt +5 -0
  18. data/lib/sublayer/{templates/cli/lib/PROJECT_NAME/actions/example_action.rb → cli/templates/cli/lib/%project_name%/actions/example_action.rb.tt} +1 -1
  19. data/lib/sublayer/{templates/cli/lib/PROJECT_NAME/agents/example_agent.rb → cli/templates/cli/lib/%project_name%/agents/example_agent.rb.tt} +1 -1
  20. data/lib/sublayer/cli/templates/cli/lib/%project_name%/cli.rb.tt +13 -0
  21. data/lib/sublayer/{templates/cli/lib/PROJECT_NAME/commands/base_command.rb → cli/templates/cli/lib/%project_name%/commands/base_command.rb.tt} +1 -1
  22. data/lib/sublayer/{templates/cli/lib/PROJECT_NAME/commands/example_command.rb → cli/templates/cli/lib/%project_name%/commands/example_command.rb.tt} +2 -2
  23. data/lib/sublayer/{templates/cli/lib/PROJECT_NAME/config.rb → cli/templates/cli/lib/%project_name%/config.rb.tt} +1 -1
  24. data/lib/sublayer/{templates/cli/lib/PROJECT_NAME/generators/example_generator.rb → cli/templates/cli/lib/%project_name%/generators/example_generator.rb.tt} +1 -1
  25. data/lib/sublayer/{templates/cli/lib/PROJECT_NAME/version.rb → cli/templates/cli/lib/%project_name%/version.rb.tt} +1 -1
  26. data/lib/sublayer/cli/templates/cli/lib/%project_name%.rb.tt +21 -0
  27. data/lib/sublayer/{templates/quick_script/project_name.rb → cli/templates/quick_script/%project_name%.rb} +2 -2
  28. data/lib/sublayer/{templates/quick_script/README.md → cli/templates/quick_script/README.md.tt} +3 -3
  29. data/lib/sublayer/cli.rb +46 -178
  30. data/lib/sublayer/components/output_adapters/single_integer.rb +25 -0
  31. data/lib/sublayer/providers/gemini.rb +10 -22
  32. data/lib/sublayer/version.rb +1 -1
  33. data/lib/sublayer.rb +0 -1
  34. data/sublayer.gemspec +0 -7
  35. metadata +36 -121
  36. data/lib/sublayer/templates/cli/bin/PROJECT_NAME +0 -5
  37. data/lib/sublayer/templates/cli/lib/PROJECT_NAME/cli.rb +0 -13
  38. data/lib/sublayer/templates/cli/lib/project_name.rb +0 -21
  39. /data/lib/sublayer/{templates → cli/templates}/cli/.gitignore +0 -0
  40. /data/lib/sublayer/{templates → cli/templates}/cli/Gemfile +0 -0
  41. /data/lib/sublayer/{templates/cli/lib/PROJECT_NAME → cli/templates/cli/lib/%project_name%}/config/.keep +0 -0
  42. /data/lib/sublayer/{templates → cli/templates}/cli/spec/.keep +0 -0
  43. /data/lib/sublayer/{templates → cli/templates}/quick_script/actions/example_action.rb +0 -0
  44. /data/lib/sublayer/{templates → cli/templates}/quick_script/agents/example_agent.rb +0 -0
  45. /data/lib/sublayer/{templates → cli/templates}/quick_script/generators/example_generator.rb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2238a9858f994058b23edf70f97255d6c3b20d648c7e73991c05eb7232a9ec6b
4
- data.tar.gz: 935f154d71e8dab6ee3906f700d1f42092f33b9e15ce07c3b305817bcca8177e
3
+ metadata.gz: ae309181fe8ac4451cb6740fbaaea2599539969ec10d67efe2de75a62e485194
4
+ data.tar.gz: 0c2f60f772efebe97bd2b9d1da34c6545a8bf67aa9f2dded954a5bb9a3c523f8
5
5
  SHA512:
6
- metadata.gz: ae8d296c701a18a7e4dde9366f05e4ecf8a5afc964cdc336a563201049660d92345f92219bd1537517bc961e6baf74bab4b9f4652d79666cdf2041e510be2451
7
- data.tar.gz: af8c3a33325de5e2354cc82edd73212f91e5f24bf8c70ae2fac31b58d480ea1c08c76e938e60be714a5f67871940d497b21611daf1e576d6333bb52772e1e4cd
6
+ metadata.gz: cffeb06614a0925c823fc0f05c79361b381146e6cba825d697f8796a8c961138f307c23faaf4fed2c4c2f5fcf532255e1374da148b4de427914fc51a9b16e0b0
7
+ data.tar.gz: 68d200d5297030675c4ce4616e30d25500f58e060e16ac22f8ddb3e6daf51d52f13ba0a92d9da85d27d5669c4106cc2a55feda98bcdf2a6846c1e098c3b84cb3
data/bin/sublayer CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "sublayer/cli"
3
+ require_relative "../lib/sublayer/cli"
4
4
 
5
5
  Sublayer::CLI.start(ARGV)
@@ -0,0 +1,65 @@
1
+ require_relative "./generators/sublayer_action_generator"
2
+
3
+ module Sublayer
4
+ module Commands
5
+ class Action < Thor::Group
6
+ include Thor::Actions
7
+
8
+ class_option :description, type: :string, desc: "Description of the action you want to generate", aliases: :d
9
+ class_option :provider, type: :string, desc: "AI provider (OpenAI, Claude, or Gemini)", aliases: :p
10
+ class_option :model, type: :string, desc: "AI model name to use (e.g. gpt-4o, claude-3-haiku-20240307, gemini-1.5-flash-latest)", aliases: :m
11
+
12
+ def confirm_usage_of_ai_api
13
+ puts "You are about to generate a new agent that uses an AI API to generate content."
14
+ puts "Please ensure you have the necessary API keys and that you are aware of the costs associated with using the API."
15
+ exit unless yes?("Do you want to continue?")
16
+ end
17
+
18
+ def determine_available_providers
19
+ @available_providers = []
20
+
21
+ @available_providers << "OpenAI" if ENV["OPENAI_API_KEY"]
22
+ @available_providers << "Claude" if ENV["ANTHROPIC_API_KEY"]
23
+ @available_providers << "Gemini" if ENV["GEMINI_API_KEY"]
24
+ end
25
+
26
+ def ask_for_action_details
27
+ @ai_provider = options[:provider] || ask("Select an AI provider:", default: "OpenAI", limited_to: @available_providers)
28
+ @ai_model = options[:model] || select_ai_model
29
+
30
+ @description = options[:description] || ask("Enter a description for the Sublayer Action you'd like to create:")
31
+ end
32
+
33
+ def generate_action
34
+ @results = SublayerActionGenerator.new(description: @description).generate
35
+ end
36
+
37
+ def determine_destination_folder
38
+ @destination_folder = if File.directory?("./actions")
39
+ "./actions"
40
+ elsif Dir.glob("./lib/**/actions").any?
41
+ Dir.glob("./lib/**/actions").first
42
+ else
43
+ "./"
44
+ end
45
+ end
46
+
47
+ def save_action_to_destination_folder
48
+ create_file File.join(@destination_folder, @results.filename), @results.code
49
+ end
50
+
51
+ private
52
+
53
+ def select_ai_model
54
+ case @ai_provider
55
+ when "OpenAI"
56
+ ask("Which OpenAI model would you like to use?", default: "gpt-4o")
57
+ when "Claude"
58
+ ask("Which Anthropic model would you like to use?", default: "claude-3-5-sonnet-20240620")
59
+ when "Gemini"
60
+ ask("Which Google model would you like to use?", default: "gemini-1.5-flash-latest")
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,79 @@
1
+ require_relative "./generators/sublayer_agent_generator"
2
+
3
+ module Sublayer
4
+ module Commands
5
+ class Agent < Thor::Group
6
+ include Thor::Actions
7
+
8
+ class_option :description, type: :string, desc: "Description of the agent you want to generate", aliases: :d
9
+ class_option :provider, type: :string, desc: "AI provider (OpenAI, Claude, or Gemini)", aliases: :p
10
+ class_option :model, type: :string, desc: "AI model name to use (e.g. gpt-4o, claude-3-haiku-20240307, gemini-1.5-flash-latest)", aliases: :m
11
+
12
+ def confirm_usage_of_ai_api
13
+ puts "You are about to generate a new agent that uses an AI API to generate content."
14
+ puts "Please ensure you have the necessary API keys and that you are aware of the costs associated with using the API."
15
+ exit unless yes?("Do you want to continue?")
16
+ end
17
+
18
+ def determine_available_providers
19
+ @available_providers = []
20
+
21
+ @available_providers << "OpenAI" if ENV["OPENAI_API_KEY"]
22
+ @available_providers << "Claude" if ENV["ANTHROPIC_API_KEY"]
23
+ @available_providers << "Gemini" if ENV["GEMINI_API_KEY"]
24
+ end
25
+
26
+ def ask_for_agent_details
27
+ @ai_provider = options[:provider] || ask("Select an AI provider:", default: "OpenAI", limited_to: @available_providers)
28
+ @ai_model = options[:model] || select_ai_model
29
+ @description = options[:description] || ask("Enter a description for the Sublayer Agent you'd like to create:")
30
+ @trigger = ask("What should trigger this agent to start acting?")
31
+ @goal = ask("What is the agent's goal condition?")
32
+ @check_status = ask("How should the agent check its status toward the goal?")
33
+ @step = ask("How does the agent take a step toward its goal?")
34
+ end
35
+
36
+ def generate_agent
37
+ Sublayer.configuration.ai_provider = Object.const_get("Sublayer::Providers::#{@ai_provider}")
38
+ Sublayer.configuration.ai_model = @ai_model
39
+
40
+ say "Generating Sublayer Agent..."
41
+
42
+ @results = SublayerAgentGenerator.new(
43
+ description: @description,
44
+ trigger: @trigger_explanation,
45
+ goal: @goal,
46
+ check_status: @check_status,
47
+ step: @step
48
+ ).generate
49
+ end
50
+
51
+ def determine_destination_folder
52
+ @destination_folder = if File.directory?("./agents")
53
+ "./agents"
54
+ elsif Dir.glob("./lib/**/agents").any?
55
+ Dir.glob("./lib/**/agents").first
56
+ else
57
+ "./"
58
+ end
59
+ end
60
+
61
+ def save_agent_to_destination_folder
62
+ create_file File.join(@destination_folder, @results.filename), @results.code
63
+ end
64
+
65
+ private
66
+
67
+ def select_ai_model
68
+ case @ai_provider
69
+ when "OpenAI"
70
+ ask("Which OpenAI model would you like to use?", default: "gpt-4o")
71
+ when "Claude"
72
+ ask("Which Anthropic model would you like to use?", default: "claude-3-5-sonnet-20240620")
73
+ when "Gemini"
74
+ ask("Which Google model would you like to use?", default: "gemini-1.5-flash-latest")
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,68 @@
1
+ require_relative "generators/sublayer_generator_generator"
2
+
3
+ module Sublayer
4
+ module Commands
5
+ class Generator < Thor::Group
6
+ include Thor::Actions
7
+
8
+ class_option :description, type: :string, desc: "Description of the generator you want to generate", aliases: :d
9
+ class_option :provider, type: :string, desc: "AI provider (OpenAI, Claude, or Gemini)", aliases: :p
10
+ class_option :model, type: :string, desc: "AI model name to use (e.g. gpt-4o, claude-3-haiku-20240307, gemini-1.5-flash-latest)", aliases: :m
11
+
12
+ def confirm_usage_of_ai_api
13
+ puts "You are about to generate a new generator that uses an AI API to generate content."
14
+ puts "Please ensure you have the necessary API keys and that you are aware of the costs associated with using the API."
15
+ exit unless yes?("Do you want to continue?")
16
+ end
17
+
18
+ def determine_available_providers
19
+ @available_providers = []
20
+
21
+ @available_providers << "OpenAI" if ENV["OPENAI_API_KEY"]
22
+ @available_providers << "Claude" if ENV["ANTHROPIC_API_KEY"]
23
+ @available_providers << "Gemini" if ENV["GEMINI_API_KEY"]
24
+ end
25
+
26
+ def ask_for_generator_details
27
+ @description = options[:description] || ask("Enter a description for the Sublayer Generator you'd like to create:")
28
+ @ai_provider = options[:provider] || ask("Select an AI provider:", default: "OpenAI", limited_to: @available_providers)
29
+ @ai_model = options[:model] || select_ai_model
30
+ end
31
+
32
+ def generate_generator
33
+ Sublayer.configuration.ai_provider = Object.const_get("Sublayer::Providers::#{@ai_provider}")
34
+ Sublayer.configuration.ai_model = @ai_model
35
+
36
+ say "Generating Sublayer Generator..."
37
+ @results = SublayerGeneratorGenerator.new(description: @description).generate
38
+ end
39
+
40
+ def determine_destination_folder
41
+ # Find either a ./generators folder or a generators folder nested one level below ./lib
42
+ @destination_folder = if File.directory?("./generators")
43
+ "./generators"
44
+ elsif Dir.glob("./lib/**/generators").any?
45
+ Dir.glob("./lib/**/generators").first
46
+ else
47
+ "./"
48
+ end
49
+ end
50
+
51
+ def save_generator_to_destination_folder
52
+ create_file File.join(@destination_folder, @results.filename), @results.code
53
+ end
54
+
55
+ private
56
+ def select_ai_model
57
+ case @ai_provider
58
+ when "OpenAI"
59
+ ask("Which OpenAI model would you like to use?", default: "gpt-4o")
60
+ when "Claude"
61
+ ask("Which Anthropic model would you like to use?", default: "claude-3-5-sonnet-20240620")
62
+ when "Gemini"
63
+ ask("Which Google model would you like to use?", default: "gemini-1.5-flash-latest")
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,23 @@
1
+ class TextToSpeechAction < Sublayer::Actions::Base
2
+ def initialize(text)
3
+ @text = text
4
+ end
5
+
6
+ def call
7
+ speech = HTTParty.post(
8
+ "https://api.openai.com/v1/audio/speech",
9
+ headers: {
10
+ "Authorization" => "Bearer #{ENV["OPENAI_API_KEY"]}",
11
+ "Content-Type" => "application/json",
12
+ },
13
+ body: {
14
+ "model": "tts-1",
15
+ "input": @text,
16
+ "voice": "nova",
17
+ "response_format": "wav"
18
+ }.to_json
19
+ )
20
+
21
+ speech
22
+ end
23
+ end
@@ -0,0 +1,12 @@
1
+ class WriteFileAction < Sublayer::Actions::Base
2
+ def initialize(file_contents:, file_path:)
3
+ @file_contents = file_contents
4
+ @file_path = file_path
5
+ end
6
+
7
+ def call
8
+ File.open(@file_path, 'wb') do |file|
9
+ file.write(@file_contents)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,33 @@
1
+ class RSpecAgent < Sublayer::Agents::Base
2
+ def initialize(implementation_file_path:, test_file_path:)
3
+ @implementation_file_path = implementation_file_path
4
+ @test_file_path = test_file_path
5
+ @tests_passing = false
6
+ end
7
+
8
+ trigger_on_files_changed { [@implementation_file_path, @test_file_path] }
9
+
10
+ goal_condition { @tests_passing == true }
11
+
12
+ check_status do
13
+ stdout, stderr, status = Sublayer::Actions::RunTestCommandAction.new(
14
+ test_command: "rspec #{@test_file_path}"
15
+ ).call
16
+
17
+ @test_output = stdout
18
+ @tests_passing = (status.exitstatus == 0)
19
+ end
20
+
21
+ step do
22
+ modified_implementation = Sublayer::Generators::ModifiedImplementationToPassTestsGenerator.new(
23
+ implementation_file_contents: File.read(@implementation_file_path),
24
+ test_file_contents: File.read(@test_file_path),
25
+ test_output: @test_output
26
+ ).generate
27
+
28
+ Sublayer::Actions::WriteFileAction.new(
29
+ file_contents: modified_implementation,
30
+ file_path: @implementation_file_path
31
+ ).call
32
+ end
33
+ end
@@ -0,0 +1,26 @@
1
+ class CodeFromDescriptionGenerator < Sublayer::Generators::Base
2
+ llm_output_adapter type: :single_string,
3
+ name: "generated_code",
4
+ description: "The generated code in the requested language"
5
+
6
+ def initialize(description:, technologies:)
7
+ @description = description
8
+ @technologies = technologies
9
+ end
10
+
11
+ def generate
12
+ super
13
+ end
14
+
15
+ def prompt
16
+ <<-PROMPT
17
+ You are an expert programmer in #{@technologies.join(", ")}.
18
+
19
+ You are tasked with writing code using the following technologies: #{@technologies.join(", ")}.
20
+
21
+ The description of the task is #{@description}
22
+
23
+ Take a deep breath and think step by step before you start coding.
24
+ PROMPT
25
+ end
26
+ end
@@ -0,0 +1,55 @@
1
+ class SublayerActionGenerator < Sublayer::Generators::Base
2
+ llm_output_adapter type: :named_strings,
3
+ name: "sublayer_action",
4
+ description: "The new sublayer action based on the description and supporting information",
5
+ attributes: [
6
+ { name: "code", description: "The code of the generated Sublayer action" },
7
+ { name: "filename", description: "The filename of the generated sublayer action snake cased with a .rb extension" }
8
+ ]
9
+
10
+ def initialize(description:)
11
+ @description = description
12
+ end
13
+
14
+ def generate
15
+ super
16
+ end
17
+
18
+ def prompt
19
+ <<-PROMPT
20
+ You are an expert ruby programmer and are great at repurposing code examples to use for new situations.
21
+
22
+ A Sublayer Action is an example of a command pattern that is used in the Sublayer AI framework to perform actions in the outside world such as manipulating files or making API calls.
23
+
24
+ The Sublayer framework also has a component called a Generator that takes data in, sends it to an LLM and gets structured data out.
25
+ Sublayer::Actions are used both to retrieve data for use in a generator or perform actions based on the output of the generator.
26
+ This is used to both aid in generating new composable bits of functionality and to ease testing.
27
+
28
+ A sublayer action is initialized with the data it needs and then exposes a `call` method which is used to perform the action.
29
+
30
+ An example of an action being used to save a file to the file system, for example after generating the contents is:
31
+ <example_file_manipulation_action>
32
+ #{example_filesystem_action}
33
+ </example_file_manipulation_action>
34
+
35
+ An example action being used to make a call to an external api, such as OpenAI's text to speech API is:
36
+ <example_api_call_action>
37
+ #{example_api_action}
38
+ </example_api_call_action>
39
+
40
+ Your task is to generate a new Sublayer::Action::Base subclass that performs an action based on the description provided.
41
+ <description>
42
+ #{@description}
43
+ </description>
44
+ PROMPT
45
+ end
46
+
47
+ private
48
+ def example_filesystem_action
49
+ File.read(File.join(File.dirname(__FILE__), 'example_action_file_manipulation.rb'))
50
+ end
51
+
52
+ def example_api_action
53
+ File.read(File.join(File.dirname(__FILE__), 'example_action_api_call.rb'))
54
+ end
55
+ end
@@ -0,0 +1,61 @@
1
+ class SublayerAgentGenerator < Sublayer::Generators::Base
2
+ llm_output_adapter type: :named_strings,
3
+ name: "sublayer_agent",
4
+ description: "The new sublayer agent based on the description and supporting information",
5
+ attributes: [
6
+ { name: "code", description: "The code of the generated Sublayer agent" },
7
+ { name: "filename", description: "The filename of the generated sublayer agent snake cased with a .rb extension" }
8
+ ]
9
+
10
+ def initialize(description:, trigger:, goal:, check_status:, step:)
11
+ @description = description
12
+ @trigger_condition = trigger
13
+ @goal = goal
14
+ @check_status = check_status
15
+ @step = step
16
+ end
17
+
18
+ def generate
19
+ super
20
+ end
21
+
22
+ def prompt
23
+ <<-PROMPT
24
+ You are an expert ruby programmer and great at repurposing code examples to use for new situations.
25
+
26
+ A Sublayer agent is a DSL for defining a feedback loop for an AI agent. The agents sit running like a daemon and are triggered to run and step toward their goal checking status along the way.
27
+
28
+ One example of a Sublayer agent is this one for doing TDD with RSpec:
29
+ <example_tdd_agent>
30
+ #{example_agent}
31
+ </example_tdd_agent>
32
+
33
+ Sublayer Agents take advantage of other Sublayer components to perform their tasks.
34
+ Sublayer::Actions are used to perform actions in the outside world, things like saving files, making external api calls, retrieving data, etc
35
+ Sublayer::Generators are used to make calls to LLMs based on information they receive from Sublayer::Actions or other sources and return structured data for other Sublayer::Actions to do use in the outside world
36
+
37
+ The Sublayer Agent DSL consists of 4 main parts:
38
+ 1. trigger: What triggers the agent, currently built into the framework is the trigger_on_files_changed, but the trigger method also accepts a Sublayer::Triggers::Base subclass that defines an initialize method and a setup method that takes an agent as an argument
39
+ The setup method then calls activate(agent) when the trigger condition is met
40
+ 2. goal: What the agent is trying to achieve, this is a block that returns a boolean
41
+ 3. check_status: A method that checks the status of the agent on its way toward the goal. Usually you update the goal condition here or can perform any Sublayer::Actions to examine the state of the outside world
42
+ 4. step: A method that encapsulates the way the agent should work toward the goal. Sublayer::Actions and Sublayer::Generators are used heavily here.
43
+
44
+ Your goal is to rely on the above information about how Sublayer Agents work to generate a new Sublayer agent based on the following information:
45
+
46
+ Agent description: #{@description}
47
+ Trigger condition: #{@trigger}
48
+ Goal condition: #{@goal}
49
+ Status check method: #{@check_status}
50
+ Step action method: #{@step}
51
+
52
+ You can assume that any Sublayer::Actions or Sublayer::Generators you need are available to you in the Sublayer framework
53
+
54
+ Take a deep breath and think step by step before coding. You can do this!
55
+ PROMPT
56
+ end
57
+
58
+ def example_agent
59
+ File.read(File.join(__dir__, "example_agent.rb"))
60
+ end
61
+ end
@@ -0,0 +1,96 @@
1
+ class SublayerGeneratorGenerator < Sublayer::Generators::Base
2
+ llm_output_adapter type: :named_strings,
3
+ name: "sublayer_generator",
4
+ description: "The new Sublayer generator code based on the description",
5
+ attributes: [
6
+ { name: "code", description: "The code of the generated Sublayer generator" },
7
+ { name: "filename", description: "The filename of the generated Sublayer generator snake cased and with a .rb extension" }
8
+ ]
9
+
10
+ def initialize(description:)
11
+ @description = description
12
+ end
13
+
14
+ def generate
15
+ super
16
+ end
17
+
18
+ def prompt
19
+ <<-PROMPT
20
+ You are an expert ruby programmer and and great at repurposing code examples to use for new situations.
21
+
22
+ A Sublayer generator is a class that acts as an abstraction for sending a request to an LLM and getting structured data back.
23
+
24
+ An example Sublayer generator is:
25
+ <example_generator>
26
+ #{example_generator}
27
+ </example_generator>
28
+
29
+ And it is the generator we're currently using for taking in a description from a user and generating a new Sublayer generator.
30
+
31
+ All Sublayer::Generators inherit from Sublayer::Generators::Base and have a generate method that simply calls super for the user to use and modify for their uses.
32
+
33
+ the llm_output_adapter directive is used to instruct the LLM on what structure of output to generate. In the example we're using type: :single_string which takes a name and description as arguments.
34
+
35
+ the other available options and their example usage is:
36
+ llm_output_adapter type: :list_of_strings,
37
+ name: "suggestions",
38
+ description: "List of keyword suggestions"
39
+
40
+ llm_output_adapter type: :single_integer,
41
+ name: "four_digit_passcode",
42
+ description: "an uncommon and difficult to guess four digit passcode"
43
+
44
+ llm_output_adapter type: :list_of_named_strings,
45
+ name: "review_summaries",
46
+ description: "List of movie reviews",
47
+ item_name: "review",
48
+ attributes: [
49
+ { name: "movie_title", description: "The title of the movie" },
50
+ { name: "reviewer_name", description: "The name of the reviewer" },
51
+ { name: "rating", description: "The rating given by the reviewer (out of 5 stars)" },
52
+ { name: "brief_comment", description: "A brief summary of the movie" }
53
+ ]
54
+
55
+ llm_output_adapter type: :named_strings,
56
+ name: "product_description",
57
+ description: "Generate product descriptions",
58
+ attributes: [
59
+ { name: "short_description", description: "A brief one-sentence description of the product" },
60
+ { name: "long_description", description: "A detailed paragraph describing the product" },
61
+ { name: "key_features", description: "A comma-separated list of key product features" },
62
+ { name: "target_audience", description: "A brief description of the target audience for this product" }
63
+ ]
64
+
65
+ # Where :available_routes is a method that returns an array of available routes
66
+ llm_output_adapter type: :string_selection_from_list,
67
+ name: "route",
68
+ description: "A route selected from the list",
69
+ options: :available_routes
70
+
71
+ # Where @sentiment_options is an array of sentiment values passed in to the initializer
72
+ llm_output_adapter type: :string_selection_from_list,
73
+ name: "sentiment_value",
74
+ description: "A sentiment value from the list",
75
+ options: -> { @sentiment_options }
76
+
77
+ Besides that, a Sublayer::Generator also has a prompt method that describes the task to the LLM and provides any necessary context for the generation which
78
+ is either passed in through the initializer or accessed in the prompt itself.
79
+
80
+ You have a description provided by the user to generate a new sublayer generator.
81
+
82
+ You are tasked with creating a new sublayer generator according to the given description.
83
+
84
+ Consider the details and requirements mentioned in the description to create the appropriate Sublayer::Generator.
85
+ <users_description>
86
+ #{@description}
87
+ </users_description>
88
+
89
+ Take a deep breath and reflect on each aspect of the description before proceeding with the generation.
90
+ PROMPT
91
+ end
92
+
93
+ def example_generator
94
+ File.read(File.join(File.dirname(__FILE__), "example_generator.rb"))
95
+ end
96
+ end
@@ -0,0 +1,116 @@
1
+ module Sublayer
2
+ module Commands
3
+ class NewProject < Thor::Group
4
+ include Thor::Actions
5
+
6
+ argument :project_name, type: :string, desc: "The name of your project"
7
+
8
+ class_option :template, type: :string, desc: "Type of project (CLI or QuickScript)", aliases: :t
9
+ class_option :provider, type: :string, desc: "AI provider (OpenAI, Claude, or Gemini)", aliases: :p
10
+ class_option :model, type: :string, desc: "AI model name to use (e.g. gpt-4o, claude-3-haiku-20240307, gemini-1.5-flash-latest)", aliases: :m
11
+
12
+ def sublayer_version
13
+ Sublayer::VERSION
14
+ end
15
+
16
+ def self.source_root
17
+ File.dirname(__FILE__)
18
+ end
19
+
20
+ def ask_for_project_details
21
+ puts options[:template]
22
+ @project_template = options[:template] || ask("Select a project template:", default: "CLI", limited_to: %w[CLI QuickScript])
23
+ @ai_provider = options[:provider] || ask("Select an AI provider:", default: "OpenAI", limited_to: %w[OpenAI Claude Gemini])
24
+ @ai_model = options[:model] || select_ai_model
25
+ end
26
+
27
+ def create_project_directory
28
+ say "Creating project directory", :green
29
+ empty_directory project_name
30
+ end
31
+
32
+ def copy_template_files
33
+ say "Copying template files", :green
34
+ template_dir = @project_template == "CLI" ? "cli" : "quick_script"
35
+ directory "../templates/#{template_dir}", project_name
36
+ empty_directory File.join(project_name, "log") if @project_template =="CLI"
37
+ end
38
+
39
+ def generate_config_file
40
+ say "Generating configuration", :green
41
+
42
+ config = {
43
+ project_name: project_name,
44
+ project_template: @project_template,
45
+ ai_provider: @ai_provider,
46
+ ai_model: @ai_model
47
+ }
48
+
49
+ if @project_template == "CLI"
50
+ create_file File.join(project_name, "lib", project_name, "config", "sublayer.yml"), YAML.dump(config)
51
+ else
52
+ append_to_file File.join(project_name, "#{project_name}.rb") do
53
+ <<~CONFIG
54
+ Sublayer.configuration.ai_provider = Sublayer::Providers::#{config[:ai_provider]}
55
+ Sublayer.configuration.ai_model = "#{config[:ai_model]}"
56
+ CONFIG
57
+ end
58
+ end
59
+ end
60
+
61
+ def finalize_project
62
+ say "Finalizing project", :green
63
+ inside(project_name) do
64
+ if @project_template == "CLI"
65
+ chmod("bin/#{project_name}", "+x")
66
+ run("bundle install") if yes?("Install gems?")
67
+ else
68
+ append_to_file "#{project_name}.rb" do
69
+ <<~INSTRUCTIONS
70
+ puts "Welcome to your quick Sublayer script!"
71
+ puts "To get started, create some generators, actions, or agents in their respective directories and call them here"
72
+ puts "For more information, visit https://docs.sublayer.com"
73
+ INSTRUCTIONS
74
+ end
75
+ end
76
+
77
+ run("git init") if yes?("Initialize a git repository?")
78
+ end
79
+ end
80
+
81
+ def print_next_steps
82
+ say "\nSublayer project '#{project_name}' created successfully!", :green
83
+ say "To get started, run:"
84
+ say " cd #{project_name}"
85
+ if @project_template == "CLI"
86
+ say " ./bin/#{project_name}"
87
+ else
88
+ say " ruby #{project_name}.rb"
89
+ end
90
+ end
91
+
92
+ private
93
+
94
+ def select_ai_model
95
+ case @ai_provider
96
+ when "OpenAI"
97
+ ask("Which OpenAI model would you like to use?", default: "gpt-4o", limited_to: %w[gpt-4o gpt-4o-mini gpt-4-turbo gpt-3.5-turbo])
98
+ when "Claude"
99
+ ask("Which Anthropic model would you like to use?", default: "claude-3-5-sonnet-20240620", limited_to: %w[claude-3-5-sonnet-20240620 claude-3-opus-20240620 claude-3-haiku-20240307])
100
+ when "Gemini"
101
+ ask("Which Google model would you like to use?", default: "gemini-1.5-flash-latest", limited_to: %w[gemini-1.5-flash-latest gemini-1.5-pro-latest])
102
+ end
103
+ end
104
+
105
+ def rename_project_name_directory
106
+ old_path = File.join(project_name, "lib", "PROJECT_NAME")
107
+ new_path = File.join(project_name, "lib", project_name.gsub("-", "_").downcase)
108
+
109
+ if File.directory?(old_path)
110
+ say "Renaming project directory", :green
111
+ FileUtils.mv(old_path, new_path)
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end