├── .gitignore ├── CLAUDE.md ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── example-components ├── layout.md └── style.md ├── exe └── monkeyspaw ├── lib ├── monkeyspaw.rb └── monkeyspaw │ ├── application.rb │ ├── cache_manager.rb │ ├── config.rb │ ├── generators │ └── html_from_description_generator.rb │ ├── prompt_manager.rb │ ├── router.rb │ ├── server.rb │ └── version.rb ├── monkeyspaw.gemspec ├── sample_app ├── README.md ├── app.rb ├── components │ ├── layout.md │ └── style.md └── wishes │ ├── best-practices.md │ ├── components.md │ ├── examples.md │ ├── getting-started.md │ ├── how-it-works.md │ └── index.md └── spec ├── monkeyspaw ├── application_spec.rb ├── cache_manager_spec.rb ├── config_spec.rb ├── generators │ └── html_from_description_generator_spec.rb ├── prompt_manager_spec.rb └── version_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # CLAUDE.md 2 | 3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 | 5 | ## Build Commands 6 | - `bundle install` - Install gem dependencies 7 | - `bundle exec rake build` - Build the gem 8 | - `bundle exec rake spec` - Run all tests 9 | - `bundle exec rspec spec/monkeyspaw/config_spec.rb` - Run a single test file 10 | - `bundle exec rspec spec/monkeyspaw/config_spec.rb:12` - Run a specific test 11 | 12 | ## Linting & Style 13 | - Ruby version: >= 3.0 14 | - Follow standard Ruby style conventions: 15 | - Use 2 spaces for indentation 16 | - Use snake_case for methods and variables 17 | - Use CamelCase for classes and modules 18 | - Place requires at the top of files 19 | - Group related methods together 20 | - Add appropriate documentation for public methods 21 | - Use meaningful variable names 22 | - Prefer Ruby idioms (e.g., attr_accessor, blocks) 23 | - Handle errors appropriately with begin/rescue where necessary 24 | 25 | ## Project Overview 26 | MonkeysPaw is a prompt-driven web framework for Ruby that uses AI to generate web content based on prompts. It's designed to allow users to express web development intentions through prompts rather than code. 27 | 28 | ## Library Usage 29 | Users of this gem will: 30 | - Create prompt files in their project's `wishes/` directory 31 | - Define components in the `components/` directory 32 | - Initialize the framework with `MonkeysPaw.pick_up!` 33 | - Configure settings with `MonkeysPaw.configure` 34 | - Access generated content through the web server (default: http://localhost:1337) -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gemspec 3 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | monkeyspaw (0.0.3) 5 | puma (~> 6.6) 6 | rack (~> 3.1) 7 | sublayer (~> 0.2.8) 8 | 9 | GEM 10 | remote: https://rubygems.org/ 11 | specs: 12 | activesupport (8.0.2) 13 | base64 14 | benchmark (>= 0.3) 15 | bigdecimal 16 | concurrent-ruby (~> 1.0, >= 1.3.1) 17 | connection_pool (>= 2.2.5) 18 | drb 19 | i18n (>= 1.6, < 2) 20 | logger (>= 1.4.2) 21 | minitest (>= 5.1) 22 | securerandom (>= 0.3) 23 | tzinfo (~> 2.0, >= 2.0.5) 24 | uri (>= 0.13.1) 25 | base64 (0.2.0) 26 | benchmark (0.4.0) 27 | bigdecimal (3.1.9) 28 | coderay (1.1.3) 29 | concurrent-ruby (1.3.5) 30 | connection_pool (2.5.0) 31 | csv (3.3.3) 32 | diff-lcs (1.6.1) 33 | drb (2.2.1) 34 | event_stream_parser (1.0.0) 35 | faraday (2.12.2) 36 | faraday-net_http (>= 2.0, < 3.5) 37 | json 38 | logger 39 | faraday-multipart (1.1.0) 40 | multipart-post (~> 2.0) 41 | faraday-net_http (3.4.0) 42 | net-http (>= 0.5.0) 43 | ffi (1.17.1) 44 | ffi (1.17.1-aarch64-linux-gnu) 45 | ffi (1.17.1-aarch64-linux-musl) 46 | ffi (1.17.1-arm-linux-gnu) 47 | ffi (1.17.1-arm-linux-musl) 48 | ffi (1.17.1-arm64-darwin) 49 | ffi (1.17.1-x86-linux-gnu) 50 | ffi (1.17.1-x86-linux-musl) 51 | ffi (1.17.1-x86_64-darwin) 52 | ffi (1.17.1-x86_64-linux-gnu) 53 | ffi (1.17.1-x86_64-linux-musl) 54 | httparty (0.23.1) 55 | csv 56 | mini_mime (>= 1.0.0) 57 | multi_xml (>= 0.5.2) 58 | i18n (1.14.7) 59 | concurrent-ruby (~> 1.0) 60 | json (2.10.2) 61 | listen (3.9.0) 62 | rb-fsevent (~> 0.10, >= 0.10.3) 63 | rb-inotify (~> 0.9, >= 0.9.10) 64 | logger (1.7.0) 65 | method_source (1.1.0) 66 | mini_mime (1.1.5) 67 | minitest (5.25.5) 68 | multi_xml (0.7.1) 69 | bigdecimal (~> 3.1) 70 | multipart-post (2.4.1) 71 | net-http (0.6.0) 72 | uri 73 | nio4r (2.7.4) 74 | pry (0.15.2) 75 | coderay (~> 1.1) 76 | method_source (~> 1.0) 77 | puma (6.6.0) 78 | nio4r (~> 2.0) 79 | rack (3.1.12) 80 | rake (13.2.1) 81 | rb-fsevent (0.11.2) 82 | rb-inotify (0.11.1) 83 | ffi (~> 1.0) 84 | rspec (3.13.0) 85 | rspec-core (~> 3.13.0) 86 | rspec-expectations (~> 3.13.0) 87 | rspec-mocks (~> 3.13.0) 88 | rspec-core (3.13.3) 89 | rspec-support (~> 3.13.0) 90 | rspec-expectations (3.13.3) 91 | diff-lcs (>= 1.2.0, < 2.0) 92 | rspec-support (~> 3.13.0) 93 | rspec-mocks (3.13.2) 94 | diff-lcs (>= 1.2.0, < 2.0) 95 | rspec-support (~> 3.13.0) 96 | rspec-support (3.13.2) 97 | ruby-openai (8.1.0) 98 | event_stream_parser (>= 0.3.0, < 2.0.0) 99 | faraday (>= 1) 100 | faraday-multipart (>= 1) 101 | securerandom (0.4.1) 102 | sublayer (0.2.9) 103 | activesupport 104 | httparty 105 | listen 106 | ruby-openai 107 | thor 108 | zeitwerk 109 | thor (1.3.2) 110 | tzinfo (2.0.6) 111 | concurrent-ruby (~> 1.0) 112 | uri (1.0.3) 113 | zeitwerk (2.7.2) 114 | 115 | PLATFORMS 116 | aarch64-linux-gnu 117 | aarch64-linux-musl 118 | arm-linux-gnu 119 | arm-linux-musl 120 | arm64-darwin 121 | ruby 122 | x86-linux-gnu 123 | x86-linux-musl 124 | x86_64-darwin 125 | x86_64-linux-gnu 126 | x86_64-linux-musl 127 | 128 | DEPENDENCIES 129 | bundler (~> 2.6) 130 | monkeyspaw! 131 | pry (~> 0.15) 132 | rake (~> 13.2) 133 | rspec (~> 3.13) 134 | 135 | BUNDLED WITH 136 | 2.6.7 137 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MonkeysPaw 🐒✋ 2 | 3 | > *Be careful what you wish for...* 4 | 5 | A prompt-driven web framework for Ruby that grants your web development wishes through the power of AI. Like the legendary monkey's paw, it will fulfill your requests exactly as stated—for better or worse. 6 | 7 | ## The Curse and the Gift 8 | 9 | MonkeysPaw removes traditional web development constraints by allowing you to express your intentions through prompts rather than code. But remember, with great power comes the responsibility to be precise. Your words shape reality here. 10 | 11 | The quality of what you receive depends entirely on how carefully you phrase your wishes. 12 | 13 | ## Installation 14 | 15 | Summon the paw into your application: 16 | 17 | ```ruby 18 | gem 'monkeyspaw' 19 | ``` 20 | 21 | Then invoke the ritual: 22 | 23 | ```bash 24 | $ bundle install 25 | ``` 26 | 27 | Or bind it directly: 28 | 29 | ```bash 30 | $ gem install monkeyspaw 31 | ``` 32 | 33 | ## AI Provider 34 | 35 | MonkeysPaw is currently set up to only commune with Gemini by default. Other AI 36 | entities will be supported shortly. 37 | 38 | To use Gemini set up your environment variable: 39 | 40 | MacOS/Linux: 41 | ```bash 42 | export GEMINI_API_KEY=your_gemini_api_key 43 | ``` 44 | 45 | Windows: 46 | 47 | ```bash 48 | Set-Item -Path env:GEMINI_API_KEY -Value "YourKeyHere" 49 | ``` 50 | 51 | ## Making Your First Wish 52 | 53 | Create a simple manifestation with just a few incantations: 54 | 55 | ```ruby 56 | require 'monkeyspaw' 57 | 58 | # Make your wish come true 59 | MonkeysPaw.pick_up! 60 | ``` 61 | 62 | That's it! MonkeysPaw will interpret your desires by: 63 | 64 | 1. Reading prompt files from the `wishes/` directory 65 | 2. Channeling the AI entity to manifest your content 66 | 3. Summoning a web server (default: http://localhost:1337) 67 | 68 | ## Structuring Your Desires 69 | 70 | MonkeysPaw follows arcane file-based conventions: 71 | 72 | ``` 73 | your-project/ 74 | ├── wishes/ # Your primary desires 75 | │ ├── index.md # Home page (/) 76 | │ ├── about.md # About page (/about) 77 | │ └── curses/ 78 | │ ├── 01-wish.md # First curse (/curses/01-wish) 79 | │ └── 02-consequence.md # Second curse (/curses/02-consequence) 80 | ├── components/ # Recurring incantations 81 | │ ├── layout.md # The container of all things 82 | │ └── navigation.md # Pathways between realms 83 | ├── assets/ # Physical manifestations 84 | │ └── images/ 85 | │ └── paw-mark.png 86 | └── app.rb # Where the magic begins 87 | ``` 88 | 89 | ## Crafting Effective Wishes (Prompts) 90 | 91 | The art of prompt crafting is delicate. Be specific, or face the consequences of ambiguity: 92 | 93 | ```markdown 94 | # Page: Introduction to MonkeysPaw 95 | 96 | ## Content Instructions: 97 | Create an engaging introduction to a framework that grants web development wishes 98 | through AI prompts. Emphasize the need for precise language. 99 | 100 | ## Key Points: 101 | - The dual nature of wish fulfillment: power and peril 102 | - How imprecise wishes lead to unexpected outcomes 103 | - Examples of well-crafted vs. poorly-crafted prompts 104 | 105 | ## Style: 106 | - Mysterious yet professional tone 107 | - Include a cautionary opening statement 108 | - End with advice for the wish-maker 109 | 110 | ## Visual Elements: 111 | - A clear warning header 112 | - Short, foreboding paragraphs 113 | - A callout box with "Do's and Don'ts" for prompt crafting 114 | ``` 115 | 116 | ## Controlling Your Fate 117 | 118 | Configure the terms of your pact: 119 | 120 | ```ruby 121 | MonkeysPaw.configure do |config| 122 | config.port = 4567 123 | config.host = 'localhost' 124 | end 125 | ``` 126 | 127 | ## Mystical Entities Available for Summoning (TODO) 128 | 129 | MonkeysPaw can channel various AI entities: 130 | 131 | ```ruby 132 | # The Gemini Spirit 133 | MonkeysPaw.use :gemini, model: :gemini_2_0_flash 134 | 135 | # The OpenAI Oracle 136 | MonkeysPaw.use :openai, model: :gpt_4 137 | 138 | # The Anthropic Sage 139 | MonkeysPaw.use :anthropic, model: :claude_3_opus 140 | 141 | # The Mistral Wind 142 | MonkeysPaw.use :mistral, model: :mistral_large 143 | ``` 144 | 145 | ## Words of Caution 146 | 147 | 1. **Be Precise**: Ambiguity is your enemy. The more specific your wishes, the fewer surprises you'll face. 148 | 149 | 2. **Test Your Wishes**: Always preview the manifestation of your wishes before sharing them with others. 150 | 151 | 3. **Expect Variations**: Even with careful crafting, each wish may manifest slightly differently on each request. 152 | 153 | 4. **Mind Your Resources**: Frequent wishes may deplete your API quota. Use caching wisely. 154 | 155 | ## The Binding Contract (License) 156 | 157 | MonkeysPaw is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 158 | 159 | --- 160 | 161 | *"The paw grants exactly what you request, not what you intend."* 162 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task default: :spec -------------------------------------------------------------------------------- /example-components/layout.md: -------------------------------------------------------------------------------- 1 | # Layout Component 2 | 3 | Create a simple and clean layout with: 4 | - A responsive header at the top with the page title 5 | - A main content area that contains the primary content 6 | - A footer with copyright information 7 | - Navigation links if appropriate for the context 8 | 9 | The layout should be responsive and work well on mobile and desktop devices. 10 | -------------------------------------------------------------------------------- /example-components/style.md: -------------------------------------------------------------------------------- 1 | # Style Component 2 | 3 | Apply these styling guidelines: 4 | - Use a clean, minimalist design with ample white space 5 | - Color scheme: soft blues (#4A90E2) for primary elements, light gray (#F5F5F5) for backgrounds 6 | - Typography: Sans-serif fonts (system-ui) with good readability 7 | - Interactive elements should have subtle hover effects 8 | - Content should be well-structured with clear hierarchy 9 | - Ensure good contrast for accessibility 10 | -------------------------------------------------------------------------------- /exe/monkeyspaw: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "monkeyspaw" 4 | require "optparse" 5 | -------------------------------------------------------------------------------- /lib/monkeyspaw.rb: -------------------------------------------------------------------------------- 1 | require "sublayer" 2 | 3 | require "monkeyspaw/version" 4 | require "monkeyspaw/application" 5 | require "monkeyspaw/config" 6 | require "monkeyspaw/server" 7 | require "monkeyspaw/router" 8 | require "monkeyspaw/prompt_manager" 9 | require "monkeyspaw/cache_manager" 10 | 11 | Dir[File.expand_path("../monkeyspaw/generators/*.rb", __FILE__)].each { |f| require f } 12 | 13 | module MonkeysPaw 14 | Sublayer.configuration.ai_provider = Sublayer::Providers::Gemini 15 | Sublayer.configuration.ai_model = "gemini-2.0-flash" 16 | 17 | class << self 18 | def application 19 | @application ||= Application.new 20 | end 21 | 22 | def configure(&block) 23 | application.configure(&block) 24 | end 25 | 26 | def root 27 | application.root 28 | end 29 | 30 | def pages_dir 31 | application.pages_dir 32 | end 33 | 34 | def components_dir 35 | application.components_dir 36 | end 37 | 38 | def assets_dir 39 | application.assets_dir 40 | end 41 | 42 | def pick_up!(**options) 43 | application.pick_up!(**options) 44 | end 45 | 46 | def root=(path) 47 | application.root = path 48 | end 49 | end 50 | end 51 | 52 | if $0 == __FILE__ 53 | MonkeysPaw.pick_up! 54 | end 55 | -------------------------------------------------------------------------------- /lib/monkeyspaw/application.rb: -------------------------------------------------------------------------------- 1 | require "pathname" 2 | 3 | module MonkeysPaw 4 | class Application 5 | attr_reader :config 6 | attr_accessor :root, :router 7 | 8 | def initialize 9 | @config = Config.new 10 | @root = Pathname.new(Dir.pwd) 11 | @server = nil 12 | @router = nil 13 | @prompt_manager = nil 14 | @cache_manager = nil 15 | end 16 | 17 | def prompt_manager 18 | @prompt_manager ||= PromptManager.new(self) 19 | end 20 | 21 | def cache_manager 22 | @cache_manager ||= CacheManager.new(self) 23 | end 24 | 25 | def configure 26 | yield config if block_given? 27 | self 28 | end 29 | 30 | def use(provider, **options) 31 | puts "Use called" 32 | end 33 | 34 | def pick_up!(**options) 35 | setup_directories 36 | 37 | @router ||= Router.new(self) 38 | 39 | # Initialize prompt manager 40 | prompt_manager 41 | 42 | @server ||= Server.new(self, **options) 43 | @server.start 44 | end 45 | 46 | def pages_dir 47 | root.join(config.pages_dir || 'wishes') 48 | end 49 | 50 | def components_dir 51 | root.join(config.components_dir || 'components') 52 | end 53 | 54 | def assets_dir 55 | root.join(config.assets_dir || 'assets') 56 | end 57 | 58 | def get_page_prompt(path) 59 | @prompt_manager.get_page_prompt(path) 60 | end 61 | 62 | def get_component_prompt(path) 63 | @prompt_manager.get_component_prompt(path) 64 | end 65 | 66 | private 67 | 68 | def setup_directories 69 | [pages_dir, components_dir, assets_dir].each do |dir| 70 | dir.mkpath unless dir.exist? 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/monkeyspaw/cache_manager.rb: -------------------------------------------------------------------------------- 1 | require 'digest' 2 | require 'fileutils' 3 | 4 | module MonkeysPaw 5 | class CacheManager 6 | attr_reader :app, :cache_dir 7 | 8 | def initialize(app) 9 | @app = app 10 | @cache_dir = app.root.join('.monkeyspaw_cache') 11 | @cache_dir.mkpath unless @cache_dir.exist? 12 | end 13 | 14 | def get_cached_page(path, prompt_file) 15 | cache_key = generate_cache_key(path, prompt_file) 16 | cache_file = cache_dir.join("#{cache_key}.html") 17 | 18 | return nil unless cache_file.exist? 19 | 20 | # If the cache exists, check if any dependencies have changed 21 | if dependencies_changed?(path, prompt_file) 22 | puts "Dependencies changed for #{path}, regenerating..." 23 | return nil 24 | end 25 | 26 | puts "Using cached version of #{path}" 27 | File.read(cache_file) 28 | end 29 | 30 | def cache_page(path, prompt_file, html_content) 31 | cache_key = generate_cache_key(path, prompt_file) 32 | cache_file = cache_dir.join("#{cache_key}.html") 33 | 34 | # Save the HTML content 35 | File.write(cache_file, html_content) 36 | 37 | # Save dependency information 38 | save_dependencies(path, prompt_file) 39 | 40 | puts "Cached page #{path}" 41 | end 42 | 43 | def clear_cache 44 | FileUtils.rm_rf(cache_dir) 45 | cache_dir.mkpath 46 | puts "Cache cleared" 47 | end 48 | 49 | private 50 | 51 | def generate_cache_key(path, prompt_file) 52 | # Create a unique but readable key based on the path 53 | safe_path = path.gsub(/[^a-zA-Z0-9_-]/, '_') 54 | safe_path = 'index' if safe_path == '_' 55 | safe_path 56 | end 57 | 58 | def dependencies_changed?(path, prompt_file) 59 | deps_file = cache_dir.join("#{generate_cache_key(path, prompt_file)}.deps") 60 | return true unless deps_file.exist? 61 | 62 | current_deps = get_current_dependencies(path, prompt_file) 63 | stored_deps = JSON.parse(File.read(deps_file)) 64 | 65 | # Compare the stored and current dependencies 66 | current_deps != stored_deps 67 | end 68 | 69 | def get_current_dependencies(path, prompt_file) 70 | deps = {} 71 | 72 | # Page content dependency 73 | deps["page"] = file_signature(prompt_file) 74 | 75 | # Layout component dependency 76 | layout_file = app.prompt_manager.get_component_prompt('layout') 77 | deps["layout"] = layout_file ? file_signature(layout_file) : app.prompt_manager.default_layout_prompt 78 | 79 | # Style component dependency 80 | style_file = app.prompt_manager.get_component_prompt('style') 81 | deps["style"] = style_file ? file_signature(style_file) : app.prompt_manager.default_style_prompt 82 | 83 | deps 84 | end 85 | 86 | def save_dependencies(path, prompt_file) 87 | deps_file = cache_dir.join("#{generate_cache_key(path, prompt_file)}.deps") 88 | deps = get_current_dependencies(path, prompt_file) 89 | 90 | File.write(deps_file, JSON.generate(deps)) 91 | end 92 | 93 | def file_signature(file_path) 94 | content = File.read(file_path) 95 | Digest::MD5.hexdigest(content) 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /lib/monkeyspaw/config.rb: -------------------------------------------------------------------------------- 1 | module MonkeysPaw 2 | class Config 3 | attr_accessor :provider, 4 | :model, 5 | :pages_dir, 6 | :components_dir, 7 | :assets_dir, 8 | :port, 9 | :host, 10 | :caching_enabled 11 | 12 | def initialize 13 | @provider = nil 14 | @model = nil 15 | @pages_dir = 'wishes' 16 | @components_dir = 'components' 17 | @assets_dir = 'assets' 18 | @port = 1337 19 | @host = "localhost" 20 | @caching_enabled = true 21 | end 22 | 23 | def to_h 24 | instance_variables.each_with_object({}) do |var, hash| 25 | hash[var.to_s.delete('@').to_sym] = instance_variable_get(var) 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/monkeyspaw/generators/html_from_description_generator.rb: -------------------------------------------------------------------------------- 1 | module MonkeysPaw 2 | class HtmlFromDescriptionGenerator < Sublayer::Generators::Base 3 | llm_output_adapter type: :single_string, 4 | name: "html_code", 5 | description: "The generated HTML code for the web page" 6 | 7 | def initialize(description:, layout_prompt: nil, style_prompt: nil) 8 | @description = description 9 | @layout_prompt = layout_prompt 10 | @style_prompt = style_prompt 11 | end 12 | 13 | def generate 14 | max_retries = 3 15 | retry_count = 0 16 | 17 | begin 18 | super 19 | rescue => e 20 | retry_count += 1 21 | if retry_count <= max_retries 22 | puts "Generator attempt #{retry_count}/#{max_retries} failed: #{e.message}. Retrying..." 23 | sleep(1) # Add a small delay between retries 24 | retry 25 | else 26 | puts "Generator failed after #{max_retries} attempts. Last error: #{e.message}" 27 | raise 28 | end 29 | end 30 | end 31 | 32 | def prompt 33 | layout_section = "" 34 | style_section = "" 35 | 36 | if @layout_prompt 37 | layout_section = <<~LAYOUT_SECTION 38 | ## Layout Requirements: 39 | #{@layout_prompt} 40 | LAYOUT_SECTION 41 | end 42 | 43 | if @style_prompt 44 | style_section = <<~STYLE_SECTION 45 | ## Style Guidelines: 46 | #{@style_prompt} 47 | STYLE_SECTION 48 | end 49 | 50 | <<~PROMPT 51 | You are an expert HTML developer. 52 | 53 | You are tasked with writing HTML code based on the following description: 54 | 55 | #{@description} 56 | 57 | #{layout_section} 58 | #{style_section} 59 | 60 | Take a deep breath and think step by step before you start coding. Ensure the HTML is well-formed and valid. 61 | 62 | The three components work together as follows: 63 | 1. The page description provides the specific content and features this page should have 64 | 2. The layout requirements define the structural organization and placement of elements 65 | 3. The style guidelines inform the visual presentation and aesthetic choices 66 | 67 | Harmonize these elements to create a cohesive and effective web page that fulfills the intended purpose. 68 | PROMPT 69 | end 70 | end 71 | end -------------------------------------------------------------------------------- /lib/monkeyspaw/prompt_manager.rb: -------------------------------------------------------------------------------- 1 | module MonkeysPaw 2 | class PromptManager 3 | attr_reader :app 4 | attr_accessor :layout_prompt, :style_prompt 5 | 6 | def initialize(app) 7 | @app = app 8 | load_default_components 9 | end 10 | 11 | def get_page_prompt(path) 12 | path = "/" if path.empty? 13 | app.router.get_file(path) 14 | end 15 | 16 | def get_component_prompt(component_name) 17 | component_file = app.components_dir.join("#{component_name}.md") 18 | component_file if component_file.exist? 19 | end 20 | 21 | def load_layout_prompt 22 | layout_file = get_component_prompt('layout') 23 | if layout_file 24 | @layout_prompt = File.read(layout_file) 25 | else 26 | @layout_prompt = default_layout_prompt 27 | end 28 | end 29 | 30 | def load_style_prompt 31 | style_file = get_component_prompt('style') 32 | if style_file 33 | @style_prompt = File.read(style_file) 34 | else 35 | @style_prompt = default_style_prompt 36 | end 37 | end 38 | 39 | def load_default_components 40 | load_layout_prompt 41 | load_style_prompt 42 | end 43 | 44 | def default_layout_prompt 45 | <<~LAYOUT 46 | # Layout Component 47 | 48 | Create a simple and clean layout with: 49 | - A responsive header at the top with the page title 50 | - A main content area that contains the primary content 51 | - A footer with copyright information 52 | - Navigation links if appropriate for the context 53 | 54 | The layout should be responsive and work well on mobile and desktop devices. 55 | LAYOUT 56 | end 57 | 58 | def default_style_prompt 59 | <<~STYLE 60 | # Style Component 61 | 62 | Apply these styling guidelines: 63 | - Use a clean, minimalist design with ample white space 64 | - Color scheme: soft blues (#4A90E2) for primary elements, light gray (#F5F5F5) for backgrounds 65 | - Typography: Sans-serif fonts (system-ui) with good readability 66 | - Interactive elements should have subtle hover effects 67 | - Content should be well-structured with clear hierarchy 68 | - Ensure good contrast for accessibility 69 | STYLE 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /lib/monkeyspaw/router.rb: -------------------------------------------------------------------------------- 1 | module MonkeysPaw 2 | class Router 3 | attr_reader :app, :routes 4 | 5 | def initialize(app) 6 | @app = app 7 | @routes = {} 8 | scan_pages 9 | end 10 | 11 | def scan_pages 12 | @routes = {} 13 | pages_dir = app.pages_dir 14 | 15 | return unless pages_dir.exist? 16 | 17 | page_files = Dir.glob(pages_dir.join('**', '*.md')) 18 | 19 | page_files.each do |file| 20 | rel_path = Pathname.new(file).relative_path_from(pages_dir) 21 | route = '/' + rel_path.to_s.sub(/\.md$/, '') 22 | 23 | route = route.sub(/\/index$/, '/') 24 | 25 | @routes[route] = file 26 | end 27 | 28 | @routes = @routes.sort_by { |route, file| file }.to_h 29 | end 30 | 31 | def get_file(path) 32 | @routes[path] 33 | end 34 | 35 | def next_page(current_path) 36 | keys = @routes.keys 37 | current_index = keys.find_index(current_path) 38 | return nil unless current_index 39 | keys[current_index + 1] 40 | end 41 | 42 | def prev_page(current_path) 43 | keys = @routes.keys 44 | current_index = keys.find_index(current_path) 45 | return nil unless current_index || current_index == 0 46 | keys[current_index - 1] 47 | end 48 | 49 | def get_route_for_file(file) 50 | @routes.key(file) 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/monkeyspaw/server.rb: -------------------------------------------------------------------------------- 1 | require "rack" 2 | require "rack/handler/puma" 3 | 4 | module MonkeysPaw 5 | class Server 6 | attr_reader :app 7 | 8 | def initialize(app, **options) 9 | @app = app 10 | @options = options 11 | @rack_app = build_rack_app 12 | end 13 | 14 | def start 15 | host = @options[:host] || app.config.host || 'localhost' 16 | port = @options[:port] || app.config.port || 1337 17 | 18 | puts "You pick up the monkey's paw. Be careful what you wish for on http://#{host}:#{port}" 19 | 20 | setup_signal_handlers 21 | 22 | Rack::Handler::Puma.run(@rack_app, Host: host, Port: port, Silent: true, Threads: "0:16", workers: 0) 23 | end 24 | 25 | def shutdown 26 | puts "Enough! You throw the paw away. Hopefully there were no unintended consequences. Mua ha ha ha ha!" 27 | exit 28 | end 29 | 30 | def generate_page(path, prompt_file) 31 | if app.config.caching_enabled 32 | # Try to get from cache first 33 | cached_content = app.cache_manager.get_cached_page(path, prompt_file) 34 | return cached_content if cached_content 35 | end 36 | 37 | # Cache miss or caching disabled, generate the page 38 | # Reload layout and style prompts to pick up changes 39 | app.prompt_manager.load_default_components 40 | layout_prompt = app.prompt_manager.layout_prompt 41 | style_prompt = app.prompt_manager.style_prompt 42 | 43 | html_content = HtmlFromDescriptionGenerator.new( 44 | description: File.read(prompt_file), 45 | layout_prompt: layout_prompt, 46 | style_prompt: style_prompt 47 | ).generate 48 | 49 | # Store in cache if enabled 50 | if app.config.caching_enabled 51 | app.cache_manager.cache_page(path, prompt_file, html_content) 52 | end 53 | 54 | html_content 55 | end 56 | 57 | def generate_error_page(message) 58 | "

