Omnigraph
Graph engine for context assembly. Object-storage native. Agents work with graph data the way engineers work with code: branched, typed, reviewed, merged, audited, and governed.
Semantic SQL for vanilla Postgres. Put jev() in any query from the CLI, the SDKs or an agent over MCP. The database only ever sees plain SQL. No extension, no proxy.
DataGo · TypeScriptGitHub @kylemclarenOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Semantic SQL for Postgres, powered by Jev
Translation pending
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
Semantic SQL for vanilla PostgreSQL. One extra family of functions, jev(), works in any query, from the CLI, from a shared HTTP and MCP node, or from the Go, TypeScript and Python SDKs.
The database only ever sees ordinary SQL. jev_* calls are evaluated by the
CLI using TypeSafe's System One model (Jev). No
CREATE EXTENSION, no superuser, no wire-protocol proxy.
Prebuilt binaries for macOS and Linux (arm64 and amd64) are attached to each
GitHub release as
jevql_ _ _ .tar.gz with a checksums.txt.Read the full README ↗Semantic SQL for vanilla PostgreSQL. One extra family of functions, jev(), works in any query, from the CLI, from a shared HTTP and MCP node, or from the Go, TypeScript and Python SDKs.
SELECT name, city, jev_prob(people, 'could work from home') AS p
FROM people
WHERE jev(people, 'could work from home')
AND country = 'PT'
ORDER BY p DESC
LIMIT 20;
The database only ever sees ordinary SQL. jev_* calls are evaluated by the
CLI using TypeSafe's System One model (Jev). No
CREATE EXTENSION, no superuser, no wire-protocol proxy.
Homebrew (macOS and Linux):
brew install kylemclaren/tap/jevql
Prebuilt binaries for macOS and Linux (arm64 and amd64) are attached to each
GitHub release as
jevql_<version>_<os>_<arch>.tar.gz with a checksums.txt.
From source, with Go 1.23+ and a C compiler (libpg_query is bundled via
pg_query_go and needs CGO):
CGO_ENABLED=1 go install github.com/kylemclaren/jevql/cmd/jevql@latest
The first build compiles libpg_query and takes about a minute. On macOS,
xcode-select --install provides the compiler. If the build complains about
CGO, make sure CGO_ENABLED=1 is set and that cc is on your PATH.
export TYPESAFE_API_KEY=tsk_...
export DATABASE_URL=postgres://user:pass@localhost:5432/app
jevql # REPL
jevql -c "SELECT * FROM people WHERE jev(people, 'could work from home')"
jevql -f query.sql
jevql --explain -c "..." # plan + cost estimate, no API calls
jevql "postgres://..." -c "SELECT 1" # plain statements pass straight through
Run jevql with nothing configured and it asks for a database URL and a
TypeSafe API key (hidden input) and offers to save them to
~/.config/jevql/env (mode 0600). That file holds KEY=VALUE lines and
only fills in environment variables that are not already set, so exported
variables and flags always win. TYPESAFE_API_URL can be added there too.
Inside the REPL, \set TYPESAFE_API_KEY tsk_... sets the key for the session.
Statements without jev_* are sent to Postgres unchanged. Any psql
connection style works: a URI, -h/-p/-U/-d, PG* environment variables, or
DATABASE_URL.
Any table with a text column is fair game. A few that are more fun than "could work from home":
-- Meetings that could have been an email
SELECT title, organizer
FROM meetings
WHERE jev(meetings, 'could have been an email')
AND starts_at > now();
-- Commit messages that are really apologies
SELECT left(sha, 7), message
FROM commits
WHERE jev(commits, 'is apologising for something')
ORDER BY committed_at DESC
LIMIT 20;
-- What is the reviewer actually upset about?
SELECT jev_choice(reviews, 'what is the reviewer really upset about?',
ARRAY['food', 'service', 'price', 'parking', 'other people']) AS gripe,
count(*)
FROM reviews
WHERE stars <= 2
GROUP BY 1
ORDER BY 2 DESC;
-- Rank the wine list by how insufferable the tasting note is
SELECT name, price,
jev_score(wines, 'how pretentious is the tasting note?',
ARRAY['plain', 'flowery', 'insufferable']) AS pretension
FROM wines
ORDER BY pretension DESC
LIMIT 5;
-- Job posts that are sales roles in disguise
SELECT title, company
FROM jobs
WHERE jev(jobs, 'is really a sales job despite the title')
AND posted_at > now() - interval '7 days';
-- Support tickets where the customer already turned it off and on again
SELECT id, subject
FROM tickets
WHERE jev(tickets, 'the customer says they already restarted it')
AND status = 'open';
-- Weeknight-feasible vegetarian dinners, best guesses first
SELECT name, jev_prob(recipes, 'can be cooked on a weeknight in under 40 minutes') AS p
FROM recipes
WHERE jev((name, ingredients), 'is vegetarian')
ORDER BY p DESC
LIMIT 10;
-- Pull requests by reviewer mood
SELECT jev_choice(prs, 'what is the overall tone of the review comments?',
ARRAY['nitpicky', 'blocking', 'rubber stamp', 'genuinely helpful']) AS mood,
count(*)
FROM prs
WHERE merged_at IS NULL
GROUP BY 1;
-- Slack messages that are passive-aggressive, by author
SELECT author, count(*) AS incidents
FROM messages
WHERE jev((author, text), 'is passive-aggressive')
AND sent_at > now() - interval '30 days'
GROUP BY author
ORDER BY incidents DESC;
Every one of these judges only the rows the SQL lets through, so the date and status filters matter for the bill. --explain shows the count before you pay.
The first argument is a FROM alias (people, or p in FROM people p).
All of that relation's columns are packed into a JSON row object and sent to
TypeSafe. To send fewer columns use the column-list form or --columns:
jev( (name, job_title, bio), 'could work from home' )
| Call | Returns |
|---|---|
jev(alias, 'condition') | boolean, p >= threshold |
jev(alias, 'condition', 0.7) | boolean with an explicit threshold |
jev_prob(alias, 'condition') | float8 in 0..1 |
jev_choice(alias, 'question', ARRAY['a','b']) | text |
jev_score(alias, 'question', ARRAY['lo','mid','hi']) | float8 weighted level index |
jev_score_norm(...) | float8 in 0..1 |
jev_confidence(alias, 'question' [, 'noul'|'choice'|'score', ARRAY[...]]) | float8 |
jev_eval(alias, 'question' [, kind, ARRAY[...]]) | raw answer JSON |
Threshold precedence: explicit argument, then --threshold, then
JEV_THRESHOLD, then 0.5. NOT jev(...) is supported as p < threshold.
A single SELECT (a WITH is fine as long as the CTE has no jev_*):
jev() as an AND term in WHERE, NOT jev() too.jev_* as a whole SELECT-list expression, and in ORDER BY.GROUP BY over jev_choice(...) (or any mix of jev / column keys) with
count(*), count(x), sum, avg, min, max. Aggregation happens in
the CLI, so sum/avg need numeric columns.ORDER BY, LIMIT, OFFSET. They are pushed to Postgres when the
collected rows are exactly the final rows (no jev filter, no grouping, no
jev sort key); otherwise they run in the CLI after judging.jev(p, ...) and jev(c, ...) on different aliases in one query.It errors with a message rather than guessing on: jev_* in
INSERT/UPDATE/DELETE/DDL, in a CTE or subquery, in HAVING, under a window
function, OR jev(...), comparisons like jev_prob(...) > 0.7 (use
jev(alias, q, 0.7)), SELECT DISTINCT, set operations, and tables with
bytea columns unless you narrow the columns.
--batch-size rows per request per
question and sent with --concurrency workers. Ctrl-C cancels in flight.--explain prints the collect SQL, the row count after SQL filters, batch
count, a rough token estimate and cost. It makes no TypeSafe requests.
-c string run one statement and exit
-f file run file and exit
-h host -p port -U user -d dbname -w / -W
--api-key else TYPESAFE_API_KEY
--api-url default https://api.typesafe.ai/v1/systemone (or TYPESAFE_API_URL)
--model default jev-latest
--threshold default 0.5 (or JEV_THRESHOLD)
--batch-size default 40
--concurrency default 6
--max-rows default 2500 abort before any HTTP call if collect exceeds this
--max-chars default 0 abort if row objects exceed this many chars (0 = off)
--cache path, default ~/.cache/jevql/cache.db
--no-cache
--explain plan + cost estimate, no TypeSafe calls
--timing like psql \timing
--csv / --json output format (default aligned table)
--expanded / -x like psql \x
-v verbose: collect SQL, batches, tokens, cost
--columns a,b columns to send for alias-form jev(alias, ...)
REPL meta commands: \q \timing \x \d [rel] \dt \dn \l \i file
\set JEV_THRESHOLD 0.7 \cache [stats|clear] \explain <query> help.
Other backslash commands print "not implemented" instead of being sent to
the server.
Exit codes: 0 ok, 1 SQL or usage error, 2 TypeSafe API or budget error.
After a jev query the REPL (or -v) prints a footer:
jev: 129 judged, 4 req, 82 cache hits, 21.0k in tokens, $0.0009, 1.02s
Answers are cached in sqlite (modernc.org/sqlite, pure Go) keyed by
sha256(model + kind + question + options + canonical_row_json). Canonical
JSON has sorted keys, RFC 3339 timestamps in UTC, no whitespace, and NULL
columns included as null. There is no expiry in v1; use \cache clear or
--no-cache.
--columns to send the minimum.TYPESAFE_API_KEY, --api-key, the interactive
prompt, or ~/.config/jevql/env, and is never printed. The saved config
file stores the key and the database URL (including its password) in plain
text with mode 0600; delete it if that is not acceptable. Point --api-url at a proxy if your environment injects
credentials.jev(); if
you send WHERE jev(...) through psql or JDBC it will fail with
"function jev(...) does not exist" until it goes through this CLI or a real
extension such as pg-jev.--explain to see how many rows
and dollars a query costs before running it. --max-rows (default 2500)
aborts before any HTTP call.0600 in ~/.cache/jevql, but it is not encrypted; delete it
with \cache clear if that matters.usage figures in the footer come from the API.The parser, rewriter, cache and TypeSafe client live in this Go module. Every
SDK reuses them rather than re-implementing SQL analysis. The wire format
shared by jevql serve, jevql --json-table and the SDKs is in sdk/PROTOCOL.md.
import jevql "github.com/kylemclaren/jevql/sdk/go"
client, err := jevql.New(ctx, jevql.Options{DatabaseURL: os.Getenv("DATABASE_URL")})
defer client.Close(ctx)
rows, err := client.QueryMaps(ctx,
"SELECT name, jev_prob(people, 'could work from home') AS p FROM people WHERE country = 'PT' ORDER BY p DESC LIMIT 5")
for _, r := range rows {
fmt.Println(r["name"], r["p"])
}
npm i jevql)The package bundles the jevql engine for your platform and starts a private one on localhost the first time you query. Nothing else to install.
import { Jevql } from "jevql"
const db = new Jevql() // embedded engine; reads DATABASE_URL, TYPESAFE_API_KEY
// const db = new Jevql({ url: "http://127.0.0.1:7433", token: "..." }) // a shared jevql serve
const tickets = await db.queryObjects(
"SELECT id, subject FROM tickets WHERE jev(tickets, 'is about billing') AND status = 'open'",
)
await db.close()
pip install jevql)from jevql import Jevql
with Jevql() as db: # embedded engine; or Jevql(url=..., token=...)
for row in db.query_dicts("SELECT title FROM movies WHERE jev(movies, 'a safe pick for a first date')"):
print(row["title"])
For a team, run one shared engine and point every client at it:
jevql serve --listen 0.0.0.0:7433 --token secret # behind your own TLS proxy
For an open demo node drop the token with --insecure (or JEVQL_INSECURE=1)
and fence it with --cors <origins>, --rate-limit <per minute per IP>,
--max-rows <n> and a read-only database role. That is how the public
playground at https://jevql.fly.dev/playground is served; deploy/ has a
Dockerfile and fly.toml for your own node.
jevql mcp is an MCP server for agents: tools query, explain, judge,
list_tables and describe_table, a jevql://sql-surface resource and a
semantic-query prompt. Read-only unless started with --allow-writes.
claude mcp add jevql -- jevql mcp # Claude Code
jevql serve --token secret # also mounts the same server at /mcp over HTTP
go build ./cmd/jevql
go test ./... # parse, rewrite (golden), typesafe mock, cache
PGTEST_URL=postgres://... go test ./internal/exec/ # loads testdata/people.sql into schema jevql_test
go test ./internal/rewrite/ -update # regenerate testdata/golden
Layout:
cmd/jevql/ main
internal/app/ flags, connection, REPL, meta commands, spinner
internal/parse/ pg_query walk, jev call extraction, statement analysis
internal/rewrite/ collect SQL builder and column layout
internal/exec/ two-pass execution: collect, judge, project
internal/typesafe/ HTTP client, batching, response parsing
internal/cache/ sqlite answer cache
internal/psqlout/ aligned / expanded / csv / json output
internal/canon/ canonical JSON for row objects
internal/stats/ the jev footer
testdata/ people.sql, queries/*.sql, golden/*.collect.sql
Prior art: realZachi/pg-jev (the
extension whose function names this mirrors) and
EugeneBoondock/jevsql (a
two-pass client).
Tag a version and push it: git tag v0.2.0 && git push origin v0.2.0. The
release workflow builds native CGO binaries for darwin/linux on arm64/amd64,
attaches them plus checksums.txt to a GitHub release, and regenerates
Formula/jevql.rb in kylemclaren/homebrew-tap
using scripts/brew-formula.sh. That step needs a HOMEBREW_TAP_TOKEN
repository secret with push access to the tap.