@tutorflow/cli 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +246 -664
- package/dist/client.d.ts +16 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +76 -16
- package/dist/client.js.map +1 -1
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +38 -23
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +20 -12
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/classroom.d.ts +3 -0
- package/dist/commands/classroom.d.ts.map +1 -0
- package/dist/commands/classroom.js +27 -0
- package/dist/commands/classroom.js.map +1 -0
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +4 -4
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/course.d.ts.map +1 -1
- package/dist/commands/course.js +25 -96
- package/dist/commands/course.js.map +1 -1
- package/dist/commands/credits.d.ts +3 -0
- package/dist/commands/credits.d.ts.map +1 -0
- package/dist/commands/credits.js +39 -0
- package/dist/commands/credits.js.map +1 -0
- package/dist/commands/evaluate.d.ts +3 -0
- package/dist/commands/evaluate.d.ts.map +1 -1
- package/dist/commands/evaluate.js +98 -57
- package/dist/commands/evaluate.js.map +1 -1
- package/dist/commands/interactive.d.ts +13 -0
- package/dist/commands/interactive.d.ts.map +1 -0
- package/dist/commands/interactive.js +100 -0
- package/dist/commands/interactive.js.map +1 -0
- package/dist/commands/learner.d.ts +4 -0
- package/dist/commands/learner.d.ts.map +1 -0
- package/dist/commands/learner.js +76 -0
- package/dist/commands/learner.js.map +1 -0
- package/dist/commands/module.d.ts.map +1 -1
- package/dist/commands/module.js +18 -98
- package/dist/commands/module.js.map +1 -1
- package/dist/commands/platform.d.ts.map +1 -1
- package/dist/commands/platform.js +10 -2
- package/dist/commands/platform.js.map +1 -1
- package/dist/commands/run.d.ts +3 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +69 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/slide.d.ts.map +1 -1
- package/dist/commands/slide.js +25 -97
- package/dist/commands/slide.js.map +1 -1
- package/dist/commands/test.d.ts.map +1 -1
- package/dist/commands/test.js +38 -106
- package/dist/commands/test.js.map +1 -1
- package/dist/commands/video.d.ts.map +1 -1
- package/dist/commands/video.js +138 -86
- package/dist/commands/video.js.map +1 -1
- package/dist/commands/webhook.d.ts +3 -0
- package/dist/commands/webhook.d.ts.map +1 -0
- package/dist/commands/webhook.js +75 -0
- package/dist/commands/webhook.js.map +1 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +17 -16
- package/dist/config.js.map +1 -1
- package/dist/content/context.d.ts +26 -0
- package/dist/content/context.d.ts.map +1 -0
- package/dist/content/context.js +74 -0
- package/dist/content/context.js.map +1 -0
- package/dist/content/resource.d.ts +66 -0
- package/dist/content/resource.d.ts.map +1 -0
- package/dist/content/resource.js +181 -0
- package/dist/content/resource.js.map +1 -0
- package/dist/content/runs.d.ts +51 -0
- package/dist/content/runs.d.ts.map +1 -0
- package/dist/content/runs.js +171 -0
- package/dist/content/runs.js.map +1 -0
- package/dist/index.js +71 -21
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +15 -65
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -4
- package/dist/utils/args.d.ts +15 -0
- package/dist/utils/args.d.ts.map +1 -0
- package/dist/utils/args.js +91 -0
- package/dist/utils/args.js.map +1 -0
- package/dist/utils/keys.d.ts +11 -0
- package/dist/utils/keys.d.ts.map +1 -0
- package/dist/utils/keys.js +29 -0
- package/dist/utils/keys.js.map +1 -0
- package/dist/utils/output.d.ts.map +1 -1
- package/dist/utils/output.js +21 -6
- package/dist/utils/output.js.map +1 -1
- package/dist/utils/runtime.d.ts +14 -4
- package/dist/utils/runtime.d.ts.map +1 -1
- package/dist/utils/runtime.js +42 -7
- package/dist/utils/runtime.js.map +1 -1
- package/package.json +4 -3
- package/dist/utils/spinner.d.ts +0 -8
- package/dist/utils/spinner.d.ts.map +0 -1
- package/dist/utils/spinner.js +0 -26
- package/dist/utils/spinner.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,67 +1,274 @@
|
|
|
1
1
|
# @tutorflow/cli
|
|
2
2
|
|
|
3
|
-
TutorFlow
|
|
3
|
+
The TutorFlow command line for developers, CI jobs and AI agents. It creates courses, slide decks, tests, interactive modules, videos, games and simulations, grades learner answers, and manages learners and webhooks through the **TutorFlow Content API** (`/v1/content/*`). Account, billing and discovery commands use the **Agent Platform** account routes (`/v1/platform/agent/*`).
|
|
4
4
|
|
|
5
5
|
- **Binary:** `tutorflow`
|
|
6
6
|
- **Package:** [`@tutorflow/cli`](https://www.npmjs.com/package/@tutorflow/cli) (current version in [`package.json`](package.json))
|
|
7
7
|
- **Source:** [tutorflow-io/cli](https://github.com/tutorflow-io/cli) (private)
|
|
8
|
-
- **Node:** `>= 18`
|
|
9
|
-
- **Agent-friendly:**
|
|
8
|
+
- **Node:** `>= 18` at runtime, `>= 20` for development (Vitest 4)
|
|
9
|
+
- **Agent-friendly:** `--json` output, env-var auth, no prompts without a TTY, stable error codes and exit codes. See [Agent usage](#agent-usage).
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
> **Upgrading from 0.3.x?** Content commands now need a `tf_content_` key and call the Content API; `tf_platform_` keys are refused there with `EPLATFORMKEY`. Generation is asynchronous: add `--wait` to get the finished resource. See [Migrating from 0.3](#migrating-from-03).
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
Install globally with any package manager:
|
|
13
|
+
## Quick start
|
|
16
14
|
|
|
17
15
|
```bash
|
|
18
|
-
npm
|
|
19
|
-
pnpm add -g @tutorflow/cli
|
|
20
|
-
yarn global add @tutorflow/cli
|
|
21
|
-
```
|
|
16
|
+
npm install -g @tutorflow/cli # or: npx @tutorflow/cli <command>
|
|
22
17
|
|
|
23
|
-
|
|
18
|
+
# 1. Create an agent account. The tf_content_ key is saved to ~/.tutorflow/config.json.
|
|
19
|
+
tutorflow agent register --name "My Agent" --slug my-agent --email me@example.com
|
|
24
20
|
|
|
25
|
-
|
|
26
|
-
tutorflow --
|
|
27
|
-
tutorflow
|
|
28
|
-
tutorflow
|
|
29
|
-
tutorflow --json platform manifest
|
|
30
|
-
tutorflow slide create "My first deck"
|
|
31
|
-
```
|
|
21
|
+
# 2. Generate content into the account's default classroom and wait for it.
|
|
22
|
+
tutorflow course generate "Beginner Python for analysts" --lessons 3 --wait
|
|
23
|
+
tutorflow slide generate "Photosynthesis" --count 8 --wait
|
|
24
|
+
tutorflow test generate "Algebra basics" --items 5 --wait
|
|
32
25
|
|
|
33
|
-
|
|
26
|
+
# 3. Grade an answer and read the learner model it builds.
|
|
27
|
+
tutorflow evaluate --type open-ended --question "Why is the sky blue?" \
|
|
28
|
+
--student-answer "Air scatters blue light more." --learner-id student-7 --concept-tags light,atmosphere
|
|
29
|
+
tutorflow evaluation mastery student-7
|
|
34
30
|
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
# 4. Check what it cost.
|
|
32
|
+
tutorflow credits balance
|
|
37
33
|
```
|
|
38
34
|
|
|
39
|
-
|
|
35
|
+
## Keys and classrooms
|
|
36
|
+
|
|
37
|
+
| Key prefix | Works with | Notes |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| `tf_content_…` | every command | Returned by `agent register` as `contentApiKey`, or created in the Agent Platform dashboard. |
|
|
40
|
+
| `tf_content_test_…` | content commands | Reaches only the sandbox classroom, returns canned results and spends nothing. Learner routes refuse test keys. |
|
|
41
|
+
| `tf_platform_…` (deprecated) | `agent`, `platform` | Refused by content commands with `EPLATFORMKEY`. The Platform content routes it served are being removed. |
|
|
42
|
+
|
|
43
|
+
Every content command acts in one classroom, chosen in this order:
|
|
44
|
+
|
|
45
|
+
1. `--classroom <id>` (a global flag; works before or after the subcommand)
|
|
46
|
+
2. `TUTORFLOW_CLASSROOM_ID`
|
|
47
|
+
3. `classroomId` in `~/.tutorflow/config.json` (`tutorflow config set classroomId <id>`)
|
|
48
|
+
4. `GET /v1/platform/agent/account`: `sandboxClassroomId` for a test key, `defaultClassroomId` otherwise
|
|
49
|
+
5. `GET /v1/content/classrooms` when the account route refuses the key (a classroom Settings key): the only classroom, or the sandbox one for a test key; with several, the command exits 2 (`ECLASSROOM`) and lists them
|
|
50
|
+
|
|
51
|
+
`tutorflow classroom list` shows the classrooms the key can reach.
|
|
52
|
+
|
|
53
|
+
## Commands
|
|
54
|
+
|
|
55
|
+
All ids are UUIDs. Every command accepts `--json`.
|
|
56
|
+
|
|
57
|
+
### Account and discovery (`/v1/platform`)
|
|
58
|
+
|
|
59
|
+
| Command | Route |
|
|
60
|
+
| --- | --- |
|
|
61
|
+
| `agent register --name --slug [--email] [--agent-identity] [--no-save]` | `POST /v1/platform/agent/register` (public). Saves `contentApiKey`. Pass `--email`: verification is required before buying credits. |
|
|
62
|
+
| `agent account` | `GET /v1/platform/agent/account`: balance, `defaultClassroomId`, `sandboxClassroomId`, Content API URLs |
|
|
63
|
+
| `agent billing status` | `GET /v1/platform/agent/billing/status` |
|
|
64
|
+
| `agent billing session` | `POST /v1/platform/agent/billing/session`: hosted checkout URL for a human (verified email) |
|
|
65
|
+
| `agent billing x402-deposit --amount-usd <n>` | `POST /v1/platform/agent/billing/x402/deposit`: USDC deposit address on Base (verified email) |
|
|
66
|
+
| `agent checkout info --token` / `agent checkout create --token --amount-usd` | `GET` / `POST /v1/platform/agent/checkout` (public) |
|
|
67
|
+
| `agent verify-email resend` | `POST /v1/platform/agent/verify-email/resend` |
|
|
68
|
+
| `platform manifest` / `platform pricing` | `GET /v1/platform/.well-known/agent.json` / `GET /v1/platform/pricing-catalog` (public) |
|
|
69
|
+
| `platform health` | `GET /v1/platform/health` |
|
|
70
|
+
| `platform share-redirect <shareToken>` | `GET /v1/platform/share-redirects/{token}` (public): where an old Platform share link moved |
|
|
71
|
+
|
|
72
|
+
### Generated content (`/v1/content/classrooms/{classroomId}/…`)
|
|
73
|
+
|
|
74
|
+
| Command | Route | Credits |
|
|
75
|
+
| --- | --- | --- |
|
|
76
|
+
| `course generate [prompt] [--lessons] [--language] [--level] [--title] [--description]` | `POST …/courses/generate` | 3 + 3 per lesson |
|
|
77
|
+
| `slide generate [topic] [--count 5-20] [--theme] [--instructions] [--text-length concise\|detailed]` | `POST …/slides/generate` | per page |
|
|
78
|
+
| `test generate [topic] [--items] [--item-types select,blank,…] [--level] [--total-score]` | `POST …/tests/generate` | 3 |
|
|
79
|
+
| `module generate [topic] [--type markdown\|ai-tutor\|flashcard\|…] [--reference]` | `POST …/modules/generate` | 3 |
|
|
40
80
|
|
|
41
|
-
|
|
81
|
+
`generate` (alias `create`) answers `202` with a run. Add `--wait [--timeout <seconds>]` to poll the run and print `{ run, resource }` with the resource's `editorUrl` and `publicUrl`. Every generate accepts `--idempotency-key`.
|
|
82
|
+
|
|
83
|
+
Each of `course`, `slide`, `test`, `module`, `video`, `game` and `simulation` also has:
|
|
84
|
+
|
|
85
|
+
| Subcommand | Route |
|
|
86
|
+
| --- | --- |
|
|
87
|
+
| `list [--limit] [--page] [--search]` | `GET …/{resource}` (JSON mode prints the raw `{ data, meta }` page) |
|
|
88
|
+
| `get <id>` | `GET …/{resource}/{id}` |
|
|
89
|
+
| `open <id> [--editor]` | prints `editorUrl` and `publicUrl`; opens one in a browser on a TTY |
|
|
90
|
+
| `delete <id> --yes` | `DELETE …/{resource}/{id}` (`--yes` required without a TTY) |
|
|
91
|
+
|
|
92
|
+
`test results <testId>` lists learners' results (`GET …/tests/{id}/results`).
|
|
93
|
+
|
|
94
|
+
### Runs
|
|
95
|
+
|
|
96
|
+
| Command | Route |
|
|
97
|
+
| --- | --- |
|
|
98
|
+
| `run list [--kind course\|slide\|test\|module] [--status] [--limit] [--cursor]` | `GET …/runs` |
|
|
99
|
+
| `run get <runId>` | `GET …/runs/{runId}` |
|
|
100
|
+
| `run wait <runId> [--timeout]` | polls `GET …/runs/{runId}`; prints `{ run, resource }` |
|
|
101
|
+
| `run resume <runId>` | `POST …/runs/{runId}/resume` (course runs only) |
|
|
102
|
+
|
|
103
|
+
A failed run exits 1 with `ERUNFAILED`; a timeout exits 1 with `ETIMEOUT` and names the `run wait` command to continue. Polling rides out up to five transient 5xx, 429 or network failures in a row.
|
|
104
|
+
|
|
105
|
+
### Videos
|
|
106
|
+
|
|
107
|
+
The Content API builds videos from scenes you script; it does not write the script from a prompt.
|
|
108
|
+
|
|
109
|
+
| Command | Route |
|
|
110
|
+
| --- | --- |
|
|
111
|
+
| `video create --title [--description] [--visibility] [--scene "<script>"]…` | `POST …/videos`, then one scene per `--scene` in order |
|
|
112
|
+
| `video scene add <videoId> [--script] [--display-text] [--duration] [--after <sceneId>]` | `POST …/videos/{id}/scenes`, then `PATCH` the new scene |
|
|
113
|
+
| `video scene update <videoId> <sceneId> [--script] [--display-text] [--duration]` | `PATCH …/scenes/{sceneId}` |
|
|
114
|
+
| `video scene delete <videoId> <sceneId> --yes` | `DELETE …/scenes/{sceneId}` |
|
|
115
|
+
| `video narrate <videoId> [--scene <sceneId>]` | `POST …/narration` (1 credit per scene) |
|
|
116
|
+
| `video render <videoId> [--generate-missing-narration] [--wait]` | `POST …/videos/{id}/render` (5 credits; `--wait` polls until `COMPLETED` and prints the mp4 URL) |
|
|
117
|
+
| `video render-status <videoId>` / `video render-cancel <videoId>` | `GET …/render` / `POST …/render/cancel` |
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
tutorflow video create --title "Why the sky is blue" \
|
|
121
|
+
--scene "Sunlight is made of many colors." \
|
|
122
|
+
--scene "Air scatters blue light the most, so the sky looks blue."
|
|
123
|
+
tutorflow video render <videoId> --generate-missing-narration --wait
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Games and simulations
|
|
127
|
+
|
|
128
|
+
`game` and `simulation` share one lifecycle. The CLI always asks for the asynchronous answer (`Prefer: respond-async`), so `--wait` polls the resource's run route.
|
|
129
|
+
|
|
130
|
+
| Command | Route | Credits |
|
|
131
|
+
| --- | --- | --- |
|
|
132
|
+
| `create --title [--description] [--visibility PRIVATE\|PUBLIC]` | `POST …/{games\|simulations}` | 0 |
|
|
133
|
+
| `brief <id> [--reference <markdown>] [--wait]` | `POST …/{id}/brief` | 1 |
|
|
134
|
+
| `build <id> [--wait]` | `POST …/{id}/build` | game 40, simulation 20 (2D) or 30 (3D) |
|
|
135
|
+
| `revise <id> --feedback <text> [--wait]` | `POST …/{id}/revise` | as build |
|
|
136
|
+
| `status <id>` | `GET …/{id}/run` | |
|
|
137
|
+
|
|
138
|
+
### Evaluations and learners
|
|
139
|
+
|
|
140
|
+
| Command | Route |
|
|
141
|
+
| --- | --- |
|
|
142
|
+
| `evaluate --type --question --student-answer [--expected-answer] [--rubric-json] [--max-score] [--learner-id] [--concept-tags a,b] [--subject] [--idempotency-key]` | `POST …/evaluations` (1 credit, synchronous) |
|
|
143
|
+
| `evaluation list [--learner-id] [--status] [--type] [--limit] [--cursor]` / `evaluation get <id>` | `GET …/evaluations` |
|
|
144
|
+
| `evaluation concepts` / `evaluation mastery <learnerId>` | `GET …/concepts` / `GET …/mastery/{learner}` |
|
|
145
|
+
| `learner list` / `get <id>` / `progress <id>` | `GET …/learners…` (live keys only) |
|
|
146
|
+
| `learner invite --email [--name] [--locale] [--course <id>]… [--test <id>]…` | `POST …/learners/invitations` |
|
|
147
|
+
| `learner remove <id> --yes` | `DELETE …/learners/{id}` |
|
|
148
|
+
|
|
149
|
+
`--type` takes `exact`, `rubric_short_answer` or `open_ended` (aliases `rubric`, `open-ended`). `exact` needs `--expected-answer`; `rubric_short_answer` needs `--rubric-json`.
|
|
150
|
+
|
|
151
|
+
### Webhooks and credits (`/v1/content`)
|
|
152
|
+
|
|
153
|
+
| Command | Route |
|
|
154
|
+
| --- | --- |
|
|
155
|
+
| `webhook list` / `get <id>` | `GET /v1/content/webhooks[/{id}]` |
|
|
156
|
+
| `webhook create --url --event <event>… [--mode live\|test]` | `POST /v1/content/webhooks` (the signing secret is shown once) |
|
|
157
|
+
| `webhook test <id>` / `deliveries <id>` / `rotate-secret <id>` / `delete <id> --yes` | the matching `/v1/content/webhooks/{id}/…` routes |
|
|
158
|
+
| `credits balance` | `GET /v1/content/credits` |
|
|
159
|
+
| `credits history [--limit] [--cursor] [--from] [--to] [--type]` | `GET /v1/content/credits/history` |
|
|
160
|
+
|
|
161
|
+
Webhook management needs the `webhooks:manage` scope, which the key from `agent register` has. Events include `course.generation.completed`, `slide.generation.completed`, `test.generation.completed`, `module.generation.completed`, `video.render.completed`, `game.build.completed`, `simulation.build.completed` and their `.failed` twins.
|
|
162
|
+
|
|
163
|
+
### Auth and config
|
|
164
|
+
|
|
165
|
+
| Command | Effect |
|
|
166
|
+
| --- | --- |
|
|
167
|
+
| `auth login --api-key <key>` | Save a key (prompts on a TTY when omitted) |
|
|
168
|
+
| `auth whoami` | Masked key, `keyKind` (`content_live`, `content_test`, `platform`, `unknown`), base URL, `source` (`env` or `config`). No network call. |
|
|
169
|
+
| `auth logout --force` | Delete `~/.tutorflow/config.json` |
|
|
170
|
+
| `config get [key]` / `config set <key> <value>` | Keys: `apiKey`, `baseUrl`, `classroomId` |
|
|
171
|
+
|
|
172
|
+
The config file is written with mode `0600`.
|
|
173
|
+
|
|
174
|
+
## Agent usage
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
export TUTORFLOW_API_KEY=tf_content_xxx
|
|
178
|
+
export TUTORFLOW_JSON=1
|
|
179
|
+
|
|
180
|
+
run=$(tutorflow course generate "Fractions for 5th grade" --lessons 2)
|
|
181
|
+
tutorflow run wait "$(echo "$run" | jq -r .runId)" | jq -r .resource.publicUrl
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
- **Output:** with `--json` or `TUTORFLOW_JSON=1`, success is one JSON document on stdout and errors are JSON on stderr. Progress lines (text mode only) go to stderr.
|
|
185
|
+
- **No prompts without a TTY:** a missing required input exits 2 with `ENOINTERACTIVE` and names the flag to pass. Destructive commands need `--yes`.
|
|
186
|
+
- **Retries:** create and generate commands accept `--idempotency-key`.
|
|
187
|
+
- **Errors:** `{"error":{"message","code",…}}`. API errors add `status`, `apiCode` (the Content API's code, e.g. `content_payment_required`), `requestId`, and when useful `hint`, `retryAfterSeconds`, `requiredScope`, `validation`, `billingSessionUrl`, `x402`.
|
|
188
|
+
|
|
189
|
+
| Exit | Meaning |
|
|
190
|
+
| --- | --- |
|
|
191
|
+
| `0` | Success |
|
|
192
|
+
| `1` | Failure: auth, network, API error, failed run, timeout |
|
|
193
|
+
| `2` | Bad input: invalid flag, missing input without a TTY, ambiguous classroom |
|
|
194
|
+
| `130` | Cancelled at a prompt |
|
|
195
|
+
|
|
196
|
+
| Code | When |
|
|
197
|
+
| --- | --- |
|
|
198
|
+
| `EAUTH` | No API key |
|
|
199
|
+
| `EPLATFORMKEY` | A `tf_platform_` key on a content command |
|
|
200
|
+
| `ECLASSROOM` | No classroom could be chosen; pass `--classroom` |
|
|
201
|
+
| `EINVAL` | Invalid flag value |
|
|
202
|
+
| `ENOINTERACTIVE` | Missing input or confirmation without a TTY |
|
|
203
|
+
| `EHTTP_<status>` | API error; see `apiCode` |
|
|
204
|
+
| `ENETWORK` | The API could not be reached |
|
|
205
|
+
| `ERUNFAILED` | A waited run, build or render failed |
|
|
206
|
+
| `ETIMEOUT` | `--wait` gave up; the work continues on the server |
|
|
207
|
+
| `ENOPREVIEW` | `open` found no URL |
|
|
208
|
+
| `ENOTFOUND` | `config get` of an unset key |
|
|
209
|
+
| `ECANCELLED` | Prompt cancelled |
|
|
210
|
+
|
|
211
|
+
| Environment variable | Effect |
|
|
212
|
+
| --- | --- |
|
|
213
|
+
| `TUTORFLOW_API_KEY` | API key (wins over the config file) |
|
|
214
|
+
| `TUTORFLOW_BASE_URL` | API origin, e.g. `https://dev-api.tutorflow.io` for staging |
|
|
215
|
+
| `TUTORFLOW_CLASSROOM_ID` | Classroom for content commands |
|
|
216
|
+
| `TUTORFLOW_JSON=1` | JSON mode |
|
|
217
|
+
| `TUTORFLOW_DEBUG=1` / `DEBUG=1` | Stack traces on errors |
|
|
218
|
+
| `TUTORFLOW_POLL_INTERVAL_MS` | `--wait` poll interval (default 3000) |
|
|
219
|
+
|
|
220
|
+
Empty environment variables are ignored.
|
|
221
|
+
|
|
222
|
+
## Migrating from 0.3
|
|
223
|
+
|
|
224
|
+
| 0.3 | Now |
|
|
225
|
+
| --- | --- |
|
|
226
|
+
| `tf_platform_` key on every command | `tf_content_` key; `agent register` saves it. Platform keys still work on `agent` and `platform`. |
|
|
227
|
+
| `/v1/platform/{courses,slides,tests,modules,videos,evaluations}` | `/v1/content/classrooms/{classroomId}/…` |
|
|
228
|
+
| `course create` returned the finished course (`mode: sync`) | `course generate` (alias `create`) returns a run; add `--wait` |
|
|
229
|
+
| `course create -l 10 -lang ko` | `course generate --lessons 10 --language ko` |
|
|
230
|
+
| `slide create -t <theme>` | `slide generate --theme <themeId>` (5 to 20 pages) |
|
|
231
|
+
| `video create "<prompt>" -s 6 -r 16:9 -d 90` | `video create --title … --scene "<script>"…`, then `video render --generate-missing-narration --wait` |
|
|
232
|
+
| `module create --title --target --locale --private --no-quiz` | `module generate [--type] [--reference]`; edit the rest in the classroom editor |
|
|
233
|
+
| `test create --classroom-id --tier --mode --time-limit` | `test generate` with the global `--classroom`; generation is always asynchronous |
|
|
234
|
+
| `test edit-token`, `previewUrl` | `editorUrl` and `publicUrl` on every resource; `open <id>` |
|
|
235
|
+
| list `--offset` | `--page` |
|
|
236
|
+
| `evaluate` → `/v1/platform/evaluations` | `evaluate` → `…/evaluations`, plus `--learner-id` and `--concept-tags` |
|
|
237
|
+
|
|
238
|
+
New in this version: `classroom`, `run`, `game`, `simulation`, `evaluation`, `learner`, `webhook`, `credits`, `video scene|narrate|render`, `agent billing x402-deposit`, `platform share-redirect`, `delete` on every resource, and the global `--classroom`.
|
|
239
|
+
|
|
240
|
+
## Development
|
|
42
241
|
|
|
43
242
|
```bash
|
|
44
|
-
git clone https://github.com/tutorflow-io/cli.git
|
|
45
|
-
cd cli
|
|
243
|
+
git clone https://github.com/tutorflow-io/cli.git && cd cli
|
|
46
244
|
pnpm install
|
|
47
|
-
pnpm build
|
|
48
|
-
pnpm
|
|
245
|
+
pnpm build && pnpm test # the e2e suites spawn dist/index.js
|
|
246
|
+
pnpm dev -- course list # run from source
|
|
247
|
+
pnpm link # expose `tutorflow` globally
|
|
49
248
|
```
|
|
50
249
|
|
|
51
|
-
|
|
250
|
+
Try it against staging with a throwaway account:
|
|
52
251
|
|
|
53
252
|
```bash
|
|
54
|
-
|
|
55
|
-
|
|
253
|
+
export HOME=$(mktemp -d) TUTORFLOW_BASE_URL=https://dev-api.tutorflow.io
|
|
254
|
+
node dist/index.js agent register --name "CLI test" --slug cli-test-$(date +%s)
|
|
255
|
+
node dist/index.js course generate "Water cycle" --lessons 1 --wait
|
|
56
256
|
```
|
|
57
257
|
|
|
58
|
-
|
|
258
|
+
### Layout
|
|
59
259
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
#
|
|
260
|
+
```
|
|
261
|
+
src/
|
|
262
|
+
├── index.ts # commander entrypoint and global flags (--json, --debug, --classroom)
|
|
263
|
+
├── client.ts # ApiClient: config resolution, HTTP, error envelope → CliError
|
|
264
|
+
├── config.ts # ~/.tutorflow/config.json (mode 0600)
|
|
265
|
+
├── content/
|
|
266
|
+
│ ├── context.ts # tf_content_ key check and classroom resolution
|
|
267
|
+
│ ├── resource.ts # shared list/get/open/delete/generate subcommands
|
|
268
|
+
│ └── runs.ts # waiting on runs, game/simulation builds and renders
|
|
269
|
+
├── commands/ # one file per top-level command
|
|
270
|
+
├── utils/ # runtime (JSON mode, CliError), output, args, keys
|
|
271
|
+
└── testing/harness.ts # test-only: fake API server and CLI spawner
|
|
65
272
|
```
|
|
66
273
|
|
|
67
274
|
## Releasing
|
|
@@ -128,7 +335,7 @@ These tripped us up during initial setup. Read before changing release infrastru
|
|
|
128
335
|
|
|
129
336
|
### Tarball hygiene
|
|
130
337
|
|
|
131
|
-
- `tsconfig.json` excludes `**/*.test.ts` so test
|
|
338
|
+
- `tsconfig.json` excludes `**/*.test.ts` and `src/testing/` so test code doesn't end up in `dist/` (and therefore not in the published tarball).
|
|
132
339
|
- `package.json#files` is an allowlist (`dist`, `README.md`, `LICENSE`). Anything not listed is excluded.
|
|
133
340
|
- Run `npm pack --dry-run` to inspect the tarball before publish.
|
|
134
341
|
|
|
@@ -136,631 +343,6 @@ These tripped us up during initial setup. Read before changing release infrastru
|
|
|
136
343
|
|
|
137
344
|
The published tarball currently includes `.js.map` and `.d.ts.map` files which embed paths from the build environment. While the source itself isn't shipped, this leaks file structure. If that matters, set `sourceMap: false` and `declarationMap: false` in [tsconfig.json](tsconfig.json) before going public.
|
|
138
345
|
|
|
139
|
-
## Development
|
|
140
|
-
|
|
141
|
-
Run with `tsx` (no build step):
|
|
142
|
-
|
|
143
|
-
```bash
|
|
144
|
-
pnpm dev -- <command> [options]
|
|
145
|
-
# e.g. pnpm dev -- slide list --limit 5
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
Build artifacts land in `dist/` (entry: `dist/index.js`, declared in `package.json#bin`).
|
|
149
|
-
|
|
150
|
-
## Non-interactive / agent usage
|
|
151
|
-
|
|
152
|
-
Every command supports the contract below so AI agents and CI scripts can use the CLI without a TTY.
|
|
153
|
-
|
|
154
|
-
### Auth without prompts
|
|
155
|
-
|
|
156
|
-
```bash
|
|
157
|
-
# Option 1: env var (preferred for one-shot calls)
|
|
158
|
-
TUTORFLOW_API_KEY=tf_xxx tutorflow --json slide list
|
|
159
|
-
|
|
160
|
-
# Option 2: persist to ~/.tutorflow/config.json once
|
|
161
|
-
tutorflow auth login --api-key tf_xxx
|
|
162
|
-
tutorflow --json slide create "Photosynthesis for 5th graders" -n 12
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
`TUTORFLOW_API_KEY` always wins over the config file. `tutorflow auth whoami --json` reports `source: "env" | "config"` so an agent can verify which credential is active.
|
|
166
|
-
|
|
167
|
-
### Machine-readable output
|
|
168
|
-
|
|
169
|
-
`--json` (or `TUTORFLOW_JSON=1`) makes every command emit a single JSON line on stdout. Errors emit JSON to stderr.
|
|
170
|
-
|
|
171
|
-
```bash
|
|
172
|
-
$ tutorflow --json auth whoami
|
|
173
|
-
{"authenticated":true,"apiKeyPreview":"tf_xxxxxxx...","baseUrl":"https://api.tutorflow.io","source":"env"}
|
|
174
|
-
|
|
175
|
-
$ tutorflow --json auth whoami # without auth
|
|
176
|
-
# stdout: (empty)
|
|
177
|
-
# stderr: {"error":{"message":"Not authenticated","code":"EAUTH"}}
|
|
178
|
-
# exit: 1
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
List commands return raw arrays:
|
|
182
|
-
|
|
183
|
-
```bash
|
|
184
|
-
$ tutorflow --json slide list --limit 3
|
|
185
|
-
[{"id":"...","title":"...","status":"ready","createdAt":"..."},...]
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
Pipe into `jq` for downstream automation:
|
|
189
|
-
|
|
190
|
-
```bash
|
|
191
|
-
tutorflow --json slide list --status ready | jq -r '.[].id' | xargs -I{} tutorflow --json slide get {}
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
### Exit codes
|
|
195
|
-
|
|
196
|
-
| Code | Meaning |
|
|
197
|
-
| ----- | ----------------------------------------------------------------- |
|
|
198
|
-
| `0` | Success |
|
|
199
|
-
| `1` | Generic failure (auth, network, API error, etc.) |
|
|
200
|
-
| `2` | Bad input (invalid flag value, missing required input in non-TTY) |
|
|
201
|
-
| `130` | User cancelled an interactive prompt (Ctrl+C) |
|
|
202
|
-
|
|
203
|
-
The error JSON includes a stable `code`: `EAUTH`, `EINVAL`, `ENOINTERACTIVE`, `ENETWORK`, `EHTTP_<status>`, `ENOPREVIEW`, `ENOTFOUND`, `ECANCELLED`.
|
|
204
|
-
|
|
205
|
-
### Required-input flags (no prompts)
|
|
206
|
-
|
|
207
|
-
Commands that have required input expose every prompt as a flag:
|
|
208
|
-
|
|
209
|
-
```bash
|
|
210
|
-
# self-register a platform workspace and save the returned API key
|
|
211
|
-
tutorflow --json agent register \
|
|
212
|
-
--name "Agent Workspace" \
|
|
213
|
-
--slug agent-workspace \
|
|
214
|
-
--email agent@example.com \
|
|
215
|
-
--agent-identity "codex"
|
|
216
|
-
|
|
217
|
-
# slide / video / course create — accept positional [prompt]
|
|
218
|
-
tutorflow --json slide create "Photosynthesis" -n 12 -l en
|
|
219
|
-
tutorflow --json video create "Newton's laws" -s 6 -r 16:9 -d 90
|
|
220
|
-
tutorflow --json course create "Beginner Python" -l 10 -lang en
|
|
221
|
-
|
|
222
|
-
# evaluate — every input is a flag
|
|
223
|
-
tutorflow --json evaluate \
|
|
224
|
-
--type exact \
|
|
225
|
-
--question "What is 2+2?" \
|
|
226
|
-
--student-answer "4" \
|
|
227
|
-
--expected-answer "4"
|
|
228
|
-
|
|
229
|
-
# logout (skip confirmation)
|
|
230
|
-
tutorflow auth logout --force
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
If a required input is missing **and** stdin is not a TTY, the command exits 2 with `ENOINTERACTIVE` and an error message naming the flag(s) to use. It never hangs waiting for input.
|
|
234
|
-
|
|
235
|
-
### Globals
|
|
236
|
-
|
|
237
|
-
| Flag | Env var | Effect |
|
|
238
|
-
| --------------------------------- | -------------------------------- | ----------------------------------------------- |
|
|
239
|
-
| `--json` | `TUTORFLOW_JSON=1` | JSON output to stdout, JSON errors to stderr |
|
|
240
|
-
| `--debug` | `DEBUG=1` or `TUTORFLOW_DEBUG=1` | Print stack traces on errors |
|
|
241
|
-
| `--api-key <k>` (auth login only) | `TUTORFLOW_API_KEY` | API key |
|
|
242
|
-
| (n/a) | `TUTORFLOW_BASE_URL` | Override API base URL (staging, self-hosted) |
|
|
243
|
-
|
|
244
|
-
### MCP server
|
|
245
|
-
|
|
246
|
-
A native MCP server (so agents can call tools directly without shelling out) is on the roadmap. For now, the `--json` shell contract is the recommended integration path.
|
|
247
|
-
|
|
248
|
-
## Authentication
|
|
249
|
-
|
|
250
|
-
Generate an API key in your TutorFlow workspace, then:
|
|
251
|
-
|
|
252
|
-
```bash
|
|
253
|
-
tutorflow auth login # interactive password prompt for API key
|
|
254
|
-
tutorflow auth whoami # prints masked API key + base URL
|
|
255
|
-
tutorflow auth logout # confirms then deletes ~/.tutorflow/config.json
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
Credentials are persisted to `~/.tutorflow/config.json`:
|
|
259
|
-
|
|
260
|
-
```json
|
|
261
|
-
{
|
|
262
|
-
"apiKey": "tf_xxxxxxxxxxxxxxxxxxxx",
|
|
263
|
-
"baseUrl": "https://api.tutorflow.io",
|
|
264
|
-
"workspace": "optional-workspace-slug"
|
|
265
|
-
}
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
The API key is sent as the `Authorization: Bearer <apiKey>` header on authenticated requests.
|
|
269
|
-
|
|
270
|
-
### Config schema
|
|
271
|
-
|
|
272
|
-
```ts
|
|
273
|
-
interface IConfig {
|
|
274
|
-
apiKey?: string; // required for all non-auth/config commands
|
|
275
|
-
baseUrl?: string; // default: https://api.tutorflow.io
|
|
276
|
-
workspace?: string; // optional workspace slug
|
|
277
|
-
}
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
## Configuration commands
|
|
281
|
-
|
|
282
|
-
```bash
|
|
283
|
-
tutorflow config get # print full config (apiKey is masked)
|
|
284
|
-
tutorflow config get <key> # print one key as KEY=VALUE
|
|
285
|
-
tutorflow config set <key> <value> # update one key
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
| Key | Type | Default | Notes |
|
|
289
|
-
| ----------- | -------- | -------------------------- | -------------------------------------- |
|
|
290
|
-
| `apiKey` | `string` | none | Set via `auth login` or `config set` |
|
|
291
|
-
| `baseUrl` | `string` | `https://api.tutorflow.io` | Override for staging / self-hosted |
|
|
292
|
-
| `workspace` | `string` | none | Reserved for future scoping |
|
|
293
|
-
|
|
294
|
-
`config set` rejects unknown keys.
|
|
295
|
-
|
|
296
|
-
## Commands
|
|
297
|
-
|
|
298
|
-
All resource commands require authentication. Without an API key they print:
|
|
299
|
-
`✗ Not authenticated. Run "tutorflow auth login" first.`
|
|
300
|
-
|
|
301
|
-
### `tutorflow agent`
|
|
302
|
-
|
|
303
|
-
Manage the agent-facing TutorFlow Platform account flow.
|
|
304
|
-
|
|
305
|
-
#### `agent register`
|
|
306
|
-
|
|
307
|
-
Public self-service registration. Creates a platform workspace through
|
|
308
|
-
`POST /v1/platform/agent/register`, returns the one-time API key, and saves it
|
|
309
|
-
to `~/.tutorflow/config.json` by default so the next CLI command can run
|
|
310
|
-
without a separate `auth login`.
|
|
311
|
-
|
|
312
|
-
| Option | Type | Default | Description |
|
|
313
|
-
| ------------------------------ | -------- | ------- | -------------------------------------------- |
|
|
314
|
-
| `--name <name>` | `string` | — | Workspace display name |
|
|
315
|
-
| `--slug <slug>` | `string` | — | Unique lowercase slug |
|
|
316
|
-
| `--email <email>` | `string` | none | Contact email for verification and billing |
|
|
317
|
-
| `--agent-identity <identity>` | `string` | none | Optional agent identity string |
|
|
318
|
-
| `--no-save` | boolean | false | Print the API key but do not persist it |
|
|
319
|
-
|
|
320
|
-
```bash
|
|
321
|
-
tutorflow --json agent register \
|
|
322
|
-
--name "Agent Workspace" \
|
|
323
|
-
--slug agent-workspace \
|
|
324
|
-
--email agent@example.com \
|
|
325
|
-
--agent-identity "codex"
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
#### `agent account`
|
|
329
|
-
|
|
330
|
-
GET `/v1/platform/agent/account`. Shows workspace status, credit balance,
|
|
331
|
-
email verification state, rate limits, and `restrictedActions`.
|
|
332
|
-
|
|
333
|
-
```bash
|
|
334
|
-
tutorflow --json agent account
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
#### `agent billing status`
|
|
338
|
-
|
|
339
|
-
GET `/v1/platform/agent/billing/status`. Shows payment-method state, credit
|
|
340
|
-
balance, billing mode, email verification state, and `restrictedActions`.
|
|
341
|
-
|
|
342
|
-
```bash
|
|
343
|
-
tutorflow --json agent billing status
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
#### `agent billing session`
|
|
347
|
-
|
|
348
|
-
POST `/v1/platform/agent/billing/session`. Returns a TutorFlow-hosted checkout
|
|
349
|
-
URL that an agent should forward to a human operator.
|
|
350
|
-
|
|
351
|
-
```bash
|
|
352
|
-
tutorflow --json agent billing session
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
#### `agent verify-email resend`
|
|
356
|
-
|
|
357
|
-
POST `/v1/platform/agent/verify-email/resend`. Resends the workspace email
|
|
358
|
-
verification message using the active API key.
|
|
359
|
-
|
|
360
|
-
```bash
|
|
361
|
-
tutorflow --json agent verify-email resend
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
#### `agent checkout info`
|
|
365
|
-
|
|
366
|
-
GET `/v1/platform/agent/checkout?token=<token>`. Reads workspace info for a
|
|
367
|
-
TutorFlow checkout token returned through the hosted billing-session flow.
|
|
368
|
-
|
|
369
|
-
```bash
|
|
370
|
-
tutorflow --json agent checkout info --token checkout_123
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
#### `agent checkout create`
|
|
374
|
-
|
|
375
|
-
POST `/v1/platform/agent/checkout`. Creates a Stripe Checkout URL from a
|
|
376
|
-
TutorFlow checkout token and amount. This is normally called by the hosted
|
|
377
|
-
checkout page; CLI support exists for complete automation and testing.
|
|
378
|
-
|
|
379
|
-
```bash
|
|
380
|
-
tutorflow --json agent checkout create --token checkout_123 --amount-usd 25
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
### `tutorflow platform`
|
|
384
|
-
|
|
385
|
-
Inspect public discovery metadata and validate the active Platform API key.
|
|
386
|
-
|
|
387
|
-
| Subcommand | Endpoint | Auth |
|
|
388
|
-
| ---------- | -------- | ---- |
|
|
389
|
-
| `manifest` | `GET /v1/platform/.well-known/agent.json` | none |
|
|
390
|
-
| `pricing` | `GET /v1/platform/pricing-catalog` | none |
|
|
391
|
-
| `health` | `GET /v1/platform/health` | Platform API key |
|
|
392
|
-
|
|
393
|
-
```bash
|
|
394
|
-
tutorflow --json platform manifest
|
|
395
|
-
tutorflow --json platform pricing
|
|
396
|
-
tutorflow --json platform health
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
### `tutorflow slide`
|
|
400
|
-
|
|
401
|
-
Create and manage slide decks.
|
|
402
|
-
|
|
403
|
-
#### `slide create [prompt]`
|
|
404
|
-
|
|
405
|
-
Creates a slide deck synchronously (`mode: 'sync'`).
|
|
406
|
-
|
|
407
|
-
| Argument / Option | Type | Default | Description |
|
|
408
|
-
| ----------------------- | --------------------- | ------- | ------------------------------------ |
|
|
409
|
-
| `[prompt]` | `string` (positional) | — | Prompted interactively if omitted |
|
|
410
|
-
| `-n, --count <number>` | `string → int` | `10` | Slide count (parsed via `parseInt`) |
|
|
411
|
-
| `-l, --language <lang>` | `string` | `en` | ISO 639-1 language code |
|
|
412
|
-
| `-t, --theme <theme>` | `string` | none | Theme identifier |
|
|
413
|
-
|
|
414
|
-
POST `/v1/platform/slides` body:
|
|
415
|
-
|
|
416
|
-
```json
|
|
417
|
-
{
|
|
418
|
-
"prompt": "...",
|
|
419
|
-
"slideCount": 10,
|
|
420
|
-
"language": "en",
|
|
421
|
-
"theme": "...",
|
|
422
|
-
"mode": "sync"
|
|
423
|
-
}
|
|
424
|
-
```
|
|
425
|
-
|
|
426
|
-
Output: `id`, `status`, `previewUrl` (if returned). After creation, prompts to open the preview URL in the browser.
|
|
427
|
-
|
|
428
|
-
```bash
|
|
429
|
-
tutorflow slide create "Photosynthesis for 5th graders" -n 12 -l en -t classic
|
|
430
|
-
```
|
|
431
|
-
|
|
432
|
-
#### `slide list`
|
|
433
|
-
|
|
434
|
-
| Option | Type | Default | Description |
|
|
435
|
-
| ----------------------- | -------- | ------- | -------------------------- |
|
|
436
|
-
| `-l, --limit <number>` | `string` | `10` | Max rows to fetch |
|
|
437
|
-
| `-s, --status <status>` | `string` | none | Server-side status filter |
|
|
438
|
-
|
|
439
|
-
GET `/v1/platform/slides?limit=<n>[&status=<s>]`. Renders an ASCII table with `ID` (8-char prefix), `Title`, `Status`, `Created`.
|
|
440
|
-
|
|
441
|
-
#### `slide open <id>`
|
|
442
|
-
|
|
443
|
-
Fetches `/v1/platform/slides/:id` and opens `previewUrl` in the default browser via [`open`](https://github.com/sindresorhus/open).
|
|
444
|
-
|
|
445
|
-
---
|
|
446
|
-
|
|
447
|
-
### `tutorflow video`
|
|
448
|
-
|
|
449
|
-
Create and manage AI-generated videos.
|
|
450
|
-
|
|
451
|
-
#### `video create [prompt]`
|
|
452
|
-
|
|
453
|
-
Creates a video synchronously (`mode: 'sync'`).
|
|
454
|
-
|
|
455
|
-
| Argument / Option | Type | Default | Description |
|
|
456
|
-
| -------------------------- | --------------------- | ------- | ---------------------------------------- |
|
|
457
|
-
| `[prompt]` | `string` (positional) | — | Prompted interactively if omitted |
|
|
458
|
-
| `-s, --scenes <number>` | `string → int` | `5` | Scene count |
|
|
459
|
-
| `-r, --ratio <ratio>` | `string` | `16:9` | Aspect ratio (`16:9`, `9:16`, `1:1`, …) |
|
|
460
|
-
| `-d, --duration <seconds>` | `string → int` | `60` | Target duration in seconds |
|
|
461
|
-
| `-l, --language <lang>` | `string` | `en` | ISO 639-1 language code |
|
|
462
|
-
|
|
463
|
-
POST `/v1/platform/videos` body:
|
|
464
|
-
|
|
465
|
-
```json
|
|
466
|
-
{
|
|
467
|
-
"prompt": "...",
|
|
468
|
-
"sceneCount": 5,
|
|
469
|
-
"aspectRatio": "16:9",
|
|
470
|
-
"targetDurationSeconds": 60,
|
|
471
|
-
"language": "en",
|
|
472
|
-
"mode": "sync"
|
|
473
|
-
}
|
|
474
|
-
```
|
|
475
|
-
|
|
476
|
-
Output: `id`, `status`, `sceneCount`, `previewUrl` (if returned). Prompts to open in browser.
|
|
477
|
-
|
|
478
|
-
```bash
|
|
479
|
-
tutorflow video create "Intro to Newton's laws" -s 6 -r 16:9 -d 90 -l en
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
#### `video list`
|
|
483
|
-
|
|
484
|
-
| Option | Type | Default | Description |
|
|
485
|
-
| ----------------------- | -------- | ------- | -------------------------- |
|
|
486
|
-
| `-l, --limit <number>` | `string` | `10` | Max rows |
|
|
487
|
-
| `-s, --status <status>` | `string` | none | Server-side status filter |
|
|
488
|
-
|
|
489
|
-
GET `/v1/platform/videos?limit=<n>[&status=<s>]`. Table columns: `ID`, `Title`, `Status`, `Scenes`, `Created`.
|
|
490
|
-
|
|
491
|
-
#### `video open <id>`
|
|
492
|
-
|
|
493
|
-
Opens the video preview URL in the browser.
|
|
494
|
-
|
|
495
|
-
---
|
|
496
|
-
|
|
497
|
-
### `tutorflow course`
|
|
498
|
-
|
|
499
|
-
Create and manage multi-lesson courses.
|
|
500
|
-
|
|
501
|
-
#### `course create [prompt]`
|
|
502
|
-
|
|
503
|
-
| Argument / Option | Type | Default | Description |
|
|
504
|
-
| -------------------------- | --------------------- | ------- | --------------------------------- |
|
|
505
|
-
| `[prompt]` | `string` (positional) | — | Prompted interactively if omitted |
|
|
506
|
-
| `-l, --lessons <number>` | `string → int` | `8` | Lesson count |
|
|
507
|
-
| `-lang, --language <lang>` | `string` | `en` | ISO 639-1 language code |
|
|
508
|
-
|
|
509
|
-
> Note: `--language` uses the short flag `-lang` (not `-L`), because `-l` is already taken by `--lessons`.
|
|
510
|
-
|
|
511
|
-
POST `/v1/platform/courses` body:
|
|
512
|
-
|
|
513
|
-
```json
|
|
514
|
-
{
|
|
515
|
-
"prompt": "...",
|
|
516
|
-
"lessonCount": 8,
|
|
517
|
-
"language": "en",
|
|
518
|
-
"mode": "sync"
|
|
519
|
-
}
|
|
520
|
-
```
|
|
521
|
-
|
|
522
|
-
```bash
|
|
523
|
-
tutorflow course create "Beginner Python in Korean" -l 10 -lang ko
|
|
524
|
-
```
|
|
525
|
-
|
|
526
|
-
#### `course list`
|
|
527
|
-
|
|
528
|
-
| Option | Type | Default | Description |
|
|
529
|
-
| ---------------------- | -------- | ------- | ----------- |
|
|
530
|
-
| `-l, --limit <number>` | `string` | `10` | Max rows |
|
|
531
|
-
|
|
532
|
-
GET `/v1/platform/courses?limit=<n>`. Table columns: `ID`, `Title`, `Status`, `Created`.
|
|
533
|
-
|
|
534
|
-
#### `course open <id>`
|
|
535
|
-
|
|
536
|
-
Opens the course preview URL in the browser.
|
|
537
|
-
|
|
538
|
-
---
|
|
539
|
-
|
|
540
|
-
### `tutorflow module`
|
|
541
|
-
|
|
542
|
-
Create and manage standalone lesson modules.
|
|
543
|
-
|
|
544
|
-
#### `module create [prompt]`
|
|
545
|
-
|
|
546
|
-
POST `/v1/platform/modules`. Generates one classroom-compatible standalone
|
|
547
|
-
lesson. `--idempotency-key` is sent as the `Idempotency-Key` header so agents
|
|
548
|
-
can safely retry a create call.
|
|
549
|
-
|
|
550
|
-
| Argument / Option | Type | Default | Description |
|
|
551
|
-
| ------------------------------ | --------------------- | -------- | -------------------------------- |
|
|
552
|
-
| `[prompt]` | `string` (positional) | — | Lesson/module prompt |
|
|
553
|
-
| `--title <title>` | `string` | none | Override generated title |
|
|
554
|
-
| `--description <description>` | `string` | none | Override generated description |
|
|
555
|
-
| `--target <target>` | `string` | none | Target learner audience |
|
|
556
|
-
| `--type <type>` | `string` | `lesson` | Module type |
|
|
557
|
-
| `--locale <locale>` | `string` | `en` | Locale code |
|
|
558
|
-
| `--language <language>` | `string` | `en` | Language code |
|
|
559
|
-
| `--slug <slug>` | `string` | none | Optional slug |
|
|
560
|
-
| `--private` | boolean | false | Set `isPublic: false` |
|
|
561
|
-
| `--no-quiz` | boolean | false | Set `hasQuiz: false` |
|
|
562
|
-
| `--idempotency-key <key>` | `string` | none | Safe retry key |
|
|
563
|
-
|
|
564
|
-
```bash
|
|
565
|
-
tutorflow --json module create "Fractions for 5th grade" \
|
|
566
|
-
--title "Fractions" \
|
|
567
|
-
--target "5th grade" \
|
|
568
|
-
--no-quiz \
|
|
569
|
-
--idempotency-key lesson-001
|
|
570
|
-
```
|
|
571
|
-
|
|
572
|
-
#### `module list`
|
|
573
|
-
|
|
574
|
-
GET `/v1/platform/modules?limit=<n>&offset=<n>[&search=<q>]`.
|
|
575
|
-
|
|
576
|
-
```bash
|
|
577
|
-
tutorflow --json module list --limit 20 --offset 0 --search fractions
|
|
578
|
-
```
|
|
579
|
-
|
|
580
|
-
#### `module get <id>` / `module open <id>`
|
|
581
|
-
|
|
582
|
-
Fetch a module by ID or open its `publicUrl`.
|
|
583
|
-
|
|
584
|
-
---
|
|
585
|
-
|
|
586
|
-
### `tutorflow test`
|
|
587
|
-
|
|
588
|
-
Create and manage generated assessments.
|
|
589
|
-
|
|
590
|
-
#### `test create [prompt]`
|
|
591
|
-
|
|
592
|
-
POST `/v1/platform/tests`. Generates a test/assessment. `--idempotency-key` is
|
|
593
|
-
sent as the `Idempotency-Key` header.
|
|
594
|
-
|
|
595
|
-
| Argument / Option | Type | Default | Description |
|
|
596
|
-
| ------------------------- | --------------------- | ---------- | ------------------------------ |
|
|
597
|
-
| `[prompt]` | `string` (positional) | — | Test generation prompt |
|
|
598
|
-
| `--title <title>` | `string` | none | Override generated title |
|
|
599
|
-
| `--description <text>` | `string` | none | Override generated description |
|
|
600
|
-
| `-l, --language <lang>` | `string` | `en` | Language code |
|
|
601
|
-
| `--level <level>` | `string` | none | Difficulty level |
|
|
602
|
-
| `--subject <subject>` | `string` | none | Subject area |
|
|
603
|
-
| `-n, --items <number>` | `string -> int` | none | Target item count |
|
|
604
|
-
| `--time-limit <minutes>` | `string -> int` | none | Time limit in minutes |
|
|
605
|
-
| `--classroom-id <id>` | `string` | none | Classroom ID |
|
|
606
|
-
| `--tier <tier>` | `string` | `standard` | Pricing tier |
|
|
607
|
-
| `--mode <mode>` | `string` | `sync` | `sync` or `async` |
|
|
608
|
-
| `--idempotency-key <key>` | `string` | none | Safe retry key |
|
|
609
|
-
|
|
610
|
-
```bash
|
|
611
|
-
tutorflow --json test create "Algebra quiz" \
|
|
612
|
-
--title "Algebra Basics" \
|
|
613
|
-
--items 8 \
|
|
614
|
-
--time-limit 30 \
|
|
615
|
-
--idempotency-key test-001
|
|
616
|
-
```
|
|
617
|
-
|
|
618
|
-
#### `test list`
|
|
619
|
-
|
|
620
|
-
GET `/v1/platform/tests?limit=<n>&offset=<n>[&status=<s>]`.
|
|
621
|
-
|
|
622
|
-
```bash
|
|
623
|
-
tutorflow --json test list --limit 20 --status READY
|
|
624
|
-
```
|
|
625
|
-
|
|
626
|
-
#### `test get <id>` / `test open <id>` / `test edit-token <id>`
|
|
627
|
-
|
|
628
|
-
Fetch a test, open its preview URL, or generate a fresh edit token.
|
|
629
|
-
|
|
630
|
-
---
|
|
631
|
-
|
|
632
|
-
### `tutorflow evaluate`
|
|
633
|
-
|
|
634
|
-
Evaluates a single learner response through the Platform Evaluations API.
|
|
635
|
-
|
|
636
|
-
| Option | Type | Default | Allowed values / notes |
|
|
637
|
-
| ------------------------- | -------- | ---------- | ---------------------- |
|
|
638
|
-
| `-t, --type <type>` | `string` | (prompted) | `exact`, `rubric_short_answer`, `open_ended`. Aliases: `rubric`, `open-ended` |
|
|
639
|
-
| `-q, --question <text>` | `string` | (prompted) | Question text |
|
|
640
|
-
| `-a, --student-answer <text>` | `string` | (prompted) | Learner answer |
|
|
641
|
-
| `-e, --expected-answer <text>` | `string` | none | Required for `exact`; sent as `referenceAnswer` |
|
|
642
|
-
| `--rubric-json <json>` | `string` | none | Required for `rubric_short_answer` |
|
|
643
|
-
| `--max-score <number>` | `number` | backend default | Maximum score |
|
|
644
|
-
| `-l, --language <lang>` | `string` | `en` | Feedback language |
|
|
645
|
-
| `--idempotency-key <key>` | `string` | none | Safe retry key |
|
|
646
|
-
|
|
647
|
-
Interactive flow:
|
|
648
|
-
|
|
649
|
-
1. Type (if not supplied)
|
|
650
|
-
2. Question
|
|
651
|
-
3. Student answer
|
|
652
|
-
4. Expected answer — required only when type is `exact`
|
|
653
|
-
|
|
654
|
-
POST `/v1/platform/evaluations` body:
|
|
655
|
-
|
|
656
|
-
```json
|
|
657
|
-
{
|
|
658
|
-
"evaluationType": "exact",
|
|
659
|
-
"questionText": "What is 2+2?",
|
|
660
|
-
"learnerAnswer": "4",
|
|
661
|
-
"referenceAnswer": "4",
|
|
662
|
-
"language": "en",
|
|
663
|
-
"mode": "sync"
|
|
664
|
-
}
|
|
665
|
-
```
|
|
666
|
-
|
|
667
|
-
`referenceAnswer` is omitted when `evaluationType` is `open_ended`.
|
|
668
|
-
`rubric_short_answer` requires `--rubric-json`, for example:
|
|
669
|
-
|
|
670
|
-
```bash
|
|
671
|
-
tutorflow --json evaluate \
|
|
672
|
-
--type rubric_short_answer \
|
|
673
|
-
--question "Explain the water cycle" \
|
|
674
|
-
--student-answer "Water evaporates and condenses." \
|
|
675
|
-
--rubric-json '{"criteria":[{"name":"Accuracy","description":"Scientific correctness","maxScore":10}]}'
|
|
676
|
-
```
|
|
677
|
-
|
|
678
|
-
---
|
|
679
|
-
|
|
680
|
-
### `tutorflow auth`
|
|
681
|
-
|
|
682
|
-
| Subcommand | Description |
|
|
683
|
-
| ---------- | ---------------------------------------------------- |
|
|
684
|
-
| `login` | Interactive prompt for API key, writes to config |
|
|
685
|
-
| `logout` | Prompts to confirm, then deletes config file |
|
|
686
|
-
| `whoami` | Prints masked API key (first 10 chars) and `baseUrl` |
|
|
687
|
-
|
|
688
|
-
## Exit behavior & errors
|
|
689
|
-
|
|
690
|
-
- All commands wrap their action in `try/catch`. Failures print `✗ <message>` in red and the raw error, but the process exit code is **not** set explicitly — it's whatever Node returns (typically `0`). Don't rely on exit codes for scripting yet.
|
|
691
|
-
- `await prompt(...)` from `@clack/prompts` returns a `symbol` when the user cancels (Ctrl+C); commands check for this and print `Cancelled` instead of throwing.
|
|
692
|
-
- HTTP errors from the API are surfaced as `Error: API Error: <status> <body-json>`.
|
|
693
|
-
|
|
694
|
-
## API client
|
|
695
|
-
|
|
696
|
-
Internal HTTP client lives in [src/client.ts](src/client.ts):
|
|
697
|
-
|
|
698
|
-
```ts
|
|
699
|
-
class ApiClient {
|
|
700
|
-
static async create(): Promise<ApiClient>; // loads from ~/.tutorflow/config.json
|
|
701
|
-
request<T>(method: string, endpoint: string, body?: object): Promise<T>;
|
|
702
|
-
setApiKey(apiKey: string): void;
|
|
703
|
-
setBaseUrl(baseUrl: string): void;
|
|
704
|
-
}
|
|
705
|
-
```
|
|
706
|
-
|
|
707
|
-
- Uses [`undici.fetch`](https://github.com/nodejs/undici).
|
|
708
|
-
- Headers always include `Content-Type: application/json`. Authenticated requests also include `Authorization: Bearer <apiKey>`.
|
|
709
|
-
- Throws `Error('API Error: <status> <body>')` on non-2xx.
|
|
710
|
-
|
|
711
|
-
## Project layout
|
|
712
|
-
|
|
713
|
-
```
|
|
714
|
-
.
|
|
715
|
-
├── package.json # bin: { tutorflow: ./dist/index.js }
|
|
716
|
-
├── tsconfig.json
|
|
717
|
-
├── vitest.config.ts
|
|
718
|
-
├── .github/workflows/
|
|
719
|
-
│ ├── ci.yml # typecheck + build + test on PRs and master
|
|
720
|
-
│ └── release.yml # OIDC publish on master/main pushes when version is new
|
|
721
|
-
├── RELEASING.md # full release procedure + Trusted Publisher setup
|
|
722
|
-
└── src/
|
|
723
|
-
├── index.ts # commander entrypoint, registers all commands
|
|
724
|
-
├── client.ts # undici-based ApiClient
|
|
725
|
-
├── client.test.ts
|
|
726
|
-
├── config.ts # ~/.tutorflow/config.json read/write/update/delete
|
|
727
|
-
├── config.test.ts
|
|
728
|
-
├── types.ts # API response interfaces (SlideResource, etc.)
|
|
729
|
-
├── commands/
|
|
730
|
-
│ ├── agent.ts # register / account / billing / email verification
|
|
731
|
-
│ ├── auth.ts # login / logout / whoami
|
|
732
|
-
│ ├── config.ts # get / set
|
|
733
|
-
│ ├── course.ts # create / list / open
|
|
734
|
-
│ ├── evaluate.ts # interactive evaluation
|
|
735
|
-
│ ├── module.ts # standalone lesson modules
|
|
736
|
-
│ ├── platform.ts # discovery / pricing / health
|
|
737
|
-
│ ├── slide.ts # create / list / open
|
|
738
|
-
│ ├── test.ts # generated assessments
|
|
739
|
-
│ └── video.ts # create / list / open
|
|
740
|
-
└── utils/
|
|
741
|
-
├── output.ts # formatTable, formatJson
|
|
742
|
-
└── spinner.ts
|
|
743
|
-
```
|
|
744
|
-
|
|
745
|
-
## npm scripts
|
|
746
|
-
|
|
747
|
-
| Script | Command | Purpose |
|
|
748
|
-
| ---------------- | ----------------------- | --------------------------------------- |
|
|
749
|
-
| `dev` | `tsx src/index.ts` | Run from source |
|
|
750
|
-
| `build` | `tsc --outDir dist` | Emit JS + `.d.ts` to `dist/` |
|
|
751
|
-
| `link` | `npm link` | Symlink `tutorflow` globally |
|
|
752
|
-
| `typecheck` | `tsc --noEmit` | Type-check without emitting |
|
|
753
|
-
| `test` | `vitest run` | Run all tests once |
|
|
754
|
-
| `test:watch` | `vitest` | Watch-mode tests |
|
|
755
|
-
| `test:coverage` | `vitest run --coverage` | Generate coverage report |
|
|
756
|
-
| `prepublishOnly` | `rm -rf dist && build` | Auto-runs before `npm publish` |
|
|
757
|
-
|
|
758
346
|
## Stack
|
|
759
347
|
|
|
760
|
-
|
|
761
|
-
- **Prompts:** [@clack/prompts](https://github.com/natemoo-re/clack) `^0.7.0`
|
|
762
|
-
- **HTTP:** [undici](https://github.com/nodejs/undici) `^5.27.0`
|
|
763
|
-
- **Color:** [chalk](https://github.com/chalk/chalk) `^5.3.0`
|
|
764
|
-
- **Browser open:** [open](https://github.com/sindresorhus/open) `^9.0.0`
|
|
765
|
-
- **Tests:** [Vitest](https://vitest.dev) `^4.1.5`
|
|
766
|
-
- **Dev:** TypeScript `^5.0.0`, tsx `^3.0.0`, `@types/node` `^20.0.0`
|
|
348
|
+
[commander](https://github.com/tj/commander.js) 11, [@clack/prompts](https://github.com/natemoo-re/clack) 0.7, [undici](https://github.com/nodejs/undici) 5, [chalk](https://github.com/chalk/chalk) 5, [open](https://github.com/sindresorhus/open) 9; Vitest 4, TypeScript 5, tsx 3.
|