Error

#{message}

" 59 | end 60 | 61 | private 62 | 63 | def setup_signal_handlers 64 | [:INT, :TERM].each do |signal| 65 | trap(signal) { shutdown } 66 | end 67 | end 68 | 69 | def build_rack_app 70 | server = self 71 | 72 | Rack::Builder.new do 73 | use Rack::Static, 74 | urls: ["/assets"], 75 | root: server.app.root.to_s 76 | 77 | run lambda { |env| 78 | request = Rack::Request.new(env) 79 | 80 | path = request.path 81 | path = "/" if path.empty? 82 | 83 | if request.params["preload"] 84 | prompt_file = server.app.get_page_prompt(path) 85 | Thread.new { server.generate_page(path, prompt_file) } if prompt_file 86 | 87 | return [204, {}, []] 88 | end 89 | 90 | prompt_file = server.app.get_page_prompt(path) 91 | 92 | if prompt_file 93 | html = server.generate_page(path, prompt_file) 94 | 95 | next_path = server.app.router.next_page(path) 96 | if next_path 97 | next_prompt = server.app.get_page_prompt(next_path) 98 | Thread.new { server.generate_page(next_path, next_prompt) } if next_prompt 99 | end 100 | 101 | [200, { "Content-Type" => "text/html"}, [html]] 102 | else 103 | [404, {"Content-Type" => "text/html"}, [server.generate_error_page("Page not found: #{path}")]] 104 | end 105 | } 106 | end 107 | end 108 | 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /lib/monkeyspaw/version.rb: -------------------------------------------------------------------------------- 1 | module MonkeysPaw 2 | VERSION = "0.0.3" 3 | end 4 | -------------------------------------------------------------------------------- /monkeyspaw.gemspec: -------------------------------------------------------------------------------- 1 | require_relative "lib/monkeyspaw/version" 2 | 3 | Gem::Specification.new do |s| 4 | s.name = "monkeyspaw" 5 | s.version = MonkeysPaw::VERSION 6 | s.authors = ["Scott Werner"] 7 | s.email = "scott@sublayer.com" 8 | 9 | s.summary = "A prompt-driven web framework for Ruby - be careful what you wish for" 10 | s.description = "MonkeysPaw is a micro web framework that grants your wishes through AI prompts, but as with any wish-granting entity, the clarity of your request determines what you receive. Craft your prompts with care, lest your website manifest in unexpected ways." 11 | s.homepage = "https://github.com/sublayerapp/monkeyspaw" 12 | s.license = "MIT" 13 | s.required_ruby_version = ">= 3.0" 14 | 15 | s.metadata['homepage_uri'] = s.homepage 16 | 17 | s.files = Dir.glob(%w[lib/**/*.rb exe/monkeyspaw bin/monkeyspaw README.md LICENSE]) 18 | s.bindir = "exe" 19 | s.executables = ["monkeyspaw"] 20 | s.require_paths = ["lib"] 21 | 22 | s.add_dependency "rack", "~> 3.1" 23 | s.add_dependency "puma", "~> 6.6" 24 | s.add_dependency "sublayer", "~> 0.2.8" 25 | 26 | s.add_development_dependency "bundler", "~> 2.6" 27 | s.add_development_dependency "rake", "~> 13.2" 28 | s.add_development_dependency "rspec", "~> 3.13" 29 | s.add_development_dependency "pry", "~> 0.15" 30 | end 31 | -------------------------------------------------------------------------------- /sample_app/README.md: -------------------------------------------------------------------------------- 1 | # MonkeysPaw Sample Website 2 | 3 | This is a sample website for the MonkeysPaw gem, built using MonkeysPaw itself! 4 | 5 | ## Meta Example 6 | 7 | This sample app demonstrates the power and flexibility of MonkeysPaw by creating a website *about* MonkeysPaw *using* MonkeysPaw. It's a perfect example of "wish-driven development" in action. 8 | 9 | ## Running the Sample 10 | 11 | To run this sample website: 12 | 13 | 1. Make sure you have Ruby 3.0+ installed 14 | 2. Install the MonkeysPaw gem if you haven't already: 15 | ``` 16 | gem install monkeyspaw 17 | ``` 18 | 19 | 3. Run the application: 20 | ``` 21 | ruby app.rb 22 | ``` 23 | 24 | 4. Visit [http://localhost:4567](http://localhost:4567) in your browser 25 | 26 | ## Performance with Caching 27 | 28 | This sample uses MonkeysPaw's caching system to improve performance. Pages are generated only once and then cached. They will be regenerated only when: 29 | 30 | - The page content (wish file) changes 31 | - The layout component changes 32 | - The style component changes 33 | 34 | To force regeneration of all pages (clear the cache), run: 35 | ``` 36 | ruby app.rb --clear-cache 37 | ``` 38 | 39 | ## Directory Structure 40 | 41 | - `wishes/` - Contains the page descriptions for each page of the site 42 | - `components/` - Contains the layout and style components 43 | - `app.rb` - The application entry point 44 | 45 | ## Pages 46 | 47 | - Home (`wishes/index.md`) - The main landing page 48 | - How It Works (`wishes/how-it-works.md`) - Explains the MonkeysPaw concept 49 | - Getting Started (`wishes/getting-started.md`) - A guide for new users 50 | 51 | ## Components 52 | 53 | - Layout (`components/layout.md`) - Defines the structural organization of the site 54 | - Style (`components/style.md`) - Defines the visual presentation and aesthetics 55 | 56 | ## Learning from this Example 57 | 58 | Study the wish files to learn effective ways to structure your own prompts. Pay special attention to: 59 | 60 | 1. The level of detail in each section 61 | 2. How specific instructions are phrased 62 | 3. The balance between content, structure, and visual guidance 63 | 4. The overall organization of the prompts 64 | 65 | Remember: The paw grants exactly what you request, not what you intend. Be precise! -------------------------------------------------------------------------------- /sample_app/app.rb: -------------------------------------------------------------------------------- 1 | require 'monkeyspaw' 2 | 3 | # Configure the application 4 | MonkeysPaw.configure do |config| 5 | config.port = 4567 6 | config.host = 'localhost' 7 | 8 | # Enable caching to improve performance 9 | # Pages will only regenerate when their content or dependencies change 10 | config.caching_enabled = true 11 | 12 | # Default directories are 'wishes', 'components', and 'assets' 13 | end 14 | 15 | # Force clear cache with command line argument --clear-cache 16 | if ARGV.include?('--clear-cache') 17 | puts "Clearing cache before starting server..." 18 | MonkeysPaw.application.cache_manager.clear_cache 19 | end 20 | 21 | # Make your wish come true 22 | MonkeysPaw.pick_up! 23 | -------------------------------------------------------------------------------- /sample_app/components/layout.md: -------------------------------------------------------------------------------- 1 | # Layout Component for MonkeysPaw Site 2 | 3 | Create a responsive layout with the following structure: 4 | 5 | ## Basic Structure 6 | - Include Tailwind CSS via CDN: `` 7 | - Use a dark theme with mystical/magical feel 8 | - Set `font-family: 'Montserrat', 'Segoe UI', system-ui` for content and `font-family: 'Cinzel', serif` for headings 9 | - Include Google Fonts: `` 10 | - Only include any CDN resources unless from jsdelivr.net 11 | 12 | ## Header 13 | - Fixed navigation at the top with slightly transparent dark background 14 | - Navigation links should slide in from below when hovered 15 | - Include navigation items with the following paths: 16 | - Home (/) 17 | - How It Works (/how-it-works) 18 | - Getting Started (/getting-started) 19 | - Components (/components) 20 | - Examples (/examples) 21 | - Best Practices (/best-practices) 22 | - Mobile: Convert to hamburger menu that slides in from the side 23 | 24 | ## Main Content Area 25 | - Centered content with max-width of 1200px 26 | - Subtle parchment-like background texture 27 | - Elegant shadows and subtle borders 28 | - Add small monkey paw print graphics as section dividers 29 | 30 | ## Footer 31 | - Dark background matching the header 32 | - Include "Be careful what you wish for..." tagline 33 | - Add GitHub link, created year 34 | - Add small animated monkey's paw icon that curls when hovering over footer 35 | 36 | ## Animations 37 | - Subtle page transitions 38 | - Elements should fade in when scrolling into view 39 | - Interactive elements should have subtle hover effects 40 | - Add a small animated cursor effect near the mouse that looks like magical sparkles 41 | 42 | ## Special Features 43 | - Include a "theme toggle" that shows a monkey's paw curling animation during theme switch 44 | - Add a subtle "fingerprint" pattern in the background that looks like monkey paw prints 45 | - When users hover over code samples, show a small "wish granted" message 46 | 47 | Ensure the layout is fully responsive and works well on all device sizes. 48 | -------------------------------------------------------------------------------- /sample_app/components/style.md: -------------------------------------------------------------------------------- 1 | # Style Component for MonkeysPaw Site 2 | 3 | Apply these styling guidelines to create a mysterious, magical, and slightly ominous aesthetic: 4 | 5 | ## Color Scheme 6 | - Primary color: Deep purple (#4A154B) - representing mystery 7 | - Secondary color: Amber gold (#FFB300) - representing wishes/magic 8 | - Accent color: Teal (#009688) - representing the unexpected consequences 9 | - Dark background: Near-black (#121212) with slight purple tint 10 | - Light text: Off-white (#F8F8F2) with slight amber glow 11 | - Warning elements: Deep red (#FF5252) for cautionary notes 12 | 13 | ## Typography 14 | - Headings: 'Cinzel', serif - elegant and mystical 15 | - Body text: 'Montserrat', system-ui - clean and readable 16 | - Code blocks: 'Fira Code', monospace - clear for code examples 17 | - Heading sizes should be dramatic with the main title being quite large 18 | - Slightly increased line height (1.6) for better readability 19 | - Subtle text shadows on headings to enhance the magical feel 20 | 21 | ## UI Elements 22 | - Buttons styled as ancient runes or mystical symbols 23 | - Hover states should include a subtle "curling finger" animation 24 | - Form inputs should have glowing borders when focused 25 | - Cards and containers should appear like ancient parchment 26 | - Dividers should look like curling monkey's paw fingers 27 | - Links should glow subtly on hover 28 | - Success messages should look like "wishes granted" 29 | - Error messages should look like "cursed outcomes" 30 | - Do not use font-awesome 31 | 32 | ## Visual Effects 33 | - Subtle magical particle effects in the background 34 | - Text highlighting should have a golden amber glow 35 | - Shadow effects should be slightly exaggerated for drama 36 | - Important elements should have a subtle pulsing glow 37 | - Add occasional very subtle "finger curling" animations randomly in the UI 38 | - Section transitions should fade through a smoke-like effect 39 | 40 | ## Imagery 41 | - Use illustrations of monkey paws in various states (open, curled) 42 | - Include mystical/magical iconography (stars, smoke, ancient symbols) 43 | - Decorative elements should include ancient-looking script and symbols 44 | - Background textures like old parchment or worn leather 45 | - Code blocks should appear to be written on aged scrolls 46 | 47 | ## Special Patterns 48 | - The overall feeling should be of a powerful but dangerous magical artifact 49 | - Balance between elegance and ominousness 50 | - Create a sense of both attraction and caution 51 | - Elements should feel mystical but with modern usability 52 | - Incorporate subtle "be careful what you wish for" visual motifs 53 | -------------------------------------------------------------------------------- /sample_app/wishes/best-practices.md: -------------------------------------------------------------------------------- 1 | # MonkeysPaw Best Practices 2 | 3 | ## Page Content 4 | 5 | Create a comprehensive guide to writing effective wishes (prompts) in MonkeysPaw. The page should help users avoid common pitfalls and learn techniques for getting the most precise and useful results from their wishes. 6 | 7 | ### Introduction 8 | - Heading: "The Art of Careful Wishing" 9 | - Overview: "The monkey's paw grants exactly what you ask for—not what you intend. Mastering the art of wish-crafting is essential to harnessing the full power of this framework without unexpected consequences. This guide will help you develop prompts that produce precisely what you desire." 10 | 11 | ### The Golden Rules Section 12 | - Present 5 fundamental principles of effective wish-crafting: 13 | 1. **Be Specific**: Clearly describe what you want, leaving minimal room for interpretation 14 | 2. **Be Structured**: Organize your wishes into logical sections with clear hierarchy 15 | 3. **Be Contextual**: Provide relevant context for the AI to understand your intentions 16 | 4. **Be Consistent**: Avoid contradictory or conflicting instructions 17 | 5. **Be Reasonable**: Make wishes within the realistic capabilities of the system 18 | 19 | ### Anatomy of an Effective Wish 20 | - Break down the structure of a well-crafted wish: 21 | - Clear title and purpose statement 22 | - Content sections with hierarchical organization 23 | - Specific details about functionality and appearance 24 | - Examples where helpful 25 | - Tone and style guidance 26 | 27 | ### Before and After Examples 28 | - Show 3-4 examples of poorly written wishes alongside improved versions: 29 | 30 | **Example 1: Vague vs. Specific** 31 | - Before: 32 | ```markdown 33 | # Contact Page 34 | Make a contact page with a form and some information. 35 | ``` 36 | 37 | - After: 38 | ```markdown 39 | # Contact Page 40 | 41 | ## Purpose 42 | Create a contact page that makes it easy for potential clients to reach out while providing enough information to qualify leads. 43 | 44 | ## Required Elements 45 | - Heading: "Get in Touch" with a brief welcome message 46 | - Contact form with fields for: 47 | - Name (required) 48 | - Email (required, with validation) 49 | - Phone (optional) 50 | - Company (optional) 51 | - Message (required, with 500 character minimum) 52 | - Reason for contact (dropdown: General Inquiry, Project Quote, Support, Partnership) 53 | - Company information sidebar with: 54 | - Physical address with map 55 | - Phone number and email 56 | - Business hours 57 | - Response time expectation (24-48 hours) 58 | - FAQ section with 3-4 common questions about the contact process 59 | 60 | ## Visual Style 61 | - Clean and professional 62 | - Form should stand out visually from the page 63 | - Use icons for contact methods 64 | - Include subtle visual cues for required fields 65 | ``` 66 | 67 | - Annotations pointing out specific improvements 68 | 69 | **Example 2: Disorganized vs. Structured** 70 | - Before/After examples showing how organization improves results 71 | 72 | **Example 3: Contradictory vs. Consistent** 73 | - Before/After examples showing how resolving contradictions improves outcomes 74 | 75 | **Example 4: Ambiguous vs. Clear** 76 | - Before/After examples showing how clarity reduces misinterpretation 77 | 78 | ### Common Pitfalls Section 79 | - List and explain frequent mistakes: 80 | - **Ambiguity**: Using terms with multiple interpretations 81 | - **Over-specification**: Providing too much detail that conflicts or overcomplicates 82 | - **Under-specification**: Not providing enough guidance in critical areas 83 | - **Mixed concerns**: Confusing content, layout, and style instructions 84 | - **Assumed knowledge**: Failing to provide necessary context 85 | - **Impossible combinations**: Requesting mutually exclusive features 86 | - **Forgetting mobile**: Not considering responsive design needs 87 | - **Inconsistent terminology**: Using different terms for the same concept 88 | 89 | - For each pitfall, include: 90 | - A brief explanation of the problem 91 | - A concrete example of the issue 92 | - A solution approach 93 | 94 | ### Advanced Techniques Section 95 | - Share sophisticated wish-crafting strategies: 96 | 97 | 1. **Progressive Disclosure Technique** 98 | - Start with a clear overview 99 | - Break down each section into components 100 | - Provide details in a hierarchical manner 101 | - Example of this technique in action 102 | 103 | 2. **Visual Language Mastery** 104 | - Using descriptive terms effectively 105 | - Creating clear visual hierarchy 106 | - Communicating design intent without visual examples 107 | - Vocabulary for common visual patterns 108 | 109 | 3. **User Journey Mapping** 110 | - Describing interactions from the user's perspective 111 | - Defining clear user flows and expectations 112 | - Anticipating edge cases and scenarios 113 | - Building narrative-driven experiences 114 | 115 | 4. **Component Thinking** 116 | - Creating modular, reusable wish patterns 117 | - Establishing consistent naming conventions 118 | - Developing a wish component library 119 | - Combining components effectively 120 | 121 | ### Testing Your Wishes 122 | - Guidance on evaluating results: 123 | - Review objective: Did you get all requested elements? 124 | - Review subjective: Does it feel right? 125 | - Identify specific areas for refinement 126 | - Iterative improvement strategies 127 | - A/B testing different wish formulations 128 | 129 | ### Specialized Wish Types 130 | - Advice for specific page types: 131 | 1. **Landing Pages**: Focus on conversion paths and clear CTAs 132 | 2. **Product Pages**: Balance information and purchasing guidance 133 | 3. **Content Sites**: Emphasize readability and content discovery 134 | 4. **Applications**: Prioritize usability and interaction design 135 | 5. **Documentation**: Structure information for both scanning and deep reading 136 | 137 | ### Collaboration Strategies 138 | - Tips for team-based wish development: 139 | - Creating wish templates and standards 140 | - Review processes before submission 141 | - Documentation of effective patterns 142 | - Sharing learnings across the team 143 | 144 | ### When Things Go Wrong 145 | - Troubleshooting guidance: 146 | - Identifying where interpretation diverged from intention 147 | - Specific strategies for correcting common issues 148 | - When to rewrite vs. when to refine 149 | - Breaking complex wishes into simpler components 150 | 151 | ### Real-World Success Stories 152 | - Brief case studies of effective wish implementation: 153 | - The challenge faced 154 | - The wish-crafting approach used 155 | - The outcome achieved 156 | - Key learnings from the process 157 | 158 | ### Final Checklist 159 | - A printer-friendly checklist for wish evaluation: 160 | - Clarity assessment 161 | - Consistency check 162 | - Completeness verification 163 | - Technical feasibility confirmation 164 | - Brand alignment review 165 | 166 | ### Further Learning 167 | - Recommendations for resources: 168 | - Prompt engineering principles 169 | - UX writing guidelines 170 | - Visual design fundamentals 171 | - Content structure best practices 172 | 173 | ### Closing Wisdom 174 | - Final advice in a stylized quote box: 175 | - "Remember that precision in language leads to precision in results. The difference between a wish gone wrong and a wish fulfilled exactly as intended often lies in the careful craft of your words. Wield the monkey's paw with the respect it deserves, and it will grant you wonders beyond code." -------------------------------------------------------------------------------- /sample_app/wishes/components.md: -------------------------------------------------------------------------------- 1 | # Components in MonkeysPaw 2 | 3 | ## Page Content 4 | 5 | Create a detailed explanation page about the component system in MonkeysPaw, focusing on how layout and style components work together with page content to create cohesive web experiences. 6 | 7 | ### Introduction Section 8 | - Heading: "The Building Blocks of Wishes" 9 | - Overview: "Components in MonkeysPaw provide the foundation and character for your wishes. While your page descriptions define what content appears, components determine how that content is structured and presented. Understanding components is key to crafting effective wishes." 10 | 11 | ### Component Types Overview 12 | - Explain the two primary component types: 13 | 1. **Layout Components**: Define the structural organization and arrangement of content 14 | 2. **Style Components**: Define the visual presentation, aesthetics, and mood 15 | 16 | ### Layout Components Section 17 | - Heading: "Layout Components: The Skeleton" 18 | - Explanation: "Layout components determine how your content is arranged on the page, establishing the structural foundation for your wishes." 19 | 20 | - Key aspects to cover: 21 | - How layout components define page structure 22 | - The relationship between layout and responsiveness 23 | - How layout influences user experience 24 | - Typical elements defined in layout components 25 | 26 | - Sample Layout Component: 27 | ```markdown 28 | # Layout Component 29 | 30 | Create a layout with: 31 | - Fixed navigation header with logo and menu 32 | - Hero section with large image and overlay text 33 | - Three-column feature section for desktop, single column for mobile 34 | - Testimonial section with carousel 35 | - Contact form in a centered card 36 | - Footer with three sections: company info, links, and social media 37 | ``` 38 | 39 | - Annotation explaining each part of the sample 40 | - Visual diagram showing how a layout component maps to the final page structure 41 | 42 | ### Style Components Section 43 | - Heading: "Style Components: The Skin" 44 | - Explanation: "Style components define the visual language of your site, determining colors, typography, visual effects, and the overall mood of your pages." 45 | 46 | - Key aspects to cover: 47 | - How style components establish visual identity 48 | - The relationship between style and brand perception 49 | - How style choices influence user engagement 50 | - Typical elements defined in style components 51 | 52 | - Sample Style Component: 53 | ```markdown 54 | # Style Component 55 | 56 | Apply these styling guidelines: 57 | - Color palette: Deep blue (#1E3A8A) for primary elements, coral (#FF6B6B) for accents, and light gray (#F3F4F6) for backgrounds 58 | - Typography: Playfair Display for headings, Raleway for body text 59 | - Buttons should have rounded corners with subtle hover effects 60 | - Cards should have soft shadows and slight border radius 61 | - Use subtle animations for interactive elements 62 | - Images should have a slight zoom effect on hover 63 | ``` 64 | 65 | - Annotation explaining each part of the sample 66 | - Visual examples showing how different style components can dramatically change the same layout 67 | 68 | ### Component Interaction Section 69 | - Heading: "The Dance of Components" 70 | - Explanation of how layout and style components work together with page content 71 | - Visual showing the same content with: 72 | 1. Different layouts but same style 73 | 2. Same layout but different styles 74 | 3. How both combine to create completely different experiences 75 | 76 | ### Creating Effective Components 77 | - Best practices for writing layout components: 78 | - Focus on structure, not specific content 79 | - Consider responsive behavior 80 | - Define clear content areas 81 | - Maintain consistency across pages 82 | 83 | - Best practices for writing style components: 84 | - Create cohesive color schemes 85 | - Establish consistent typography rules 86 | - Define interactive behavior 87 | - Consider accessibility needs 88 | 89 | ### Advanced Component Techniques 90 | - Heading: "Mastering Component Craft" 91 | - Tips for advanced usage: 92 | - Creating themed variants of components 93 | - Developing component systems for different page types 94 | - Using conditional elements in components 95 | - Creating component libraries for reuse 96 | 97 | ### Common Pitfalls Section 98 | - Discuss typical mistakes and how to avoid them: 99 | - Overloading components with too much detail 100 | - Mixing content concerns with layout/style 101 | - Conflicting instructions between components 102 | - Ignoring mobile/responsive considerations 103 | 104 | ### Component Examples Gallery 105 | - Show 3-4 pairs of layout+style components and the resulting pages 106 | - Include annotations explaining how specific component instructions manifested in the final design 107 | 108 | ### Next Steps 109 | - Call to action: "Ready to create your own components?" 110 | - Links to: 111 | - Examples page for inspiration 112 | - Best Practices for more advanced techniques 113 | - Getting Started for a refresher on implementation -------------------------------------------------------------------------------- /sample_app/wishes/examples.md: -------------------------------------------------------------------------------- 1 | # MonkeysPaw Examples Gallery 2 | 3 | ## Page Content 4 | 5 | Create an inspiring showcase of different web pages created with MonkeysPaw, demonstrating the range and flexibility of the framework. Include examples of wishes alongside their results. 6 | 7 | ### Introduction 8 | - Heading: "Wishes Manifest" 9 | - Overview: "See the power of MonkeysPaw in action through these examples. Each showcase demonstrates how specific wishes translate into fully-realized web pages. Study these examples to improve your own wish-crafting skills." 10 | 11 | ### Gallery Layout 12 | - Create a visually appealing gallery layout with filtering options 13 | - Each example should show: 14 | 1. A preview image/screenshot of the resulting page 15 | 2. The title and category of the example 16 | 3. An expandable section showing the original wish 17 | 4. Key points highlighting notable aspects of the wish-to-page translation 18 | 19 | ### Example 1: Business Landing Page 20 | - Title: "Startup Launch Page" 21 | - Category: Business 22 | - Preview image: A clean, modern SaaS product landing page 23 | - The Wish (collapsible): 24 | ```markdown 25 | # SaaS Product Landing Page 26 | 27 | ## Content Requirements 28 | Create a landing page for a cloud-based project management tool called "TaskFlow" that helps teams organize work. 29 | 30 | ## Key Sections 31 | - Hero with headline, subheading, and primary CTA button 32 | - Features section with 3 key benefits and icons 33 | - How it works (3-step process) 34 | - Pricing plans (Free, Pro, Enterprise) 35 | - Customer testimonials (3 quotes) 36 | - FAQ accordion section 37 | - Newsletter signup and footer 38 | 39 | ## Tone & Style 40 | - Professional but friendly 41 | - Modern and sleek 42 | - Emphasize efficiency and simplicity 43 | ``` 44 | - Key Points: 45 | - Notice how the 3-step process was structured visually 46 | - The pricing comparison maintains clear hierarchy 47 | - Testimonials are presented with visual balance 48 | - The accordion FAQ section saves space while providing information 49 | 50 | ### Example 2: Personal Blog 51 | - Title: "Travel Blog Homepage" 52 | - Category: Personal 53 | - Preview image: A vibrant blog with rich imagery and clear typography 54 | - The Wish (collapsible): 55 | ```markdown 56 | # Travel Blog Homepage 57 | 58 | ## Content Description 59 | Create a homepage for a travel blog called "Wanderlust Chronicles" that showcases adventures around the world. 60 | 61 | ## Required Sections 62 | - Header with blog name and navigation 63 | - Featured post with large image 64 | - Recent posts grid (3-4 posts) 65 | - About the blogger sidebar with photo 66 | - Categories list 67 | - Instagram feed preview (6 images) 68 | - Newsletter signup 69 | - Footer with social links 70 | 71 | ## Visual Style 72 | - Vibrant and colorful 73 | - Large, immersive travel photography 74 | - Casual, adventurous feel 75 | ``` 76 | - Key Points: 77 | - Observe how the featured post draws attention 78 | - The grid layout balances text and images effectively 79 | - Social proof elements are integrated naturally 80 | - Mobile-responsive design adjusts content hierarchy 81 | 82 | ### Example 3: E-Commerce Product Page 83 | - Title: "Artisan Coffee Shop" 84 | - Category: E-Commerce 85 | - Preview image: An elegant product page for specialty coffee 86 | - The Wish (collapsible): 87 | ```markdown 88 | # Specialty Coffee Product Page 89 | 90 | ## Page Purpose 91 | Create a product page for a single-origin Ethiopia Yirgacheffe coffee that showcases its unique qualities and encourages purchase. 92 | 93 | ## Required Elements 94 | - Product title and origin details 95 | - Large product image with zoom capability 96 | - Price and "Add to Cart" button 97 | - Roast level indicator (Medium-Light) 98 | - Flavor notes with visual representation 99 | - Origin story and farmer information 100 | - Brewing recommendations 101 | - Customer reviews section 102 | - Related products carousel 103 | 104 | ## Desired Feeling 105 | - Artisanal and premium 106 | - Educational about coffee 107 | - Warm and inviting 108 | ``` 109 | - Key Points: 110 | - The wish specifically requested visual representation of flavor notes 111 | - Origin story creates emotional connection with the product 112 | - Brewing recommendations add value beyond the purchase 113 | - Mobile version prioritizes purchase elements 114 | 115 | ### Example 4: Portfolio Site 116 | - Title: "Designer Portfolio" 117 | - Category: Portfolio 118 | - Preview image: A minimalist, elegant portfolio with strong typography 119 | - The Wish (collapsible): 120 | ```markdown 121 | # Designer Portfolio Site 122 | 123 | ## Content Needs 124 | Create a portfolio homepage for a UI/UX designer that showcases their work and skills. 125 | 126 | ## Key Areas 127 | - Minimal header with name and navigation 128 | - Hero section with brief introduction and specialties 129 | - Work gallery with 6 featured projects (thumbnails) 130 | - About section with professional background 131 | - Skills and tools section 132 | - Process explanation (4 steps) 133 | - Contact section with form 134 | - Footer with social media links 135 | 136 | ## Aesthetic 137 | - Minimalist and clean 138 | - Ample white space 139 | - Subtle animations on interaction 140 | - Typography-focused design 141 | ``` 142 | - Key Points: 143 | - Notice how white space is used to create focus 144 | - Project thumbnails expand with details on interaction 145 | - Typography hierarchy clearly guides the viewer 146 | - Animation is used purposefully rather than decoratively 147 | 148 | ### Example 5: Documentation Page 149 | - Title: "API Documentation" 150 | - Category: Technical 151 | - Preview image: A clean, organized technical documentation page 152 | - The Wish (collapsible): 153 | ```markdown 154 | # API Documentation Page 155 | 156 | ## Content Requirements 157 | Create a documentation page for a payment processing API that is both technical and accessible. 158 | 159 | ## Required Sections 160 | - Sticky navigation sidebar with collapsible categories 161 | - Introduction with quick start guide 162 | - Authentication section with code examples 163 | - Endpoints reference table 164 | - Request/response examples for each endpoint 165 | - Error codes and handling 166 | - Rate limiting information 167 | - SDK links and alternatives 168 | - Code snippets in multiple languages (JS, Python, Ruby) 169 | 170 | ## Visual Style 171 | - Clean and highly readable 172 | - Syntax highlighting for code blocks 173 | - Light/dark mode toggle 174 | - Easy to scan structure 175 | ``` 176 | - Key Points: 177 | - The wish emphasized readability and scannable structure 178 | - Code examples use proper syntax highlighting 179 | - The sticky navigation makes complex information accessible 180 | - Light/dark mode respects developer preferences 181 | 182 | ### Interactive Feature 183 | - Add an interactive element where visitors can modify simple wish parameters and see how it changes the outcome 184 | - Include toggles for: 185 | - Color scheme preference 186 | - Typography style 187 | - Layout density 188 | - Content focus 189 | - Show a "before/after" preview based on these changes 190 | 191 | ### Download Section 192 | - Provide downloadable examples for users to explore: 193 | - Complete wish markdown files 194 | - Project structure examples 195 | - Component variations 196 | 197 | ### Best Practices Spotlight 198 | - Highlight key learnings from the examples: 199 | - Be specific about structure and relationships 200 | - Use visual language to describe desired aesthetics 201 | - Provide content examples for proper context 202 | - Consider user journey and interactions 203 | 204 | ### Next Steps 205 | - Call to action: "Ready to craft your own wishes?" 206 | - Links to: 207 | - Getting Started guide 208 | - Components explanation 209 | - Best Practices -------------------------------------------------------------------------------- /sample_app/wishes/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started with MonkeysPaw 2 | 3 | ## Page Content 4 | 5 | Create a practical, step-by-step guide that helps users start using MonkeysPaw quickly while providing enough detail for them to understand what they're doing. Maintain the mystical theme while being clear and instructive. 6 | 7 | ### Introduction 8 | - Heading: "Your First Wish Awaits" 9 | - Brief overview: "This guide will walk you through summoning the MonkeysPaw, preparing your first wishes, and witnessing them manifest. Follow these steps carefully to begin your journey into wish-driven development." 10 | 11 | ### Prerequisites 12 | - A short list of requirements: 13 | - Ruby 3.0 or higher 14 | - Bundler 15 | - Basic familiarity with Ruby and web concepts 16 | - A healthy respect for the power of wishes 17 | 18 | ### Installation Section 19 | - Step 1: Add to your Gemfile 20 | ```ruby 21 | gem 'monkeyspaw' 22 | ``` 23 | - Step 2: Run bundle install 24 | ```bash 25 | $ bundle install 26 | ``` 27 | - Alternative: Direct installation 28 | ```bash 29 | $ gem install monkeyspaw 30 | ``` 31 | - Note: "The paw is now in your possession. Use it wisely." 32 | 33 | ### Project Setup 34 | - Creating a new MonkeysPaw project: 35 | ```bash 36 | $ mkdir my_wishes 37 | $ cd my_wishes 38 | ``` 39 | - Setting up the basic directory structure: 40 | ```bash 41 | mkdir -p wishes components assets 42 | ``` 43 | - Explain each directory's purpose: 44 | - `wishes/`: Where your page descriptions live 45 | - `components/`: Layout and style components 46 | - `assets/`: Static files like images 47 | 48 | ### Your First Component Files 49 | - Create a layout component (`components/layout.md`): 50 | ```markdown 51 | # Layout Component 52 | 53 | Create a clean layout with: 54 | - A header with site title and navigation 55 | - Main content area 56 | - Footer with copyright 57 | - Responsive design for mobile and desktop 58 | ``` 59 | 60 | - Create a style component (`components/style.md`): 61 | ```markdown 62 | # Style Component 63 | 64 | Apply these styling guidelines: 65 | - Modern, clean aesthetic with ample white space 66 | - Primary color: #3490dc, Secondary: #f6993f 67 | - Sans-serif fonts for readability 68 | - Subtle animations for interactive elements 69 | ``` 70 | 71 | ### Your First Wish 72 | - Create your first wish file (`wishes/index.md`): 73 | ```markdown 74 | # Home Page 75 | 76 | ## Content 77 | Create a welcoming home page for a personal blog about hiking adventures. 78 | 79 | ## Required Sections 80 | - Hero section with a greeting and brief introduction 81 | - Featured hikes section with 3 recent adventures 82 | - About the author section with a short bio 83 | - Newsletter signup form 84 | 85 | ## Tone 86 | Friendly, adventurous, and inspiring 87 | ``` 88 | 89 | ### Bringing It to Life 90 | - Create a simple Ruby file to run your application (`app.rb`): 91 | ```ruby 92 | require 'monkeyspaw' 93 | 94 | MonkeysPaw.pick_up! 95 | ``` 96 | - Run the application: 97 | ```bash 98 | $ ruby app.rb 99 | ``` 100 | - View your creation: 101 | ``` 102 | You pick up the monkey's paw. Be careful what you wish for on http://localhost:1337 103 | ``` 104 | 105 | ### Customizing Your Experience 106 | - Basic configuration options: 107 | ```ruby 108 | MonkeysPaw.configure do |config| 109 | config.port = 4567 110 | config.host = 'localhost' 111 | # Custom directories if needed 112 | config.wishes_dir = 'my_custom_wishes_folder' 113 | config.components_dir = 'my_custom_components_folder' 114 | end 115 | ``` 116 | 117 | ### Understanding the Results 118 | - Tips for evaluating what you receive: 119 | - Compare the output against your wish 120 | - Note areas where more specificity would help 121 | - Identify patterns in how your wishes are interpreted 122 | - Keep track of particularly effective phrasings 123 | 124 | ### Debugging Common Issues 125 | - Troubleshooting section with common problems: 126 | - Missing directories 127 | - Unclear or contradictory wishes 128 | - Performance considerations 129 | - Server configuration issues 130 | 131 | ### Next Steps 132 | - Suggestions for further exploration: 133 | - Try creating more complex pages 134 | - Experiment with different layout and style components 135 | - Learn how to structure multi-page sites 136 | - Read the Best Practices guide for more advanced usage 137 | 138 | ### Warning Box 139 | - Final cautionary note in a stylized warning box: 140 | - "Remember: The paw grants exactly what you request, not what you intend. Be precise, be clear, and most importantly—be careful what you wish for." 141 | 142 | ### Continue Your Journey 143 | - Call-to-action buttons: 144 | - "Explore Components" (links to Components page) 145 | - "See Examples" (links to Examples page) 146 | - "Learn Best Practices" (links to Best Practices page) -------------------------------------------------------------------------------- /sample_app/wishes/how-it-works.md: -------------------------------------------------------------------------------- 1 | # How MonkeysPaw Works 2 | 3 | ## Page Content 4 | 5 | Create an informative page that explains the inner workings of the MonkeysPaw framework with visuals and examples. The page should clarify how prompts are transformed into web pages while maintaining the mystical theme. 6 | 7 | ### Introduction Section 8 | - Heading: "The Magic Behind the Paw" 9 | - Brief explanation: "MonkeysPaw operates on a simple but powerful principle: your wishes (prompts) are interpreted by advanced AI to manifest as fully-formed web pages. But like any mystical artifact, understanding its workings can help you wield it more effectively." 10 | 11 | ### The Wish Cycle Diagram 12 | - Create a visual flow diagram showing: 13 | 1. **Your Wish**: Written in markdown files in the `wishes/` directory 14 | 2. **Components**: Layout and style components that influence the outcome 15 | 3. **The Granting**: AI processing of the combined inputs 16 | 4. **Manifestation**: The resulting HTML page displayed to users 17 | - Each step should have a brief explanation and small visualization 18 | 19 | ### Anatomy of a Wish 20 | - Show a sample wish file with annotations explaining key parts: 21 | ```markdown 22 | # Product Landing Page 23 | 24 | ## Content Instructions: 25 | Create a landing page for a premium fountain pen with elegant design. 26 | Focus on craftsmanship and writing experience. 27 | 28 | ## Key Features to Highlight: 29 | - Hand-crafted 14k gold nib 30 | - Piston filling mechanism 31 | - Ebonite body with custom patterns 32 | - Smooth writing experience 33 | 34 | ## Desired Sections: 35 | - Hero with product image 36 | - Features breakdown 37 | - Testimonials from writers 38 | - Specifications table 39 | - Purchase options 40 | 41 | ## Tone: 42 | Sophisticated, elegant, premium but not pretentious 43 | ``` 44 | - Explain how each section of this "wish" influences the final outcome 45 | 46 | ### The Role of Components 47 | - Explain the two special components: 48 | 1. **Layout Component**: Controls the structural organization of elements 49 | 2. **Style Component**: Defines the visual presentation and aesthetics 50 | - Show how these components interact with page-specific wishes 51 | - Include a visual showing how the same content looks with different layout/style combinations 52 | 53 | ### Behind the Scenes 54 | - Simple technical explanation of how MonkeysPaw: 55 | 1. Reads the files from the directory structure 56 | 2. Combines page content with layout and style guidance 57 | 3. Generates a comprehensive prompt for the AI 58 | 4. Processes the AI response into valid HTML 59 | 5. Serves the resulting page via a built-in web server 60 | 61 | ### Example: Wish vs. Reality 62 | - Show a side-by-side comparison of: 63 | - A sample wish (prompt file) 64 | - The resulting web page 65 | - The HTML code generated 66 | - Highlight areas where specific instructions directly influenced the outcome 67 | 68 | ### Common Questions 69 | - Include a FAQ section addressing: 70 | - "How specific should my wishes be?" 71 | - "Can I modify the HTML after it's generated?" 72 | - "How does caching work?" 73 | - "Can I use multiple layout or style components?" 74 | - "What happens if my wish is contradictory or impossible?" 75 | 76 | ### Warnings Section 77 | - Title: "The Fine Print" with a small monkey's paw icon 78 | - List of important considerations: 79 | - Ambiguous wishes may have unexpected interpretations 80 | - Highly complex wishes might require more precise language 81 | - The paw grants what you ask for, not what you intend 82 | - Each wish may manifest slightly differently on each request 83 | 84 | ### Next Steps 85 | - Call-to-action: "Ready to craft your own wishes? Continue to Getting Started" 86 | - Secondary link: "Browse Examples to see the paw in action" -------------------------------------------------------------------------------- /sample_app/wishes/index.md: -------------------------------------------------------------------------------- 1 | # MonkeysPaw Home Page 2 | 3 | ## Page Content 4 | 5 | Create a dramatic, visually striking home page for the MonkeysPaw Ruby gem that introduces the concept of "wish-driven development." The page should convey both the power and the potential danger of using the framework. 6 | 7 | ### Hero Section 8 | - Large, dramatic headline: "MonkeysPaw 🐒✋" 9 | - Tagline: "Wish-Driven Development for Ruby" 10 | - Dramatic subtitle: "Be careful what you wish for..." 11 | - Brief 2-3 sentence explanation: "A prompt-driven web framework that grants your web development wishes through AI. Like the legendary monkey's paw, it will fulfill your requests exactly as stated—for better or worse." 12 | - Call-to-action button: "Make Your First Wish" (links to Getting Started) 13 | 14 | ### Key Features Section 15 | - Title: "The Power of the Paw" 16 | - 3 feature cards with icons: 17 | 1. **Prompt-Driven**: "Express your intentions through natural language, not code" 18 | 2. **AI-Powered**: "Harness advanced language models to generate complete web pages" 19 | 3. **Eerily Accurate**: "Get precisely what you ask for—exactly as you phrase it" 20 | - Each card should have a subtle warning about the consequences of imprecise wishes 21 | 22 | ### How It Works Section 23 | - Simple 3-step graphic showing: 24 | 1. Write your wish (prompt) 25 | 2. The paw curls (processing) 26 | 3. Your page manifests (result) 27 | - Include a brief code sample showing how simple it is to use: 28 | ```ruby 29 | require 'monkeyspaw' 30 | 31 | # Make your wish come true 32 | MonkeysPaw.pick_up! 33 | ``` 34 | - Note explaining: "MonkeysPaw reads your wishes from the `wishes/` directory and manifests your content at http://localhost:1337" 35 | 36 | ### Testimonials Section 37 | - Title: "Tales of Granted Wishes" 38 | - 3-4 fictional but amusing testimonials that hint at both success and unexpected consequences: 39 | - "I wished for a contact form and got exactly that—though I forgot to ask for form validation. Be specific!" — Ruby Developer 40 | - "Asked for an 'eye-catching' header and now visitors can't look away... literally. Impressive, if slightly concerning." — Web Designer 41 | - "The landing page I wished for converts 200% better than my hand-coded one. I'm both thrilled and slightly unnerved." — Startup Founder 42 | 43 | ### Final Call-to-Action 44 | - Large button: "Learn to Make Careful Wishes" (links to Getting Started) 45 | - Small warning text beneath: "Remember: With great power comes the responsibility to be precise." 46 | 47 | ### Visual Elements 48 | - Incorporate a subtle animated monkey's paw that curls one finger for each section scrolled 49 | - Add magical/mystical accents without being too whimsical 50 | - Create an atmosphere that balances wonder with a touch of foreboding 51 | -------------------------------------------------------------------------------- /spec/monkeyspaw/application_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe MonkeysPaw::Application do 4 | subject(:application) { described_class.new } 5 | 6 | it 'initializes with default values' do 7 | expect(application.config).to be_a(MonkeysPaw::Config) 8 | expect(application.root).to be_a(Pathname) 9 | end 10 | 11 | describe '#configure' do 12 | it 'yields the config object to the block' do 13 | expect { |b| application.configure(&b) }.to yield_with_args(application.config) 14 | end 15 | 16 | it 'allows setting configuration values' do 17 | application.configure do |config| 18 | config.port = 8080 19 | config.host = '127.0.0.1' 20 | end 21 | 22 | expect(application.config.port).to eq(8080) 23 | expect(application.config.host).to eq('127.0.0.1') 24 | end 25 | end 26 | 27 | describe 'directory methods' do 28 | it 'returns the pages directory' do 29 | expect(application.pages_dir).to eq(application.root.join('wishes')) 30 | end 31 | 32 | it 'returns the components directory' do 33 | expect(application.components_dir).to eq(application.root.join('components')) 34 | end 35 | 36 | it 'returns the assets directory' do 37 | expect(application.assets_dir).to eq(application.root.join('assets')) 38 | end 39 | 40 | it 'allows changing the root directory' do 41 | new_path = Pathname.new('/tmp/monkeyspaw-test') 42 | application.root = new_path 43 | expect(application.root).to eq(new_path) 44 | expect(application.pages_dir).to eq(new_path.join('wishes')) 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/monkeyspaw/cache_manager_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'json' 3 | require 'fileutils' 4 | 5 | RSpec.describe MonkeysPaw::CacheManager do 6 | let(:app) { MonkeysPaw::Application.new } 7 | let(:cache_manager) { described_class.new(app) } 8 | let(:test_prompt_path) { Pathname.new(Dir.mktmpdir).join('test_prompt.md') } 9 | let(:temp_dir) { Pathname.new(Dir.mktmpdir) } 10 | 11 | before do 12 | # Set up a temporary root directory 13 | app.root = temp_dir 14 | 15 | # Create necessary directories 16 | FileUtils.mkdir_p(app.pages_dir) 17 | FileUtils.mkdir_p(app.components_dir) 18 | 19 | # Create a test prompt file 20 | File.write(test_prompt_path, "# Test Page\n\nThis is a test page.") 21 | end 22 | 23 | after do 24 | # Clean up temporary files 25 | FileUtils.rm_rf(temp_dir) 26 | FileUtils.rm_rf(test_prompt_path.dirname) 27 | end 28 | 29 | describe '#get_current_dependencies' do 30 | context 'when component files exist' do 31 | before do 32 | # Create layout and style component files 33 | File.write(app.components_dir.join('layout.md'), "# Custom Layout") 34 | File.write(app.components_dir.join('style.md'), "# Custom Style") 35 | 36 | # Reload the components to recognize the new files 37 | app.prompt_manager.load_default_components 38 | end 39 | 40 | it 'includes file signatures for custom components' do 41 | deps = cache_manager.send(:get_current_dependencies, '/test', test_prompt_path) 42 | 43 | expect(deps['page']).to eq(Digest::MD5.hexdigest(File.read(test_prompt_path))) 44 | expect(deps['layout']).to eq(Digest::MD5.hexdigest("# Custom Layout")) 45 | expect(deps['style']).to eq(Digest::MD5.hexdigest("# Custom Style")) 46 | end 47 | end 48 | 49 | context 'when component files do not exist' do 50 | it 'uses default component prompts' do 51 | deps = cache_manager.send(:get_current_dependencies, '/test', test_prompt_path) 52 | 53 | expect(deps['page']).to eq(Digest::MD5.hexdigest(File.read(test_prompt_path))) 54 | expect(deps['layout']).to eq(app.prompt_manager.default_layout_prompt) 55 | expect(deps['style']).to eq(app.prompt_manager.default_style_prompt) 56 | end 57 | end 58 | end 59 | 60 | describe '#dependencies_changed?' do 61 | before do 62 | # Create a mock dependencies file 63 | deps = { 64 | 'page' => 'test_hash', 65 | 'layout' => app.prompt_manager.default_layout_prompt, 66 | 'style' => app.prompt_manager.default_style_prompt 67 | } 68 | 69 | cache_key = cache_manager.send(:generate_cache_key, '/test', test_prompt_path) 70 | deps_file = cache_manager.cache_dir.join("#{cache_key}.deps") 71 | 72 | File.write(deps_file, JSON.generate(deps)) 73 | end 74 | 75 | it 'detects when dependencies have changed' do 76 | # Change the test prompt file 77 | File.write(test_prompt_path, "# Modified Test Page\n\nThis page was modified.") 78 | 79 | expect(cache_manager.send(:dependencies_changed?, '/test', test_prompt_path)).to be true 80 | end 81 | 82 | it 'detects when dependencies have not changed' do 83 | # Mock the get_current_dependencies method to return consistent results 84 | allow(cache_manager).to receive(:get_current_dependencies).and_return({ 85 | 'page' => 'test_hash', 86 | 'layout' => app.prompt_manager.default_layout_prompt, 87 | 'style' => app.prompt_manager.default_style_prompt 88 | }) 89 | 90 | expect(cache_manager.send(:dependencies_changed?, '/test', test_prompt_path)).to be false 91 | end 92 | end 93 | 94 | describe 'caching workflow' do 95 | it 'correctly caches and retrieves pages' do 96 | html_content = "Test content" 97 | 98 | # Cache the page 99 | cache_manager.cache_page('/test', test_prompt_path, html_content) 100 | 101 | # Retrieve the cached content 102 | cached_content = cache_manager.get_cached_page('/test', test_prompt_path) 103 | 104 | expect(cached_content).to eq(html_content) 105 | end 106 | 107 | it 'works correctly when no component files exist' do 108 | # Ensure no component files exist 109 | expect(File.exist?(app.components_dir.join('layout.md'))).to be false 110 | expect(File.exist?(app.components_dir.join('style.md'))).to be false 111 | 112 | html_content = "Test content" 113 | 114 | # This should not raise any errors 115 | expect { 116 | cache_manager.cache_page('/test', test_prompt_path, html_content) 117 | cached_content = cache_manager.get_cached_page('/test', test_prompt_path) 118 | }.not_to raise_error 119 | 120 | # Verify cached content 121 | cached_content = cache_manager.get_cached_page('/test', test_prompt_path) 122 | expect(cached_content).to eq(html_content) 123 | end 124 | end 125 | end 126 | -------------------------------------------------------------------------------- /spec/monkeyspaw/config_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe MonkeysPaw::Config do 4 | subject(:config) { described_class.new } 5 | 6 | it 'initializes with default values' do 7 | expect(config.pages_dir).to eq('wishes') 8 | expect(config.components_dir).to eq('components') 9 | expect(config.assets_dir).to eq('assets') 10 | expect(config.port).to eq(1337) 11 | expect(config.host).to eq('localhost') 12 | end 13 | 14 | it 'can be converted to a hash' do 15 | expect(config.to_h).to include( 16 | pages_dir: 'wishes', 17 | components_dir: 'components', 18 | assets_dir: 'assets', 19 | port: 1337, 20 | host: 'localhost' 21 | ) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/monkeyspaw/generators/html_from_description_generator_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe MonkeysPaw::HtmlFromDescriptionGenerator do 4 | subject(:generator) do 5 | described_class.new( 6 | description: page_description, 7 | layout_prompt: layout_prompt, 8 | style_prompt: style_prompt 9 | ) 10 | end 11 | 12 | let(:page_description) { 'Create a simple hello world page' } 13 | let(:layout_prompt) { 'Use a centered layout with header and footer' } 14 | let(:style_prompt) { 'Use blue colors and sans-serif fonts' } 15 | 16 | describe '#prompt' do 17 | it 'includes the page description' do 18 | expect(generator.prompt).to include(page_description) 19 | end 20 | 21 | it 'includes the layout requirements when provided' do 22 | expect(generator.prompt).to include('Layout Requirements') 23 | expect(generator.prompt).to include(layout_prompt) 24 | end 25 | 26 | it 'includes the style guidelines when provided' do 27 | expect(generator.prompt).to include('Style Guidelines') 28 | expect(generator.prompt).to include(style_prompt) 29 | end 30 | 31 | it 'explains how the components work together' do 32 | expect(generator.prompt).to include('The three components work together') 33 | expect(generator.prompt).to include('page description provides') 34 | expect(generator.prompt).to include('layout requirements define') 35 | expect(generator.prompt).to include('style guidelines inform') 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/monkeyspaw/prompt_manager_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe MonkeysPaw::PromptManager do 4 | let(:app) { MonkeysPaw::Application.new } 5 | subject(:prompt_manager) { described_class.new(app) } 6 | 7 | it 'initializes with an application' do 8 | expect(prompt_manager.app).to eq(app) 9 | end 10 | 11 | it 'loads default components when initialized' do 12 | expect(prompt_manager.layout_prompt).not_to be_nil 13 | expect(prompt_manager.style_prompt).not_to be_nil 14 | end 15 | 16 | describe '#load_layout_prompt' do 17 | it 'provides a default layout prompt when no file exists' do 18 | prompt_manager.load_layout_prompt 19 | expect(prompt_manager.layout_prompt).to include('Layout Component') 20 | expect(prompt_manager.layout_prompt).to include('responsive header') 21 | end 22 | end 23 | 24 | describe '#load_style_prompt' do 25 | it 'provides a default style prompt when no file exists' do 26 | prompt_manager.load_style_prompt 27 | expect(prompt_manager.style_prompt).to include('Style Component') 28 | expect(prompt_manager.style_prompt).to include('clean, minimalist design') 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/monkeyspaw/version_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe MonkeysPaw do 4 | it 'has a version number' do 5 | expect(MonkeysPaw::VERSION).not_to be nil 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'monkeyspaw' 2 | require 'tempfile' 3 | require 'pathname' 4 | 5 | RSpec.configure do |config| 6 | config.example_status_persistence_file_path = "./spec/examples.txt" 7 | config.disable_monkey_patching! 8 | config.warnings = true 9 | 10 | config.expect_with :rspec do |expectations| 11 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 12 | end 13 | 14 | config.mock_with :rspec do |mocks| 15 | mocks.verify_partial_doubles = true 16 | end 17 | 18 | config.shared_context_metadata_behavior = :apply_to_host_groups 19 | config.order = :random 20 | Kernel.srand config.seed 21 | end 22 | --------------------------------------------------------------------------------