diff --git a/package.json b/package.json index 397c027f..a356335a 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,8 @@ "scripts": { "dev": "pnpm --filter agentation watch & pnpm --filter feedback-tool-example dev", "build": "pnpm --filter agentation build", + "build:rails": "pnpm --filter agentation build && pnpm --filter agentation-rails-build build", + "build:all": "pnpm build && pnpm --filter agentation-rails-build build", "example": "pnpm --filter feedback-tool-example dev", "pack": "cd package && pnpm pack", "mcp": "pnpm --filter agentation-mcp start", @@ -17,5 +19,6 @@ "better-sqlite3", "esbuild" ] - } + }, + "packageManager": "pnpm@10.13.1+sha512.37ebf1a5c7a30d5fabe0c5df44ee8da4c965ca0c5af3dbab28c3a1681b70a256218d05c81c9c0dcf767ef6b8551eb5b960042b9ed4300c59242336377e01cfad" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 72fe188e..d2b72414 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -121,6 +121,40 @@ importers: specifier: ^5.0.0 version: 5.9.3 + rails: + dependencies: + agentation: + specifier: workspace:* + version: link:../package + react: + specifier: ^18.2.0 + version: 18.3.1 + react-dom: + specifier: ^18.2.0 + version: 18.3.1(react@18.3.1) + devDependencies: + '@types/react': + specifier: ^18.2.0 + version: 18.3.28 + '@types/react-dom': + specifier: ^18.2.0 + version: 18.3.7(@types/react@18.3.28) + postcss: + specifier: ^8.5.6 + version: 8.5.6 + postcss-modules: + specifier: ^6.0.1 + version: 6.0.1(postcss@8.5.6) + sass: + specifier: ^1.97.2 + version: 1.97.3 + tsup: + specifier: ^8.0.0 + version: 8.5.1(postcss@8.5.6)(typescript@5.9.3) + typescript: + specifier: ^5.0.0 + version: 5.9.3 + packages: '@asamuzakjp/css-color@3.2.0': diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 645858de..fd0dd406 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,3 +2,4 @@ packages: - 'package' - 'package/example' - 'mcp' + - 'rails' diff --git a/rails/.gitignore b/rails/.gitignore new file mode 100644 index 00000000..c2658d7d --- /dev/null +++ b/rails/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/rails/gem/LICENSE b/rails/gem/LICENSE new file mode 100644 index 00000000..4dc169a0 --- /dev/null +++ b/rails/gem/LICENSE @@ -0,0 +1,27 @@ +PolyForm Shield License 1.0.0 + +Copyright (c) 2026 Benji Taylor + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to use, +copy, modify, and distribute the Software, subject to the following conditions: + +1. You may not use the Software to provide a product or service that competes + with the Software or any product or service offered by the Licensor that + includes the Software. + +2. You may not remove or obscure any licensing, copyright, or other notices + included in the Software. + +3. If you distribute the Software or any derivative works, you must include a + copy of this license. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +For more information, see https://polyformproject.org/licenses/shield/1.0.0 diff --git a/rails/gem/README.md b/rails/gem/README.md new file mode 100644 index 00000000..cc6eb6e5 --- /dev/null +++ b/rails/gem/README.md @@ -0,0 +1,87 @@ +# agentation-rails + +Drop-in Rails engine that adds the [Agentation](https://github.com/benjitaylor/agentation) annotation toolbar to your app in development. One line in your Gemfile, zero configuration. + +## Installation + +```ruby +# Gemfile +gem "agentation-rails", group: :development +``` + +```bash +bundle install +``` + +The toolbar appears automatically in development. Nothing to configure. + +## Configuration (optional) + +Generate an initializer: + +```bash +rails generate agentation:install +``` + +Or add one manually: + +```ruby +# config/environments/development.rb +Agentation.configure do |config| + config.endpoint = "http://localhost:4747" # MCP sync server (default) + config.webhook_url = "https://example.com/hooks/agentation" + config.copy_to_clipboard = false # disable auto-copy +end +``` + +## JavaScript events + +The toolbar dispatches `CustomEvent`s on `document` for every annotation lifecycle event. Use these with Stimulus controllers or plain JS: + +```javascript +document.addEventListener("agentation:add", (e) => { + console.log("Annotation added:", e.detail); +}); + +document.addEventListener("agentation:delete", (e) => { + console.log("Annotation deleted:", e.detail); +}); + +document.addEventListener("agentation:update", (e) => { + console.log("Annotation updated:", e.detail); +}); + +document.addEventListener("agentation:clear", (e) => { + console.log("Annotations cleared:", e.detail); +}); + +document.addEventListener("agentation:copy", (e) => { + console.log("Copied markdown:", e.detail.markdown); +}); + +document.addEventListener("agentation:submit", (e) => { + console.log("Submitted:", e.detail.output, e.detail.annotations); +}); + +document.addEventListener("agentation:session", (e) => { + console.log("Session created:", e.detail.sessionId); +}); +``` + +## How it works + +The gem inserts a `) + end + + # Recomputed each request so config changes via console/reloader take effect + def body_tag + config = Agentation.configuration + attrs = [] + attrs << data_attr("endpoint", config.endpoint) + attrs << data_attr("session-id", config.session_id) + attrs << data_attr("webhook-url", config.webhook_url) + attrs << data_attr("copy-to-clipboard", config.copy_to_clipboard) unless config.copy_to_clipboard.nil? + attrs.compact! + + return nil if attrs.empty? + + %(
) + end + + def data_attr(name, value) + return nil unless value + + %( data-#{name}="#{ERB::Util.html_escape(value)}") + end + + def agentation_js + @agentation_js ||= File.read( + File.expand_path("../../app/assets/javascripts/agentation.js", __dir__) + ) + end + end +end diff --git a/rails/gem/lib/generators/agentation/install_generator.rb b/rails/gem/lib/generators/agentation/install_generator.rb new file mode 100644 index 00000000..83f637b2 --- /dev/null +++ b/rails/gem/lib/generators/agentation/install_generator.rb @@ -0,0 +1,12 @@ +module Agentation + module Generators + class InstallGenerator < Rails::Generators::Base + desc "Creates an Agentation initializer in config/initializers." + source_root File.expand_path("templates", __dir__) + + def copy_initializer + template "agentation.rb", "config/initializers/agentation.rb" + end + end + end +end diff --git a/rails/gem/lib/generators/agentation/templates/agentation.rb b/rails/gem/lib/generators/agentation/templates/agentation.rb new file mode 100644 index 00000000..5db431e4 --- /dev/null +++ b/rails/gem/lib/generators/agentation/templates/agentation.rb @@ -0,0 +1,18 @@ +# Agentation — visual annotation toolbar for AI coding agents. +# https://github.com/benjitaylor/agentation +# +# The toolbar appears automatically in development with no configuration. +# Uncomment lines below to customize. +Agentation.configure do |config| + # MCP sync server endpoint (default: "http://localhost:4747") + # config.endpoint = "http://localhost:4747" + + # Webhook URL for annotation events + # config.webhook_url = "https://example.com/hooks/agentation" + + # Disable copy-to-clipboard (default: enabled) + # config.copy_to_clipboard = false + + # Force enable/disable (default: auto-detects Rails.env.development?) + # config.enabled = true +end diff --git a/rails/package.json b/rails/package.json new file mode 100644 index 00000000..d2b7585a --- /dev/null +++ b/rails/package.json @@ -0,0 +1,24 @@ +{ + "name": "agentation-rails-build", + "version": "0.0.1", + "private": true, + "description": "Build pipeline for agentation-rails gem — bundles React + Agentation into a standalone IIFE", + "scripts": { + "build": "tsup", + "watch": "tsup --watch" + }, + "dependencies": { + "agentation": "workspace:*", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "postcss": "^8.5.6", + "postcss-modules": "^6.0.1", + "sass": "^1.97.2", + "tsup": "^8.0.0", + "typescript": "^5.0.0" + } +} diff --git a/rails/src/standalone.ts b/rails/src/standalone.ts new file mode 100644 index 00000000..db44023f --- /dev/null +++ b/rails/src/standalone.ts @@ -0,0 +1,106 @@ +/** + * Agentation Standalone Entry Point + * + * Bundles React + Agentation into a single self-executing script. + * Used by the agentation-rails gem — Rails developers never see React. + * + * Dispatches CustomEvents on document so Rails developers can listen + * with Stimulus controllers or plain JS: + * + * document.addEventListener("agentation:add", (e) => { ... }) + * document.addEventListener("agentation:delete", (e) => { ... }) + * document.addEventListener("agentation:update", (e) => { ... }) + * document.addEventListener("agentation:clear", (e) => { ... }) + * document.addEventListener("agentation:copy", (e) => { ... }) + * document.addEventListener("agentation:submit", (e) => { ... }) + * document.addEventListener("agentation:session", (e) => { ... }) + */ +import React from "react"; +import { createRoot, type Root } from "react-dom/client"; +import { PageFeedbackToolbarCSS } from "agentation"; +import type { Annotation } from "agentation"; + +let root: Root | null = null; +let container: HTMLElement | null = null; + +function dispatch(name: string, detail: unknown) { + document.dispatchEvent( + new CustomEvent(`agentation:${name}`, { detail, bubbles: true }) + ); +} + +function getConfig(): Record