Getting it into your agent
One page per mod, every tool's command on it. A separate URL per tool would split the same page into five that compete with each other.
npx agentmods add skills/othmanadi/openui-forge/openui-forge-rubynpx skills add OthmanAdi/openui-forge --skill openui-forge-rubygit clone --depth 1 https://github.com/OthmanAdi/openui-forgeWhat it costs to keep this loaded
Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.
| Model | Per session | Once invoked |
|---|---|---|
| Fable 5 | $0.00038 | $0.02407 |
| Opus 5 | $0.00019 | $0.01203 |
| Sonnet 5 | $0.00008 | $0.00481 |
| Haiku 4.5 | $0.00004 | $0.00241 |
Grade A, and why
openui-forge-ruby scanned grade A with 0 findings against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 2d ago.
A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.
Nothing flagged
None of the 26 patterns this scan looks for appear in this file: no shell pipes, no recursive deletes, no credential paths, no hidden text, no instruction-override or anti-refusal phrasing, no agent-config snooping. That is not a guarantee, it is the absence of the things that are checkable.
How it starts
The opening of the file, as written. The whole thing — 238 lines — stays where its author put it; the contents beside it link to each section on GitHub.
OpenUI Forge — Ruby
Build generative UI apps with a React frontend + Ruby on Rails backend. Streams OpenAI API responses directly via ActionController::Live, forwarding OpenAI's native SSE with Net::HTTP.
Activation Triggers
- "openui ruby", "openui rails", "openui ruby backend"
- "generative ui ruby", "rails streaming ui backend"
Prerequisites
- Node.js >= 22 (24 LTS recommended) + React >= 18.3.1 (19+ recommended) (frontend)
- Ruby >= 3.2 + Rails 8.1.x (backend; run on Puma —
ActionController::Liveneeds a threaded server, not WEBrick) OPENAI_API_KEYenvironment variable set
Quick Start
- Create the React frontend and install OpenUI deps:
npm install @openuidev/react-ui @openuidev/react-headless @openuidev/react-lang lucide-react zod
- Generate the system prompt into the Rails app:
npx @openuidev/cli generate ./src/lib/library.ts --out config/system-prompt.txt
- Create the Rails backend (see Full Code below)
- Run:
bin/rails server -p 3001on:3001, frontend on:3000
Full Code
Backend: Gemfile
source "https://rubygems.org"
gem "rails", "~> 8.1"
gem "puma", ">= 6.0"
# Net::HTTP is in the standard library — no extra HTTP-client gem required.
# Optional: load OPENAI_API_KEY etc. from a .env file in development.
gem "dotenv-rails", groups: [:development, :test]
Backend: app/controllers/chat_controller.rb
require "net/http"
require "json"
require "uri"
class ChatController < ApplicationController
include ActionController::Live
skip_forgery_protection
before_action :set_cors_headers
before_action :handle_preflight, only: :create
OPENAI_BASE_URL = ENV.fetch("OPENAI_BASE_URL", "https://api.openai.com/v1").freeze
OPENAI_MODEL = ENV.fetch("OPENAI_MODEL", "gpt-5.5").freeze
# Loaded once at boot.
SYSTEM_PROMPT = Rails.root.join("config", "system-prompt.txt").read.freeze
# POST /api/chat { "messages": [{ "role": "...", "content": "..." }] }
def create
api_key = ENV["OPENAI_API_KEY"]
if api_key.nil? || api_key.empty?
render(json: { error: "OPENAI_API_KEY not set" }, status: :internal_server_error)
return
end
body = JSON.parse(request.body.read) rescue {}
incoming = body["messages"]
unless incoming.is_a?(Array) && !incoming.empty?
render(json: { error: "messages must be a non-empty array" }, status: :bad_request)
return
end
# Prepend the server-side system prompt; never trust a client-sent one.
messages = [{ "role" => "system", "content" => SYSTEM_PROMPT }]
incoming.each do |m|
next unless m.is_a?(Hash)
messages << { "role" => m["role"].to_s, "content" => m["content"].to_s }
end
# Headers MUST be set before the first write (the response commits on write).
response.headers["Content-Type"] = "text/event-stream"
response.headers["Cache-Control"] = "no-cache"
# Rails inserts Rack::ETag, which buffers the whole body and breaks
# streaming. Setting Last-Modified makes Rack::ETag pass the body through.
response.headers["Last-Modified"] = Time.now.httpdate
# Defeat proxy buffering (nginx) so chunks reach the browser immediately.
response.headers["X-Accel-Buffering"] = "no"
uri = URI.parse("#{OPENAI_BASE_URL}/chat/completions")
payload = JSON.generate(
model: OPENAI_MODEL,
stream: true,
messages: messages,
)
# read_timeout: nil disables the default 60s per-read timeout; a streaming
# completion can pause longer than 60s between chunks and would otherwise
# raise Net::ReadTimeout and cut the response off mid-stream.
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https", read_timeout: nil) do |http|
upstream = Net::HTTP::Post.new(uri)
upstream["Content-Type"] = "application/json"
upstream["Authorization"] = "Bearer #{api_key}"
upstream["Accept"] = "text/event-stream"
upstream.body = payload
http.request(upstream) do |res|
unless res.code.to_i == 200
err = +""
res.read_body { |c| err << c }
response.stream.write("data: #{JSON.generate(error: "OpenAI returned #{res.code}: #{err}")}\n\n")
response.stream.write("data: [DONE]\n\n")
next
end
# Forward OpenAI's native SSE bytes verbatim. The upstream already emits
# `data: {chunk}\n\n` frames terminated by `data: [DONE]`, so we just
# write each chunk through and flush — no buffering, no reframing. This
# avoids splitting a frame that straddles a TCP read boundary, because
# the browser's adapter reassembles SSE frames itself.
res.read_body do |chunk|
response.stream.write(chunk)
end
end
end
rescue IOError, Errno::EPIPE
# Client disconnected mid-stream — nothing more to send.
rescue => e
Rails.logger.error("[chat] stream error: #{e.class}: #{e.message}")
begin
response.stream.write("data: #{JSON.generate(error: e.message)}\n\n")
response.stream.write("data: [DONE]\n\n")
rescue IOError, Errno::EPIPE
# client already gone
end
ensure
# ALWAYS close, or the socket leaks for the lifetime of the worker.
response.stream.close
end
private
# Lock CORS to the single configured frontend origin (NOT "*"): the request
# is credentialed-capable and a wildcard would let any site spend your key.
def set_cors_headers
response.headers["Access-Control-Allow-Origin"] = ENV.fetch("FRONTEND_ORIGIN", "http://localhost:3000")
response.headers["Vary"] = "Origin"
response.headers["Access-Control-Allow-Methods"] = "POST, OPTIONS"
response.headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization"
end
def handle_preflight
head(:no_content) if request.method == "OPTIONS"
end
end
What this file has done since we first saw it
Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.
- 2d ago First seen · 238 lines · 38 tokens per session scan A 014f97daca47
openui-forge-ruby is a skill published in the GitHub repository OthmanAdi/openui-forge (22 stars, last pushed 29d ago), licensed MIT. It adds 38 tokens to every session and 2,407 once invoked, about $0.0002 per session on Opus 5. A static security scan graded it A with 0 findings. No closer match exists in the catalogue, so it is treated as the original; first seen 2026-08-30.
Other skills, from other repositories
openbot-data-access
Governs how the OpenBot browser app reads and writes server data — every request goes through client in app/src/lib/client.ts, every read is a queryOptions factory in app/src/lib/ /queries.ts, every write is a mutationOptions factory in app/src/lib/ /mutations.ts, and components consume them through…
openbot-screen-layout
The default layout for every OpenBot configuration screen — PageShell and its prose/wide widths, PageSection and PageRows, Item row composition, the settings-row pattern where a summary and a chevron open a dialog, and the size and variant vocabulary. This is what a new screen looks like unless an instruction says…
dd-code-generation
Use pup CLI for immediate Datadog operations or generate code for integration into applications.
redux-to-swr
Migrate React components from Redux + Saga to SWR hooks. Use when converting data fetching from Redux store (reducers, sagas, selectors, connect HOC) to SWR-based hooks in CockroachDB DB Console or cluster-ui.
mma-investigator
Expert system for investigating MMA (Multi-Metric Allocator) behavior on CockroachDB clusters. Helps oncall engineers diagnose load imbalances, understand rebalancing decisions, and identify why MMA did or didn't act.
migrate-state-management
Migrate Redux or React Context to the correct state option (React Query for server state, nuqs for URL/shareable state, Zustand for global client state). Use when refactoring away from Redux/Context, moving state to the right store, or when the user asks to migrate state management.