2025-11-04 ☼ ai ☼ vibe coding
As vibe coding grows in popularity, there are domains where we want to narrow what users can build. Instead of giving a blank canvas, we can offer an opinionated set of well-defined primitives that combine into predictable, safe applications. Think of it less like traditional software development and more like HyperCard: flexible, but within bounds.
Even in these constrained environments, non-technical users still need a way to express custom logic. That is the problem Vibescript tries to solve. Vibescript is a Ruby-like scripting language designed to be easy to read and easy for AI to vibe code. The interpreter is written in Go and can be embedded directly into any Go application.

Vibescript is an embeddable language with a small, practical standard library. It favors readability, predictable evaluation, and host-controlled capabilities. You decide which actions the script can perform. The script stays in its lane.
The language looks like Ruby at a glance: methods, blocks, symbols, arrays, and hashes. It borrows the parts that make everyday automation feel natural, without pulling in a sprawling runtime.
Functions
def total_with_fee(amount)
amount + 1
end
total_with_fee(99) # => 100
Literals and collections
# numbers, strings, symbols
5
"hello"
:signed
# arrays
[1, 2, 3].map { |n| n * n } # => [1, 4, 9]
# hashes with symbol keys
user = { :id => 42, :name => "Ava" }
user[:name] # => "Ava"
Blocks and enumerable helpers
# select, map, reduce
scores = [3, 10, 7, 10, 5]
top = scores.select { |s| s >= 7 } # => [10, 7, 10]
sum = scores.reduce(0) { |acc, s| acc + s } # => 35
avg = sum / scores.length # integer math for now
Control flow
def fib(n)
if n <= 1
n
else
fib(n - 1) + fib(n - 2)
end
end
Ranges and loops
1..5 # inclusive range
(1..5).each { |n| print(n) } # prints 12345
Durations and money
# examples focus on readable business logic
2.hours.to_seconds # planned helpers for derived values
money_cents(1299) + 200 # => 1499
The repo ships categorized examples for basics, collections, control flow, blocks, hashes, loops, ranges, money, durations, errors, and more in examples/. Some directories sketch future features and background jobs to track interpreter progress.
Vibescript does not reach outside the sandbox unless you let it. The host app passes data and capabilities into a call. Scripts use what they are handed and nothing else.
At the API level, you compile a script, then call functions with CallOptions. You can seed Globals for pure values or register typed adapters through Capabilities. The README’s quick start shows the basic shape and explains this integration point.
package main
import (
"context"
"fmt"
"vibescript/vibes"
)
func main() {
engine := vibes.NewEngine(vibes.Config{})
// Compile inline or load from a .vibe file
script, err := engine.Compile(`
def greet(name)
"Hello, #{name}"
end
`)
if err != nil {
panic(err)
}
// Call a function with arguments and optional capabilities
res, err := script.Call(
context.Background(),
"greet",
[]vibes.Value{vibes.NewString("world")},
vibes.CallOptions{
// Globals: map[string]vibes.Value{...}
// Capabilities: map[string]any{...} // host adapters
},
)
if err != nil {
panic(err)
}
fmt.Println(res.String()) // Hello, world
}
This is the pattern: compile, pass in values and capabilities, call a named function, get a strongly-typed result back.
A lot of useful automation is enumerable work: filter a collection, transform items, reduce into a report. Blocks make that feel compact. The language leans on readable block forms for day-to-day data shaping.
orders = [
{ :id => 1, :cents => 1299, :status => :paid },
{ :id => 2, :cents => 499, :status => :void },
{ :id => 3, :cents => 2599, :status => :paid }
]
paid = orders
.select { |o| o[:status] == :paid }
.map { |o| o[:cents] }
.reduce(0) { |acc, c| acc + c }
money_cents(paid) # => 3898
Use assert for simple guardrails. Fail fast and keep scripts honest.
def apply_discount(cents, pct)
assert(pct >= 0 && pct <= 100, "discount must be 0..100")
cents - (cents * pct / 100)
end
The vision is to pair readable scripts with host-level policies. The host declares what is allowed: which capabilities exist, which inputs are trusted, and which operations require a check. Scripts remain simple. The platform stays safe.
You can see the direction in the examples/policies/ and examples/capabilities/ folders, along with notes pointing to future background jobs and events as host integrations mature.
Vibescript thrives when you need:
It is not trying to be a general purpose language with a giant package ecosystem. It is a tool for shaping small automations inside bigger systems.
The README has a quick start with a tiny Go program. Scripts can live in .vibe files or be embedded inline. Examples are grouped by topic under examples/. Long-form guides live in docs/ and cover arrays, hashes, control flow, blocks, and integration.
# run tests and linters in the repo
just test
just lint
The repository keeps stretch examples around to guide development. That includes background jobs, events, richer duration helpers, and more complete money handling. Those examples exist to keep the interpreter honest as features land.
Vibescript is a small language with a specific goal: let people shape useful behavior inside safe bounds. If that sounds like the missing layer between your product and your users, try it in a small corner of your app and grow from there.