Skip to main content

Turing CLI

@viglet/turing-cli gives you the turing command — a developer CLI for building, deploying and testing Viglet Turing ES agents as code in a Git repo instead of clicking through the admin console. It's zero-dependency (compiled ESM, tsc-only) and needs a modern Node runtime (see the package's engines).

When you'd reach for it. Use the CLI when you want an agent-as-code workflow: keep your agent definition, chat flows, custom tools, skills and regression tests in version control; boot a local Turing stack to iterate; and deploy the whole thing to local / staging / production from a script or CI pipeline. It pairs naturally with @viglet/turing-flow-dsl (author flows as typed TypeScript that compile to the flows/*.chat-flow.json the CLI deploys).

The five commands at a glance:

CommandWhat it does
turing init <name>Scaffold a new agent project.
turing devBoot a local Turing stack via Docker Compose (--watch = hot redeploy).
turing deployPush agent + flows + tools + skills to an environment.
turing evalRun *.eval.yaml regression suites (exits non-zero on failure → CI-ready).
turing logsTail live chat events (SSE) for a conversation.
turing migrateImport an Elasticsearch/Algolia index into an SN site (schema + records).
turing researchDefine/run a Synthetic User Research study and fetch its insights (CI-ready).

Install

npm i -g @viglet/turing-cli # global `turing` binary
# or run without installing:
npx @viglet/turing-cli init my-copilot

Your first project (5 minutes)

turing init my-copilot # 1. scaffold the project
cd my-copilot
turing dev # 2. boot a local Turing stack (Docker Compose)
turing deploy --env=local # 3. push agent + flows + tools + skills
turing eval # 4. run the YAML regression suites in evals/

That's the full loop: scaffold → run → deploy → test. Everything below is detail on each step.


Project layout

turing init <name> scaffolds a project you commit to Git:

