After this guide an agent or a CI job can generate TutorFlow content from a shell with one command per step. The CLI, @tutorflow/cli 0.5.0, calls the same Content API routes as the earlier guides, waits for runs for you, and prints JSON when you ask.
Install
The CLI needs Node.js 18 or later.
0.5.0Give it your key
If you already have a Content key from Quick Start, put it in the environment:
export TUTORFLOW_API_KEY="tf_content_..."tutorflow auth login --api-key tf_content_... saves it to ~/.tutorflow/config.json instead. With no key yet, tutorflow agent register --name "My Agent" --slug my-agent --email ops@example.com creates an account and saves its key. See Agent Account and Billing CLI.
The CLI finds your default classroom from the account, so you do not pass a classroom id.
Generate a course and wait for it
tutorflow course generate "Spreadsheet basics for new office staff" \
-n 1 --level beginner \
--idempotency-key course:spreadsheets:v1 \
--wait--wait polls the run, prints each stage on stderr, and prints the result when it ends:
run 57b8...: running · outlining
run 57b8...: running · generating_lessons · 0/1 lessons
runId: 57b8...
status: completed
creditsCharged: 6
resourceId: 8e25...
title: Spreadsheet Basics for New Office Staff
editorUrl: https://tutorflow.io/org/my-agent/my-agent/courses/chat/3bc7.../lessons
publicUrl: https://my-agent.tutorflow.ac/courses/spreadsheet-basics-for-new-office-staffWithout --wait, the command prints the run and returns. Follow it later with tutorflow run wait <runId>, or resume a failed course run with tutorflow run resume <runId>. Add --json to any command for one JSON line on stdout. For the course from Generate Your First Content:
tutorflow run wait 6bff... --json{
"run": {
"runId": "6bff...",
"kind": "course",
"status": "completed",
"progress": { "completed": 1, "total": 1, "unit": "lesson" },
"resourceId": "5d2a...",
"creditsCharged": 6,
"estimatedCredits": 6,
"error": null
},
"resource": {
"id": "5d2a...",
"title": "Beginner Python for Adult Career Changers: Variables, Loops, and Functions",
"editorUrl": "https://tutorflow.io/org/my-agent/my-agent/courses/chat/09cf.../lessons",
"publicUrl": "https://my-agent.tutorflow.ac/courses/beginner-python-for-adult-career-changers-variables-loops-and-functions"
}
}run holds every field of the run and resource every field of the course; the example drops some for space.
Find and open what you made
tutorflow course list┌──────────────┬────────────────────────────────────────────────────────────────────────────┬────────┬────────────┐
│ ID │ Title │ Status │ Updated │
├──────────────┼────────────────────────────────────────────────────────────────────────────┼────────┼────────────┤
│ 8e25... │ Spreadsheet Basics for New Office Staff │ PUBLIC │ 2026-10-16 │
│ 5d2a... │ Beginner Python for Adult Career Changers: Variables, Loops, and Functions │ PUBLIC │ 2026-10-16 │
└──────────────┴────────────────────────────────────────────────────────────────────────────┴────────┴────────────┘tutorflow course open 5d2a... --json{
"id": "5d2a...",
"editorUrl": "https://tutorflow.io/org/my-agent/my-agent/courses/chat/09cf.../lessons",
"publicUrl": "https://my-agent.tutorflow.ac/courses/beginner-python-for-adult-career-changers-variables-loops-and-functions"
}open works the same for slide, test, module, video, game, and simulation. In an interactive terminal it also opens a browser: publicUrl by default, or editorUrl with --editor or when publicUrl is null. With --json it only prints. With a test key both links are null, and open exits 1 with ENOPREVIEW.
Send publicUrl to a person who should look at the result, and editorUrl to one who should change it. publicUrl stays null until the page is public; Let a person look at it lists the change each type needs. share makes that change and prints the new publicUrl:
tutorflow --json course share 5d2a... | jq -r .publicUrlA video or test is shared as UNLISTED, so it opens by link but stays out of lists and search; add --listed for PUBLIC. A slide deck, game, simulation, or course becomes PUBLIC, and a module gets isPublic: true. tutorflow <type> visibility <id> <value> sets any other value, such as PRIVATE. publicUrl can still be null after share until a video is rendered, a game or simulation is built, or a course is published, and always with a test key.
Grade an answer
tutorflow evaluate -t exact -q "What is 7 x 8?" -a "56" -e "56" --max-score 1 --learner-id learner-42 id: d0ef...
classroomId: 0fce...
status: COMPLETED
evaluationType: exact
mode: sync
isTerminal: true
learner: {"externalId":"learner-42","name":null}
conceptTags: []
score: 1
maxScore: 1
normalizedScore: 100
confidence: 1
strengths: ["Answer matches the expected response."]
mistakes: []
suggestions: []
feedbackSummary: Correct! Your answer matches the expected response.
createdAt: 2026-10-16T18:55:13.223Z
completedAt: 2026-10-16T18:55:13.268ZAn exact evaluation needs the reference answer in -e. Each evaluation costs 1 credit.
Check your balance
tutorflow --json credits balance{"credit":36,"overdraftCredit":0,"overdraftLimit":100,"availableCredit":36,"isPaymentFailed":false,"livemode":true}In scripts and CI
- Use
--json. Results go to stdout as one line, errors to stderr as one line withcode,status, and the API'sapiCode. - The exit status is
0on success,1for an API error, a failed run, or a--waittimeout, and2for a usage error. - Use a test key (
tf_content_test_) in CI. It works in the sandbox classroom and spends nothing. - Always pass
--idempotency-keyongenerate, so a retried job does not start a second run.
Reference
- CLI Overview: configuration, classroom choice, JSON mode, and exit codes.
- Content Generation CLI: every content command and flag.
- Agent Account and Billing CLI: register, account, and top-ups.
- Platform Discovery CLI: manifest, pricing, and key health.