@tutorflow/cli 0.3.1 → 0.5.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 +261 -659
- 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 +27 -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 +105 -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 +20 -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 +27 -97
- package/dist/commands/slide.js.map +1 -1
- package/dist/commands/test.d.ts.map +1 -1
- package/dist/commands/test.js +40 -106
- package/dist/commands/test.js.map +1 -1
- package/dist/commands/video.d.ts.map +1 -1
- package/dist/commands/video.js +143 -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/content/sharing.d.ts +24 -0
- package/dist/content/sharing.d.ts.map +1 -0
- package/dist/content/sharing.js +88 -0
- package/dist/content/sharing.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,766 +1,368 @@
|
|
|
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:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
npm install -g @tutorflow/cli
|
|
19
|
-
pnpm add -g @tutorflow/cli
|
|
20
|
-
yarn global add @tutorflow/cli
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Then anywhere on the system:
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
tutorflow --version
|
|
27
|
-
tutorflow auth login
|
|
28
|
-
tutorflow agent register --name "My Agent Workspace" --slug my-agent-workspace --email me@example.com
|
|
29
|
-
tutorflow --json platform manifest
|
|
30
|
-
tutorflow slide create "My first deck"
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
**One-shot use without installing**:
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
npx @tutorflow/cli slide create "My first deck"
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
This is the recommended path for AI agents and CI environments — no install step, always pulls the latest published version.
|
|
40
|
-
|
|
41
|
-
### B. Local development (this repo)
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
git clone https://github.com/tutorflow-io/cli.git
|
|
45
|
-
cd cli
|
|
46
|
-
pnpm install
|
|
47
|
-
pnpm build
|
|
48
|
-
pnpm link # exposes the `tutorflow` binary globally
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
Verify:
|
|
13
|
+
## Quick start
|
|
52
14
|
|
|
53
15
|
```bash
|
|
54
|
-
tutorflow
|
|
55
|
-
tutorflow --help
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
To unlink later: `npm unlink -g @tutorflow/cli`.
|
|
59
|
-
|
|
60
|
-
Run from source without building:
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
pnpm dev -- <command> [options]
|
|
64
|
-
# e.g. pnpm dev -- slide list --limit 5
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
## Releasing
|
|
68
|
-
|
|
69
|
-
Releases are automated by GitHub Actions on pushes to `master` / `main`.
|
|
70
|
-
Auth uses **npm OIDC Trusted Publishing** — no `NPM_TOKEN` secret in CI.
|
|
71
|
-
|
|
72
|
-
```bash
|
|
73
|
-
git checkout master
|
|
74
|
-
git pull
|
|
75
|
-
|
|
76
|
-
npm version patch --no-git-tag-version # 0.3.0 → 0.3.1 (or minor / major)
|
|
77
|
-
git push origin master # Release workflow publishes if that version is not on npm yet
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
The [`Release` workflow](.github/workflows/release.yml):
|
|
81
|
-
|
|
82
|
-
1. Reads `package.json` name and version
|
|
83
|
-
2. Skips npm publish if that exact version already exists
|
|
84
|
-
3. Runs `pnpm typecheck` + `pnpm build` + `pnpm test`
|
|
85
|
-
4. Publishes new versions with `npm publish --access public` (auth via OIDC)
|
|
86
|
-
5. Creates a GitHub Release only for `v*.*.*` tag pushes
|
|
87
|
-
|
|
88
|
-
For full setup (npm org, Trusted Publisher config) see [RELEASING.md](RELEASING.md).
|
|
89
|
-
|
|
90
|
-
### Pre-release / beta
|
|
91
|
-
|
|
92
|
-
```bash
|
|
93
|
-
npm version prerelease --preid=beta --no-git-tag-version # 0.3.1 → 0.3.2-beta.0
|
|
94
|
-
git push origin master
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
Pre-release versions publish to a dist-tag derived from the prerelease id
|
|
98
|
-
(`0.3.1-beta.0` publishes with `--tag beta`).
|
|
99
|
-
|
|
100
|
-
## ⚠️ Caveats & gotchas
|
|
101
|
-
|
|
102
|
-
These tripped us up during initial setup. Read before changing release infrastructure.
|
|
103
|
-
|
|
104
|
-
### Versioning
|
|
105
|
-
|
|
106
|
-
- **Don't hardcode the version in `src/index.ts`.** It reads `version` from [`package.json`](package.json) via `resolveJsonModule`. Hardcoding will silently lie to users via `--version` after every `npm version` bump.
|
|
107
|
-
- The `prepublishOnly` script in `package.json` rebuilds `dist/` before publish. Don't skip it.
|
|
16
|
+
npm install -g @tutorflow/cli # or: npx @tutorflow/cli <command>
|
|
108
17
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
- **npm CLI must be 11.5.1+** for OIDC auth. npm also requires a modern Node 22 runtime for trusted publishing, so the workflow uses Node 22.x and invokes `pnpm dlx npm@latest ...` for registry checks and publish. Do not replace that with the runner's bundled npm unless you verify its version supports OIDC trusted publishing.
|
|
112
|
-
- The Trusted Publisher binding at [npmjs.com/package/@tutorflow/cli/access](https://www.npmjs.com/package/@tutorflow/cli/access) hardcodes:
|
|
113
|
-
- GitHub org: `tutorflow-io`
|
|
114
|
-
- Repo: `cli`
|
|
115
|
-
- Workflow filename: `release.yml`
|
|
116
|
-
- Environment: (none)
|
|
117
|
-
|
|
118
|
-
Renaming the repo, moving the workflow file, or adding an `environment:` to the publish job will break OIDC auth until the binding is updated.
|
|
119
|
-
- Local manual publishes still need a token (set `NPM_TOKEN` in `.env` or run `npm login`). Use a Granular Access Token with **"Bypass two-factor authentication (2FA)"** checked, otherwise CI/scripts get `EOTP` errors.
|
|
120
|
-
|
|
121
|
-
### Provenance
|
|
122
|
-
|
|
123
|
-
- `--provenance` is **disabled** in [release.yml](.github/workflows/release.yml). npm only accepts provenance attestations from **public** GitHub repos. Add `--provenance` back to the `npm publish` command when this repo goes public.
|
|
124
|
-
|
|
125
|
-
### Node + tooling
|
|
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
|
|
126
20
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
|
130
25
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
-
|
|
134
|
-
|
|
135
|
-
### Source maps and a private repo
|
|
136
|
-
|
|
137
|
-
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.
|
|
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
|
|
138
30
|
|
|
139
|
-
|
|
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
|
|
31
|
+
# 4. Check what it cost.
|
|
32
|
+
tutorflow credits balance
|
|
146
33
|
```
|
|
147
34
|
|
|
148
|
-
|
|
35
|
+
## Keys and classrooms
|
|
149
36
|
|
|
150
|
-
|
|
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. |
|
|
151
42
|
|
|
152
|
-
Every command
|
|
43
|
+
Every content command acts in one classroom, chosen in this order:
|
|
153
44
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
```
|
|
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
|
|
164
50
|
|
|
165
|
-
`
|
|
51
|
+
`tutorflow classroom list` shows the classrooms the key can reach.
|
|
166
52
|
|
|
167
|
-
|
|
53
|
+
## Commands
|
|
168
54
|
|
|
169
|
-
|
|
55
|
+
All ids are UUIDs. Every command accepts `--json`.
|
|
170
56
|
|
|
171
|
-
|
|
172
|
-
$ tutorflow --json auth whoami
|
|
173
|
-
{"authenticated":true,"apiKeyPreview":"tf_xxxxxxx...","baseUrl":"https://api.tutorflow.io","source":"env"}
|
|
57
|
+
### Account and discovery (`/v1/platform`)
|
|
174
58
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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 |
|
|
180
71
|
|
|
181
|
-
|
|
72
|
+
### Generated content (`/v1/content/classrooms/{classroomId}/…`)
|
|
182
73
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
[
|
|
186
|
-
|
|
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 |
|
|
187
80
|
|
|
188
|
-
|
|
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`.
|
|
189
82
|
|
|
190
|
-
|
|
191
|
-
tutorflow --json slide list --status ready | jq -r '.[].id' | xargs -I{} tutorflow --json slide get {}
|
|
192
|
-
```
|
|
83
|
+
Each of `course`, `slide`, `test`, `module`, `video`, `game` and `simulation` also has:
|
|
193
84
|
|
|
194
|
-
|
|
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
|
+
| `share <id> [--listed]` | `PATCH …/{resource}/{id}`: makes it open by link and prints `publicUrl` |
|
|
92
|
+
| `visibility <id> <value>` | `PATCH …/{resource}/{id}` with that visibility |
|
|
195
93
|
|
|
196
|
-
|
|
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) |
|
|
94
|
+
`share` sets the least open value that still opens by link, and `visibility` takes the values below (case does not matter). The PATCH answer carries the new `publicUrl`; JSON mode prints the whole answer.
|
|
202
95
|
|
|
203
|
-
|
|
96
|
+
| Resource | `share` sets | `visibility` values |
|
|
97
|
+
| --- | --- | --- |
|
|
98
|
+
| `video`, `test` | `UNLISTED` (opens by link, kept out of lists and search); `--listed` sets `PUBLIC` | `PUBLIC`, `UNLISTED`, `PRIVATE` |
|
|
99
|
+
| `slide`, `game`, `simulation` | `PUBLIC` | `PUBLIC`, `PRIVATE` |
|
|
100
|
+
| `course` | `PUBLIC` | `PUBLIC`, `PRIVATE`, `ORGANIZATION`, `CLASSROOM`, `COURSE` |
|
|
101
|
+
| `module` | `isPublic: true` | `PUBLIC`, `PRIVATE` (written as `isPublic`) |
|
|
204
102
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
Commands that have required input expose every prompt as a flag:
|
|
103
|
+
`publicUrl` can stay null after `share`: a video's watch page opens once it is rendered, a game or simulation's play page once it is built, a course page once the course is published, and the sandbox classroom of `tf_content_test_` keys has no public pages. A test's public page shows its cover; answering needs a learner sign-in. New tests start `PUBLIC`.
|
|
208
104
|
|
|
209
105
|
```bash
|
|
210
|
-
|
|
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
|
|
106
|
+
tutorflow --json video share <videoId> | jq -r .publicUrl
|
|
231
107
|
```
|
|
232
108
|
|
|
233
|
-
|
|
109
|
+
`test results <testId>` lists learners' results (`GET …/tests/{id}/results`).
|
|
234
110
|
|
|
235
|
-
###
|
|
111
|
+
### Runs
|
|
236
112
|
|
|
237
|
-
|
|
|
238
|
-
|
|
|
239
|
-
|
|
|
240
|
-
|
|
|
241
|
-
|
|
|
242
|
-
|
|
|
113
|
+
| Command | Route |
|
|
114
|
+
| --- | --- |
|
|
115
|
+
| `run list [--kind course\|slide\|test\|module] [--status] [--limit] [--cursor]` | `GET …/runs` |
|
|
116
|
+
| `run get <runId>` | `GET …/runs/{runId}` |
|
|
117
|
+
| `run wait <runId> [--timeout]` | polls `GET …/runs/{runId}`; prints `{ run, resource }` |
|
|
118
|
+
| `run resume <runId>` | `POST …/runs/{runId}/resume` (course runs only) |
|
|
243
119
|
|
|
244
|
-
|
|
120
|
+
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.
|
|
245
121
|
|
|
246
|
-
|
|
122
|
+
### Videos
|
|
247
123
|
|
|
248
|
-
|
|
124
|
+
The Content API builds videos from scenes you script; it does not write the script from a prompt.
|
|
249
125
|
|
|
250
|
-
|
|
126
|
+
| Command | Route |
|
|
127
|
+
| --- | --- |
|
|
128
|
+
| `video create --title [--description] [--visibility PUBLIC\|UNLISTED\|PRIVATE] [--scene "<script>"]…` | `POST …/videos`, then one scene per `--scene` in order |
|
|
129
|
+
| `video scene add <videoId> [--script] [--display-text] [--duration] [--after <sceneId>]` | `POST …/videos/{id}/scenes`, then `PATCH` the new scene |
|
|
130
|
+
| `video scene update <videoId> <sceneId> [--script] [--display-text] [--duration]` | `PATCH …/scenes/{sceneId}` |
|
|
131
|
+
| `video scene delete <videoId> <sceneId> --yes` | `DELETE …/scenes/{sceneId}` |
|
|
132
|
+
| `video narrate <videoId> [--scene <sceneId>]` | `POST …/narration` (1 credit per scene) |
|
|
133
|
+
| `video render <videoId> [--generate-missing-narration] [--wait]` | `POST …/videos/{id}/render` (5 credits; `--wait` polls until `COMPLETED` and prints the mp4 URL) |
|
|
134
|
+
| `video render-status <videoId>` / `video render-cancel <videoId>` | `GET …/render` / `POST …/render/cancel` |
|
|
251
135
|
|
|
252
136
|
```bash
|
|
253
|
-
tutorflow
|
|
254
|
-
|
|
255
|
-
|
|
137
|
+
tutorflow video create --title "Why the sky is blue" \
|
|
138
|
+
--scene "Sunlight is made of many colors." \
|
|
139
|
+
--scene "Air scatters blue light the most, so the sky looks blue."
|
|
140
|
+
tutorflow video render <videoId> --generate-missing-narration --wait
|
|
256
141
|
```
|
|
257
142
|
|
|
258
|
-
|
|
143
|
+
### Games and simulations
|
|
259
144
|
|
|
260
|
-
|
|
261
|
-
{
|
|
262
|
-
"apiKey": "tf_xxxxxxxxxxxxxxxxxxxx",
|
|
263
|
-
"baseUrl": "https://api.tutorflow.io",
|
|
264
|
-
"workspace": "optional-workspace-slug"
|
|
265
|
-
}
|
|
266
|
-
```
|
|
145
|
+
`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.
|
|
267
146
|
|
|
268
|
-
|
|
147
|
+
| Command | Route | Credits |
|
|
148
|
+
| --- | --- | --- |
|
|
149
|
+
| `create --title [--description] [--visibility PRIVATE\|PUBLIC]` | `POST …/{games\|simulations}` | 0 |
|
|
150
|
+
| `brief <id> [--reference <markdown>] [--wait]` | `POST …/{id}/brief` | 1 |
|
|
151
|
+
| `build <id> [--wait]` | `POST …/{id}/build` | game 40, simulation 20 (2D) or 30 (3D) |
|
|
152
|
+
| `revise <id> --feedback <text> [--wait]` | `POST …/{id}/revise` | as build |
|
|
153
|
+
| `status <id>` | `GET …/{id}/run` | |
|
|
154
|
+
|
|
155
|
+
### Evaluations and learners
|
|
156
|
+
|
|
157
|
+
| Command | Route |
|
|
158
|
+
| --- | --- |
|
|
159
|
+
| `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) |
|
|
160
|
+
| `evaluation list [--learner-id] [--status] [--type] [--limit] [--cursor]` / `evaluation get <id>` | `GET …/evaluations` |
|
|
161
|
+
| `evaluation concepts` / `evaluation mastery <learnerId>` | `GET …/concepts` / `GET …/mastery/{learner}` |
|
|
162
|
+
| `learner list` / `get <id>` / `progress <id>` | `GET …/learners…` (live keys only) |
|
|
163
|
+
| `learner invite --email [--name] [--locale] [--course <id>]… [--test <id>]…` | `POST …/learners/invitations` |
|
|
164
|
+
| `learner remove <id> --yes` | `DELETE …/learners/{id}` |
|
|
269
165
|
|
|
270
|
-
|
|
166
|
+
`--type` takes `exact`, `rubric_short_answer` or `open_ended` (aliases `rubric`, `open-ended`). `exact` needs `--expected-answer`; `rubric_short_answer` needs `--rubric-json`.
|
|
271
167
|
|
|
272
|
-
|
|
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 |
|
|
168
|
+
### Webhooks and credits (`/v1/content`)
|
|
293
169
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
170
|
+
| Command | Route |
|
|
171
|
+
| --- | --- |
|
|
172
|
+
| `webhook list` / `get <id>` | `GET /v1/content/webhooks[/{id}]` |
|
|
173
|
+
| `webhook create --url --event <event>… [--mode live\|test]` | `POST /v1/content/webhooks` (the signing secret is shown once) |
|
|
174
|
+
| `webhook test <id>` / `deliveries <id>` / `rotate-secret <id>` / `delete <id> --yes` | the matching `/v1/content/webhooks/{id}/…` routes |
|
|
175
|
+
| `credits balance` | `GET /v1/content/credits` |
|
|
176
|
+
| `credits history [--limit] [--cursor] [--from] [--to] [--type]` | `GET /v1/content/credits/history` |
|
|
297
177
|
|
|
298
|
-
|
|
299
|
-
`✗ Not authenticated. Run "tutorflow auth login" first.`
|
|
178
|
+
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.
|
|
300
179
|
|
|
301
|
-
###
|
|
180
|
+
### Auth and config
|
|
302
181
|
|
|
303
|
-
|
|
182
|
+
| Command | Effect |
|
|
183
|
+
| --- | --- |
|
|
184
|
+
| `auth login --api-key <key>` | Save a key (prompts on a TTY when omitted) |
|
|
185
|
+
| `auth whoami` | Masked key, `keyKind` (`content_live`, `content_test`, `platform`, `unknown`), base URL, `source` (`env` or `config`). No network call. |
|
|
186
|
+
| `auth logout --force` | Delete `~/.tutorflow/config.json` |
|
|
187
|
+
| `config get [key]` / `config set <key> <value>` | Keys: `apiKey`, `baseUrl`, `classroomId` |
|
|
304
188
|
|
|
305
|
-
|
|
189
|
+
The config file is written with mode `0600`.
|
|
306
190
|
|
|
307
|
-
|
|
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`.
|
|
191
|
+
## Agent usage
|
|
332
192
|
|
|
333
193
|
```bash
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
#### `agent billing status`
|
|
194
|
+
export TUTORFLOW_API_KEY=tf_content_xxx
|
|
195
|
+
export TUTORFLOW_JSON=1
|
|
338
196
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
```bash
|
|
343
|
-
tutorflow --json agent billing status
|
|
197
|
+
run=$(tutorflow course generate "Fractions for 5th grade" --lessons 2)
|
|
198
|
+
tutorflow run wait "$(echo "$run" | jq -r .runId)" | jq -r .resource.publicUrl
|
|
344
199
|
```
|
|
345
200
|
|
|
346
|
-
|
|
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
|
-
```
|
|
201
|
+
To hand the work to a person, make it open by link and send them `publicUrl`: `tutorflow course share <courseId> | jq -r .publicUrl`.
|
|
354
202
|
|
|
355
|
-
|
|
203
|
+
- **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.
|
|
204
|
+
- **No prompts without a TTY:** a missing required input exits 2 with `ENOINTERACTIVE` and names the flag to pass. Destructive commands need `--yes`.
|
|
205
|
+
- **Retries:** create and generate commands accept `--idempotency-key`.
|
|
206
|
+
- **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`.
|
|
356
207
|
|
|
357
|
-
|
|
358
|
-
|
|
208
|
+
| Exit | Meaning |
|
|
209
|
+
| --- | --- |
|
|
210
|
+
| `0` | Success |
|
|
211
|
+
| `1` | Failure: auth, network, API error, failed run, timeout |
|
|
212
|
+
| `2` | Bad input: invalid flag, missing input without a TTY, ambiguous classroom |
|
|
213
|
+
| `130` | Cancelled at a prompt |
|
|
214
|
+
|
|
215
|
+
| Code | When |
|
|
216
|
+
| --- | --- |
|
|
217
|
+
| `EAUTH` | No API key |
|
|
218
|
+
| `EPLATFORMKEY` | A `tf_platform_` key on a content command |
|
|
219
|
+
| `ECLASSROOM` | No classroom could be chosen; pass `--classroom` |
|
|
220
|
+
| `EINVAL` | Invalid flag value |
|
|
221
|
+
| `ENOINTERACTIVE` | Missing input or confirmation without a TTY |
|
|
222
|
+
| `EHTTP_<status>` | API error; see `apiCode` |
|
|
223
|
+
| `ENETWORK` | The API could not be reached |
|
|
224
|
+
| `ERUNFAILED` | A waited run, build or render failed |
|
|
225
|
+
| `ETIMEOUT` | `--wait` gave up; the work continues on the server |
|
|
226
|
+
| `ENOPREVIEW` | `open` found no URL |
|
|
227
|
+
| `ENOTFOUND` | `config get` of an unset key |
|
|
228
|
+
| `ECANCELLED` | Prompt cancelled |
|
|
229
|
+
|
|
230
|
+
| Environment variable | Effect |
|
|
231
|
+
| --- | --- |
|
|
232
|
+
| `TUTORFLOW_API_KEY` | API key (wins over the config file) |
|
|
233
|
+
| `TUTORFLOW_BASE_URL` | API origin, e.g. `https://dev-api.tutorflow.io` for staging |
|
|
234
|
+
| `TUTORFLOW_CLASSROOM_ID` | Classroom for content commands |
|
|
235
|
+
| `TUTORFLOW_JSON=1` | JSON mode |
|
|
236
|
+
| `TUTORFLOW_DEBUG=1` / `DEBUG=1` | Stack traces on errors |
|
|
237
|
+
| `TUTORFLOW_POLL_INTERVAL_MS` | `--wait` poll interval (default 3000) |
|
|
238
|
+
|
|
239
|
+
Empty environment variables are ignored.
|
|
240
|
+
|
|
241
|
+
## Migrating from 0.3
|
|
242
|
+
|
|
243
|
+
| 0.3 | Now |
|
|
244
|
+
| --- | --- |
|
|
245
|
+
| `tf_platform_` key on every command | `tf_content_` key; `agent register` saves it. Platform keys still work on `agent` and `platform`. |
|
|
246
|
+
| `/v1/platform/{courses,slides,tests,modules,videos,evaluations}` | `/v1/content/classrooms/{classroomId}/…` |
|
|
247
|
+
| `course create` returned the finished course (`mode: sync`) | `course generate` (alias `create`) returns a run; add `--wait` |
|
|
248
|
+
| `course create -l 10 -lang ko` | `course generate --lessons 10 --language ko` |
|
|
249
|
+
| `slide create -t <theme>` | `slide generate --theme <themeId>` (5 to 20 pages) |
|
|
250
|
+
| `video create "<prompt>" -s 6 -r 16:9 -d 90` | `video create --title … --scene "<script>"…`, then `video render --generate-missing-narration --wait` |
|
|
251
|
+
| `module create --title --target --locale --private --no-quiz` | `module generate [--type] [--reference]`; edit the rest in the classroom editor |
|
|
252
|
+
| `test create --classroom-id --tier --mode --time-limit` | `test generate` with the global `--classroom`; generation is always asynchronous |
|
|
253
|
+
| `test edit-token`, `previewUrl` | `editorUrl` and `publicUrl` on every resource; `open <id>` |
|
|
254
|
+
| list `--offset` | `--page` |
|
|
255
|
+
| `evaluate` → `/v1/platform/evaluations` | `evaluate` → `…/evaluations`, plus `--learner-id` and `--concept-tags` |
|
|
256
|
+
|
|
257
|
+
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`.
|
|
359
258
|
|
|
360
|
-
|
|
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.
|
|
259
|
+
## Development
|
|
427
260
|
|
|
428
261
|
```bash
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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
|
-
}
|
|
262
|
+
git clone https://github.com/tutorflow-io/cli.git && cd cli
|
|
263
|
+
pnpm install
|
|
264
|
+
pnpm build && pnpm test # the e2e suites spawn dist/index.js
|
|
265
|
+
pnpm dev -- course list # run from source
|
|
266
|
+
pnpm link # expose `tutorflow` globally
|
|
474
267
|
```
|
|
475
268
|
|
|
476
|
-
|
|
269
|
+
Try it against staging with a throwaway account:
|
|
477
270
|
|
|
478
271
|
```bash
|
|
479
|
-
|
|
272
|
+
export HOME=$(mktemp -d) TUTORFLOW_BASE_URL=https://dev-api.tutorflow.io
|
|
273
|
+
node dist/index.js agent register --name "CLI test" --slug cli-test-$(date +%s)
|
|
274
|
+
node dist/index.js course generate "Water cycle" --lessons 1 --wait
|
|
480
275
|
```
|
|
481
276
|
|
|
482
|
-
|
|
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`.
|
|
277
|
+
### Layout
|
|
510
278
|
|
|
511
|
-
POST `/v1/platform/courses` body:
|
|
512
|
-
|
|
513
|
-
```json
|
|
514
|
-
{
|
|
515
|
-
"prompt": "...",
|
|
516
|
-
"lessonCount": 8,
|
|
517
|
-
"language": "en",
|
|
518
|
-
"mode": "sync"
|
|
519
|
-
}
|
|
520
279
|
```
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
280
|
+
src/
|
|
281
|
+
├── index.ts # commander entrypoint and global flags (--json, --debug, --classroom)
|
|
282
|
+
├── client.ts # ApiClient: config resolution, HTTP, error envelope → CliError
|
|
283
|
+
├── config.ts # ~/.tutorflow/config.json (mode 0600)
|
|
284
|
+
├── content/
|
|
285
|
+
│ ├── context.ts # tf_content_ key check and classroom resolution
|
|
286
|
+
│ ├── resource.ts # shared list/get/open/delete/generate subcommands
|
|
287
|
+
│ ├── sharing.ts # shared share/visibility subcommands
|
|
288
|
+
│ └── runs.ts # waiting on runs, game/simulation builds and renders
|
|
289
|
+
├── commands/ # one file per top-level command
|
|
290
|
+
├── utils/ # runtime (JSON mode, CliError), output, args, keys
|
|
291
|
+
└── testing/harness.ts # test-only: fake API server and CLI spawner
|
|
524
292
|
```
|
|
525
293
|
|
|
526
|
-
|
|
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.
|
|
294
|
+
## Releasing
|
|
549
295
|
|
|
550
|
-
|
|
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 |
|
|
296
|
+
Releases are automated by GitHub Actions on pushes to `master` / `main`.
|
|
297
|
+
Auth uses **npm OIDC Trusted Publishing** — no `NPM_TOKEN` secret in CI.
|
|
563
298
|
|
|
564
299
|
```bash
|
|
565
|
-
|
|
566
|
-
|
|
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>]`.
|
|
300
|
+
git checkout master
|
|
301
|
+
git pull
|
|
575
302
|
|
|
576
|
-
|
|
577
|
-
|
|
303
|
+
npm version patch --no-git-tag-version # 0.3.0 → 0.3.1 (or minor / major)
|
|
304
|
+
git push origin master # Release workflow publishes if that version is not on npm yet
|
|
578
305
|
```
|
|
579
306
|
|
|
580
|
-
|
|
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 |
|
|
307
|
+
The [`Release` workflow](.github/workflows/release.yml):
|
|
609
308
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
--idempotency-key test-001
|
|
616
|
-
```
|
|
309
|
+
1. Reads `package.json` name and version
|
|
310
|
+
2. Skips npm publish if that exact version already exists
|
|
311
|
+
3. Runs `pnpm typecheck` + `pnpm build` + `pnpm test`
|
|
312
|
+
4. Publishes new versions with `npm publish --access public` (auth via OIDC)
|
|
313
|
+
5. Creates a GitHub Release only for `v*.*.*` tag pushes
|
|
617
314
|
|
|
618
|
-
|
|
315
|
+
For full setup (npm org, Trusted Publisher config) see [RELEASING.md](RELEASING.md).
|
|
619
316
|
|
|
620
|
-
|
|
317
|
+
### Pre-release / beta
|
|
621
318
|
|
|
622
319
|
```bash
|
|
623
|
-
|
|
320
|
+
npm version prerelease --preid=beta --no-git-tag-version # 0.3.1 → 0.3.2-beta.0
|
|
321
|
+
git push origin master
|
|
624
322
|
```
|
|
625
323
|
|
|
626
|
-
|
|
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
|
-
```
|
|
324
|
+
Pre-release versions publish to a dist-tag derived from the prerelease id
|
|
325
|
+
(`0.3.1-beta.0` publishes with `--tag beta`).
|
|
666
326
|
|
|
667
|
-
|
|
668
|
-
`rubric_short_answer` requires `--rubric-json`, for example:
|
|
327
|
+
## ⚠️ Caveats & gotchas
|
|
669
328
|
|
|
670
|
-
|
|
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
|
-
```
|
|
329
|
+
These tripped us up during initial setup. Read before changing release infrastructure.
|
|
677
330
|
|
|
678
|
-
|
|
331
|
+
### Versioning
|
|
679
332
|
|
|
680
|
-
|
|
333
|
+
- **Don't hardcode the version in `src/index.ts`.** It reads `version` from [`package.json`](package.json) via `resolveJsonModule`. Hardcoding will silently lie to users via `--version` after every `npm version` bump.
|
|
334
|
+
- The `prepublishOnly` script in `package.json` rebuilds `dist/` before publish. Don't skip it.
|
|
681
335
|
|
|
682
|
-
|
|
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` |
|
|
336
|
+
### npm Trusted Publishing (OIDC)
|
|
687
337
|
|
|
688
|
-
|
|
338
|
+
- **npm CLI must be 11.5.1+** for OIDC auth. npm also requires a modern Node 22 runtime for trusted publishing, so the workflow uses Node 22.x and invokes `pnpm dlx npm@latest ...` for registry checks and publish. Do not replace that with the runner's bundled npm unless you verify its version supports OIDC trusted publishing.
|
|
339
|
+
- The Trusted Publisher binding at [npmjs.com/package/@tutorflow/cli/access](https://www.npmjs.com/package/@tutorflow/cli/access) hardcodes:
|
|
340
|
+
- GitHub org: `tutorflow-io`
|
|
341
|
+
- Repo: `cli`
|
|
342
|
+
- Workflow filename: `release.yml`
|
|
343
|
+
- Environment: (none)
|
|
689
344
|
|
|
690
|
-
|
|
691
|
-
-
|
|
692
|
-
- HTTP errors from the API are surfaced as `Error: API Error: <status> <body-json>`.
|
|
345
|
+
Renaming the repo, moving the workflow file, or adding an `environment:` to the publish job will break OIDC auth until the binding is updated.
|
|
346
|
+
- Local manual publishes still need a token (set `NPM_TOKEN` in `.env` or run `npm login`). Use a Granular Access Token with **"Bypass two-factor authentication (2FA)"** checked, otherwise CI/scripts get `EOTP` errors.
|
|
693
347
|
|
|
694
|
-
|
|
348
|
+
### Provenance
|
|
695
349
|
|
|
696
|
-
|
|
350
|
+
- `--provenance` is **disabled** in [release.yml](.github/workflows/release.yml). npm only accepts provenance attestations from **public** GitHub repos. Add `--provenance` back to the `npm publish` command when this repo goes public.
|
|
697
351
|
|
|
698
|
-
|
|
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
|
-
```
|
|
352
|
+
### Node + tooling
|
|
706
353
|
|
|
707
|
-
-
|
|
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.
|
|
354
|
+
- **Vitest 4.x requires Node 20+** (uses `node:util.styleText`). CI tests Node 20 and Node 22, while release publishing runs on Node 22 for npm OIDC. The runtime CLI itself still supports Node 18 (declared in `package.json#engines`).
|
|
710
355
|
|
|
711
|
-
|
|
356
|
+
### Tarball hygiene
|
|
712
357
|
|
|
713
|
-
|
|
714
|
-
.
|
|
715
|
-
|
|
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
|
-
```
|
|
358
|
+
- `tsconfig.json` excludes `**/*.test.ts` and `src/testing/` so test code doesn't end up in `dist/` (and therefore not in the published tarball).
|
|
359
|
+
- `package.json#files` is an allowlist (`dist`, `README.md`, `LICENSE`). Anything not listed is excluded.
|
|
360
|
+
- Run `npm pack --dry-run` to inspect the tarball before publish.
|
|
744
361
|
|
|
745
|
-
|
|
362
|
+
### Source maps and a private repo
|
|
746
363
|
|
|
747
|
-
|
|
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` |
|
|
364
|
+
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.
|
|
757
365
|
|
|
758
366
|
## Stack
|
|
759
367
|
|
|
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`
|
|
368
|
+
[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.
|