PathPurpose
agent.jsonThe AI Agent definition — title, system prompt, flags, native tools.
flows/*.chat-flow.json chat flows. Author them with @viglet/turing-flow-dsl.
tools/*.groovy custom-tool scripts (+ an optional <name>.tool.json sidecar for metadata).
skills/Anthropic-compatible skill folders — one per subdirectory, each with a SKILL.md.
evals/*.eval.yaml regression suites (a sample smoke.eval.yaml is scaffolded).
turing.config.jsonInstance URL(s) per environment + the deployed agent id. Never secrets.
docker-compose.dev.ymlThe local stack turing dev brings up.

Configuration & authentication

Connection settings resolve in this order (last wins): turing.config.json → environment variables → command flags.

turing.config.json

Holds only non-secret settings — the project name, the deployed agentId (auto-filled after the first deploy), and a URL per environment:

{
"name": "my-copilot",
"agentId": "",
"default": "local",
"envs": {
"local": { "url": "http://localhost:2700" },
"staging": { "url": "https://staging.turing.example.com" }
}
}

Credentials

Credentials are never written to the config file — supply them via the environment or flags. Two auth modes:

  • Dev token (preferred for CI): set TURING_TOKEN (or --token). Sent as the Key: header. No prompt.
  • HTTP Basic (interactive): set TURING_USERNAME (or --username); the password comes from TURING_PASSWORD, --password, or an interactive hidden prompt. Fails on a non-TTY (CI) if no password env var is set.
Env varFlagUse
TURING_URL--urlOverride the target instance URL.
TURING_ENV--envDefault environment name.
TURING_TOKEN--tokenDev token for unattended / CI use.
TURING_USERNAME--usernameUsername for basic auth.
TURING_PASSWORD--passwordPassword for basic auth.
TURING_AGENT_ID--agentAgent id override (rarely needed).

Under the hood the CLI primes a CSRF token (GET /api/csrf) and carries the session cookie across requests.


Commands

turing init <name> [--dir <path>] [--force]

Scaffold a new agent project directory.

  • <name> — project name (required).
  • --dir <path> — target directory (default ./<name>).
  • --force, -f — overwrite an existing folder.
turing init my-copilot
turing init my-copilot --dir ./projects/ai --force

turing dev [--file <compose>] [--detach] [--watch]

Boot a local Turing stack via docker compose up. It resolves docker-compose.ymldocker-compose.yamldocker-compose.dev.yml (first match wins).

  • --file <compose> — use a specific compose file.
  • --detach, -d — run in the background.
  • --watch — detach the stack, then redeploy the project on file changes (watches agent.json, flows/, tools/, skills/; 400 ms debounce; Ctrl-C to stop).
turing dev # foreground
turing dev --watch # hot reload while you edit
turing dev --file docker-compose.prod.yml --detach

turing deploy [--env <name>] [connection flags]

Push the whole project to an environment. The default env resolves --envTURING_ENVturing.config.json's default"local".

What it deploys, and how it stays idempotent:

  • AgentPOST /api/ai-agent on first deploy, then PUT /api/ai-agent/{id} after. The new id is written back to turing.config.json.
  • Flows — every flows/*.chat-flow.json pushed atomically via POST /api/ai-agent/{id}/chat-flow/import-bundle.
  • Custom tools — every tools/*.groovy (+ optional <name>.tool.json sidecar); matched by title → PUT if it exists, else POST. A file my-tool.groovy auto-titles to "My Tool".
  • Skills — each skills/<name>/ folder (must contain SKILL.md) is zipped and uploaded to POST /api/skill/import.
turing deploy --env=local
turing deploy --env=staging --token "$TURING_TOKEN"
turing deploy --url http://localhost:2700 --username admin --password secret

turing eval [path] [--watch] [connection flags]

Discover, run and report *.eval.yaml / *.eval.yml / *.eval.json suites (defaults to ./evals/). Each fixture replays a scripted conversation against the live agent and checks assertions. Exits non-zero if any fixture fails, so it drops straight into CI. --watch re-runs on change.

turing eval # run everything in evals/
turing eval evals/smoke.eval.yaml # a single suite
turing eval --watch --env=staging

An eval suite is a list of fixtures, each a sequence of user turns and assert.* checks:

fixtures:
- id: greeting
steps:
- user: "Hello, can you help me?"
- assert.assistant.matches: ".+"
- assert.persona.forbidden: ["I cannot help", "I'm just an AI"]
- id: capture-email
steps:
- user: "I want the brochure — my email is ada@example.com"
- assert.slot.email: "ada@example.com"
- assert.tool_called: "save_lead"

Available assertions (both the flat dotted form above and a nested assert: block are accepted):

AssertionChecks
assistant.matches / not_matchesRegex over the streamed reply.
assistant.contains / not_containsSubstring over the reply.
slot.<name>A captured slot value.
tool_called / tool_not_calledWhether a tool ran (via the slot-audit TOOL trail).
persona.required / persona.forbiddenRequired / forbidden phrases (case-insensitive).
nodeThe flow cursor position (via /state).

Related: the console has its own pre-publish Agent Eval gate (golden sets + LLM judge). The CLI's YAML evals are the code-first, CI-runnable counterpart — the same idea, checked into your repo.

turing eval record <conversationId> [--out <file>]

Capture a real conversation from a running instance into a starter eval suite. It pulls the full snapshot (GET /api/chat/sessions/{id}/export), turns visitor turns into user: steps and the captured slots into a final assert.slot.* block, ready for you to tighten by hand.

turing eval record abc-123-def-456 --out evals/from-prod.eval.yaml

turing eval --dataset <id|name> [--stack <id|name>] [--min-score <n>]

Run a stored eval dataset against a grader stack on the server, instead of local YAML fixtures. This is the CLI face of POST /api/eval/run: the dataset (reusable datasets) and the grader stack both live in the Turing instance and are referenced by id or name, so one dataset gates any number of pipelines without checking fixtures into each repo. Omit --stack to use the default stack (slot / outcome / node / rubric).

The command replays every dataset row against the agent (--agent / TURING_AGENT_ID), scores it with the stack, prints a per-case summary, and exits non-zero when the gate fails. The gate passes when every case passes and — if you set --min-score — the aggregate score is at least that threshold (0–1).

turing eval --dataset "Golden Leads" --agent abc-123 --min-score 0.8
turing eval --dataset ds-42 --stack "Strict RAG" --env=staging

The run is recorded in the agent's history and appears on the Eval Studio timeline, but it never becomes the agent's pre-publish baseline — an ad-hoc dataset run can't disturb the golden-set gate.

turing logs --conversation <id> [--slots] [connection flags]

Tail live chat events for a conversation over the spectator SSE stream — first a transcript snapshot, then each new turn as it happens. --slots also relays slot updates. Ctrl-C stops.

turing logs --conversation abc-123-def-456
turing logs --conversation abc-123-def-456 --slots

turing migrate elasticsearch|algolia … [connection flags]

Import a source search index into a Turing SN site — the automated "step 2" of switching from Elasticsearch or Algolia. Turing reads the source schema, derives a field manifest, provisions the site, and imports every record. Add --dry-run to preview the derived schema without changing anything.

# Elasticsearch — URL + credentials + index only, no vendor SDK
turing migrate elasticsearch \
--source-url https://es.example.com:9200 \
--source-user elastic --source-password "$ES_PASSWORD" \
--index products --site Products --se-instance <seInstanceId> --dry-run

# Algolia — schema inferred from a record sample + index settings
turing migrate algolia \
--app-id "$ALGOLIA_APP" --api-key "$ALGOLIA_KEY" \
--index catalog --site Catalog --se-instance <seInstanceId> --use-llm
FlagEnginePurpose
--indexbothSource index name (required).
--sitebothTarget SN site name (required).
--se-instancebothSearch-engine instance id (required only to create a new site).
--source-urlESSource cluster URL (required).
--source-user / --source-password / --source-api-keyESSource authentication.
--app-id / --api-keyAlgoliaApplication id + a read-capable API key (required).
--use-llmAlgoliaRefine the inferred schema with a configured LLM.
--overrides-file <path>bothJSON array of field-mapping overrides (rename / retype / drop / default).
--locale · --batch-size · --max-documents · --dry-runbothTuning + preview.

Synonyms found on an Algolia index are reported so you can apply them in your target engine (Solr / Elasticsearch); native synonym management in Turing is on the roadmap.

Reshape fields on the way in — a source schema is rarely 1:1 with what you want. --overrides-file points at a JSON array that renames, retypes, drops, or defaults fields (applied to both the manifest and every document):

[
{ "field": "cost", "rename": "price", "type": "CURRENCY" },
{ "field": "internal_notes", "drop": true },
{ "field": "source", "type": "STRING", "defaultValue": "algolia-import" }
]

turing migrate compare --engine <e> --index <i> --site <s> --queries-file <path>

Before you flip traffic, measure relevance parity: run the same queries against the source engine and the migrated SN site and diff the top-N result sets. Read-only on both sides.

turing migrate compare \
--engine elasticsearch \
--source-url https://es.example.com:9200 --source-user elastic --source-password "$ES_PASSWORD" \
--index products --site Products \
--queries-file ./queries.txt --rows 10

--queries-file is one query per line (blank lines and # comments ignored). The report gives, per query and in aggregate, the set overlap (Jaccard), how much of the source's top-N Turing reproduced (source recall), and how often the #1 result matches.

turing research — define, run & fetch a study

Drive Synthetic User Research from code or CI, mirroring turing eval. Subcommands take the same connection flags and resolve the instance the same way.

turing research list # studies on the instance
turing research run <studyId> --force # run the cohort interviews (blocking)
turing research insights <studyId> # print the synthesized report
turing research rollup <studyId> <studyId> # cross-study program rollup (PRISMA)
  • list — id, name, protocol, participant/interview counts, last-run.
  • run <studyId> [--force] — runs the cohort (blocking) and reports the resulting interview count. Exits non-zero when no interview ran, so a CI job can gate on it.
  • insights <studyId> — prints the executive summary, ranked themes (with participant counts) and recommendations; exits non-zero when the report isn't available yet.
  • rollup <studyId> [<studyId>…] — program totals + the themes that recur across the selected studies.

The customer-facing embed SDKs (@viglet/turing-sdk, @viglet/turing-react-sdk) stay focused on search & chat — running studies is an admin/research capability, so it lives here in the CLI.

turing version · turing help

turing version (or --version/-v) prints the version; turing help (or --help/-h, or no args) prints usage.


Using it as a library

The CLI's internals are also exported from @viglet/turing-cli so you can embed them — e.g. TuringClient, resolveConnection, scaffoldFiles, deployProject, runSuite/evaluate (with a custom EvalBackend), runEval (the programmatic dataset × grader stack CI gate, returning { gatePassed, report }), parseYaml, buildZip. Handy for building your own tooling on the same primitives.

import { TuringClient, runEval } from "@viglet/turing-cli";

const client = new TuringClient("https://turing.example.com", { kind: "token", token: process.env.TURING_TOKEN! });
await client.authenticate();
const result = await runEval(client, { agentId: "abc-123", dataset: "Golden Leads", minScore: 0.8 });
if (!result.gatePassed) process.exit(1);

  • Flow DSL — author the flows/*.chat-flow.json the CLI deploys, as typed TypeScript.
  • AI Agents — what agent.json configures.
  • Custom Tools — the .groovy scripts in tools/.
  • Skills — the skill folders in skills/.
  • Agent Eval — the console-side eval gate the CLI evals complement.
  • JavaScript SDK · React SDK — the runtime clients your app uses once deployed.