CLI reference

Reference · v0.0.1

Bela CLI reference

The @bela/cli package ships one binary, bela, with three real subcommands — init, train, eval — plus help. All flag names and defaults below are verified against packages/cli/src/cli.ts.

Today vs. in-repo dev. Bela is live on npm as bela-ai (the unscoped name bela is blocked by npm's name-similarity filter; the CLI bins still install as bela and bela-mcp), so npm install bela-ai then npx bela … works today for consumers. Inside this monorepo, however, the CLI's init/train/eval functions are exercised via packages/cli/test/cli.test.ts (vitest) or run with npx tsx against packages/cli/src/cli.ts directly, because the workspace packages (@bela/core, @bela/model) resolve to TypeScript source ("main": "src/index.ts") rather than a compiled dist/.

A plain node packages/cli/bin/bela.mjs … invocation from inside the repo only works end-to-end once the packages are built (npm run build). This page documents the CLI's real behaviour either way; the worked session below was run through the actual runInit/runTrain/runEval functions, not hand-written.

bela init [dir]

Scaffolds a starter agent.tools.mjs in dir (defaults to the current directory; also accepts --dir <path>).

FlagDefaultNotes
[dir] (positional)cwdCurrent working directory. Ignored if it starts with --.
--dir <path>Overrides the positional argument if both are given.
terminal
$ bela init ./my-agent
created ./my-agent/agent.tools.mjs

Exit code 0 on success; non-zero (uncaught error printed to stderr) if the file already exists or the directory can't be created.

bela train --config <path>

Trains a model from a config file and saves it if it clears the quality gate.

FlagDefaultNotes
--config <path>requiredPath to a config module whose default export is a defineAgent(...) result. Throws --config is required if omitted.
--out <path>bela-model.jsonWhere the trained artifact is written (only if the gate passes or --force).
--seed <n>42Passed to trainModel's dataset/weight RNG seeding.
--per-tool <n>200Examples synthesized per tool for training/test data.
--epochs <n>12Training epochs for both the intent classifier and slot tagger.
--min-intent <n>0.9Gate threshold for intentAcc (see meetsGate).
--min-slot <n>0.8Gate threshold for slotExact.
--forceoffAny value other than the literal string true is ignored — pass --force (parsed as "true" since it has no following value) to save even if the gate fails.

Behavior:

  1. Loads the config (import(configPath), so .mjs configs run directly; see the tsx note below for .ts configs).
  2. Runs trainModel(config, { seed, perTool, epochs }) and prints the metrics line: intentAcc=<n> slotExact=<n> testCount=<n>.
  3. Checks meetsGate(metrics, { intentAcc: minIntent, slotExact: minSlot }).
  4. If the gate passes, or --force is set, calls saveModel(model, out) and prints saved model to <out> (with a (gate failed, --force) suffix if it was forced).
  5. If the gate fails and --force wasn't given, nothing is saved and it prints gate failed (need intentAcc>=<n>, slotExact>=<n>); not saving. Use --force to override.

Exit code: the bela bin (packages/cli/bin/bela.mjs) calls process.exit(1) when train did not save (gate failed, no --force); otherwise exit code 0. Any thrown error (bad config path, --config missing) is caught at the top level, printed to stderr, and also exits 1.

bela eval --config <path> --model <path>

Re-evaluates a previously saved model against a freshly generated test set from the same config.

FlagDefaultNotes
--config <path>requiredSame config module used (or compatible with) training. Throws --config is required if omitted.
--model <path>requiredPath to a saveModel-produced JSON artifact. Throws --model is required if omitted.
--seed <n>42Seeds the regenerated dataset — must match the seed AND --per-tool used at training time to reproduce the same split.
--per-tool <n>200Examples synthesized per tool for the regenerated dataset — must match the value used at training time (--per-tool on bela train) to reproduce the same split; otherwise eval silently regenerates against a different-sized dataset.

Behavior: loads the config and model, re-collects entity values (collectEntityValues), regenerates a dataset with the given seed and --per-tool (generateDataset), evaluates the loaded model against the test split, and prints the same metrics line as train: intentAcc=<n> slotExact=<n> testCount=<n>.

As of P4, loadModel validates the artifact's shape before returning it — a corrupted or hand-edited model file fails fast with a message like invalid model artifact: intents must be a string array (also checked: version, params, tagLabels, positive-integer buckets/dim, and that each embedding/weight/bias array is exactly the expected length) — instead of crashing deep inside evaluate/predict with an opaque error.

Exit code 0 on success; a thrown error (missing flags, invalid model artifact, bad config) is caught at the top level and exits 1.

bela help

Prints the built-in usage text (also shown when no command, or an unknown command, is given):

bela help
bela — tiny self-training agent CLI

Usage:
  bela init [dir]              scaffold a starter agent.tools.mjs
  bela train --config <path>   train a model from a config
      [--out <path>] [--seed <n>] [--per-tool <n>] [--epochs <n>]
      [--min-intent <n>] [--min-slot <n>] [--force]
  bela eval --config <path> --model <path>   evaluate a saved model
      [--seed <n>] [--per-tool <n>]
  bela help                    show this message

Exit code 0.

.mjs vs. TypeScript configs

runInit scaffolds agent.tools.mjs, and both train/eval load the config with a plain dynamic import() of the given path (pathToFileURL(configPath).href) — no TypeScript transform happens in the CLI itself. That means:

Worked session

This is a real transcript — bela init, then train, then eval — run through the actual CLI functions (numbers are not fabricated; see intentAcc/slotExact/testCount below):

terminal — real transcript
$ bela init ./my-agent
created ./my-agent/agent.tools.mjs

$ vim ./my-agent/agent.tools.mjs   # replace the two TODO resolve() calls
                                    # with real queries against your DB/API

$ bela train --config ./my-agent/agent.tools.mjs --out ./my-agent/bela-model.json
intentAcc=0.9891 slotExact=1.0000 testCount=92
saved model to ./my-agent/bela-model.json

$ bela eval --config ./my-agent/agent.tools.mjs --model ./my-agent/bela-model.json
intentAcc=0.9891 slotExact=1.0000 testCount=92

The unedited starter config (two entities, two tools) already clears the default gate (intentAcc>=0.9, slotExact>=0.8) out of the box — real projects with more tools/utterances should expect similar or better numbers once entity resolve() is wired to real data, since more/varied rows give the model more to train and test on. If a change to your config drops metrics below the gate, train refuses to save (see above) — either fix your utterances/entities or explicitly --force a save while iterating.

Exit codes summary

Command01
initscaffold writtendirectory exists / can't write
traingate passed, or --force usedgate failed and no --force; or a thrown error (bad --config, bad config module)
evalevaluation printedmissing --config/--model, invalid model artifact, or bad config module
helpalways