CLI#
Commands#
janux dev [--port 3000] # Vite dev server: SSR, HMR, api stubs, agent endpoint
janux build # client bundle + styles + public/ → dist/client (+ prerendered HTML with output: "static")
janux start [--port 3000] # production server on Bun (no Vite at runtime)
janux verify # agent-surface contract checks (CI-friendly)
janux eval [files...] # scripted agent-task scenarios against a live appPORT env is honored when --port is absent.
janux start serves dist/client before falling back to the app: compressed with brotli (or gzip, whichever the request accepts), each file compressed once and kept in memory, and cached immutable for a year when its name carries a content hash. Behind a CDN that already does this, it costs nothing; on a box without one, it is the difference between shipping a bundle and shipping four of them.
janux verify#
Renders every route's manifest and fails (exit 1) when an agent-reachable tool
— an intent() or api() whose guard is not forbidden — has no
description. Descriptions are the contract agents plan against; an
undescribed tool degrades every conversation silently, so it fails the build
instead. Routes that throw during render are reported as warnings (their
surface cannot be verified).
janux eval#
"Can an agent actually complete this task through my tools?" as a repeatable
CI check. Runs evals/**/*.eval.json (or explicit files) against a live app
and exits 1 on any failed expectation.
janux eval --start "janux start" # boots the app, runs, stops it
janux eval --url http://localhost:3000 # against a server you manage
janux eval evals/checkout.eval.json --json{
"name": "shop agent checkout",
"steps": [
{ "tool": "api.shop.catalog", "expect": { "result": { "products": [{ "id": "p1" }] } } },
{ "tool": "api.shop.pay", "input": { "total": 5999 },
"expect": { "result": { "status": "proposal" } } },
{ "approve": "$steps[1].result.id", "expect": { "result": { "charged": 5999 } } }
]
}Steps run in order with x-janux-origin: agent. $steps[i].<path> references
resolve against earlier outcomes ({ status, ok, result, error }) anywhere in
input or approve. An approve step exercises the real human-in-the-loop
flow (POST /_janux/approve) — the same pipeline your UI uses. expect
checks any of ok (default true when omitted), status, error
(substring) and result (deep subset match).
create-janux#
bun create janux my-app # the starter app
bun create janux my-shop --example shop # start from any examples/ app--example <name> scaffolds a copy of one of the example apps (shop, i18n, interop-react, nested-islands, data-cache) instead of the starter template; omit the name to list them. bunx create-janux is the same command.
The starter template scaffolds the conventional layout with a resumable counter island, an agent panel and an example unit test.
Project conventions#
Everything is convention over configuration — each of these is optional:
| Path | Purpose |
|---|---|
src/routes/** |
File-system routing (index.tsx → /, [id].tsx → :id) |
src/server/*.api.ts |
api() modules → endpoints + client stubs + agent tools |
src/stores.ts |
Store defs available during SSR |
src/agent.ts |
export default defineAgent({...}) |
src/i18n.ts (or src/i18n/index.ts) |
export default an I18nConfig — activates internationalization |
src/ctx.ts |
export default a (req) => ctx — per-request context and auth |
src/middleware.ts |
export default a (req) => Response | undefined — runs before routing |
src/matchers.ts |
Named exports = custom [param=matcher] matchers |
src/client.ts |
boot({ defs }) — omit for fully static apps (0 KB JS) |
src/styles.css |
App stylesheet, linked automatically |
public/ |
Static assets served at / (favicon.svg auto-linked) |
janux.config.ts |
Optional app config (title, llmsTxt, output, …) — same shape as the Vite plugin options, which win over it |
// janux.config.ts
import { defineConfig } from 'janux';
export default defineConfig({
llmsTxt: { title: 'My App', description: 'What agents should know.' },
output: 'static',
});A
"janux"field inpackage.jsonstill works as a deprecated fallback;janux.config.tswins over it.
All config fields#
Everything is optional — the defaults are the conventional layout. Override a field only to move things off-convention. The Vite plugin accepts the same shape and wins over janux.config.ts.
| Field | Default | Purpose |
|---|---|---|
title |
— | Default document title / shell title |
lang |
'en' |
<html lang> for the whole app. An i18n app ignores it: each page declares its own locale and direction |
siteUrl |
— | Public origin (https://janux.dev). Resolves a route's relative image/canonical into the absolute URLs Open Graph needs (see PageMeta), and opts into /sitemap.xml + /robots.txt |
llmsTxt |
off | { title?, description? } — opt into serving GET /llms.txt |
inlineStyles |
false |
Inline the built stylesheet into every page instead of linking it: one less render-blocking round trip before the first paint, at the cost of a cacheable request. Production only — dev keeps the link so CSS hot-reload works |
output |
'bun' |
'bun' or 'static' — see output |
routesDir |
src/routes |
File-system routing root |
serverDir |
src/server |
Where *.api.ts modules are discovered |
clientEntry |
src/client.ts |
Client boot() entry; absent → fully static app, 0 KB JS |
agentModule |
src/agent.ts |
defineAgent() default export; absent → the built-in default agent |
storesModule |
src/stores.ts |
Store defs available during SSR |
output#
| Value | Meaning |
|---|---|
"bun" (default) |
janux start serves the app on a Bun server |
"static" |
janux build also prerenders every page into dist/client (/docs/x → docs/x/index.html, plus llms.txt) — deploy to any static host, no server. Dynamic routes need staticParams (Route modules); those without it are skipped with a warning |
More output targets will come later. Full walkthrough: Deploying → Static export.
Programmatic use#
@janux/cli is also a module: runCli(argv) is what bin.ts calls, parseArgs(argv, cwd) parses a command line into { command, root, port, … }, and HELP_TEXT is the usage string.
import { createJanuxServer } from '@janux/server';
import { prodServerOptions } from '@janux/cli';
const server = createJanuxServer(await prodServerOptions(process.cwd()));prodServerOptions(root) resolves an app's conventions into the ServerOptions that janux start uses — routes, *.api.ts modules, stores, agent, i18n, per-request ctx, middleware, matchers, src/api/** handlers, the built client.js and stylesheet. Spread it to override individual fields. It expects janux build to have run (that's where dist/client/client.js comes from) and it does not serve static files: see custom server.
Environment#
| Variable | Purpose |
|---|---|
JANUX_MODEL |
provider/model for the copilot |
ANTHROPIC_API_KEY / OPENAI_API_KEY / GOOGLE_GENERATIVE_AI_API_KEY |
Provider auth (also drives model sniffing) |
PORT |
Server port |