@tutorflow/cli 0.1.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/LICENSE +21 -0
- package/README.md +441 -0
- package/dist/client.d.ts +13 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +48 -0
- package/dist/client.js.map +1 -0
- package/dist/commands/auth.d.ts +3 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +72 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +65 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/course.d.ts +3 -0
- package/dist/commands/course.d.ts.map +1 -0
- package/dist/commands/course.js +113 -0
- package/dist/commands/course.js.map +1 -0
- package/dist/commands/evaluate.d.ts +3 -0
- package/dist/commands/evaluate.d.ts.map +1 -0
- package/dist/commands/evaluate.js +68 -0
- package/dist/commands/evaluate.js.map +1 -0
- package/dist/commands/slide.d.ts +3 -0
- package/dist/commands/slide.d.ts.map +1 -0
- package/dist/commands/slide.js +116 -0
- package/dist/commands/slide.js.map +1 -0
- package/dist/commands/video.d.ts +3 -0
- package/dist/commands/video.d.ts.map +1 -0
- package/dist/commands/video.js +120 -0
- package/dist/commands/video.js.map +1 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +80 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +34 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/output.d.ts +3 -0
- package/dist/utils/output.d.ts.map +1 -0
- package/dist/utils/output.js +36 -0
- package/dist/utils/output.js.map +1 -0
- package/dist/utils/spinner.d.ts +8 -0
- package/dist/utils/spinner.d.ts.map +1 -0
- package/dist/utils/spinner.js +26 -0
- package/dist/utils/spinner.js.map +1 -0
- package/package.json +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TutorFlow
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
# @tutorflow/cli
|
|
2
|
+
|
|
3
|
+
TutorFlow CLI for creating AI-powered educational content (slides, videos, courses) and evaluating student responses from the terminal. Wraps the TutorFlow Platform API (`/v1/platform/*`).
|
|
4
|
+
|
|
5
|
+
- **Binary:** `tutorflow`
|
|
6
|
+
- **Version:** `0.1.0`
|
|
7
|
+
- **Node:** `>= 18`
|
|
8
|
+
- **Package:** `@tutorflow/cli`
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
### A. End users (other machines)
|
|
13
|
+
|
|
14
|
+
Once published to npm, install globally with any package manager:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install -g @tutorflow/cli
|
|
18
|
+
pnpm add -g @tutorflow/cli
|
|
19
|
+
yarn global add @tutorflow/cli
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Then anywhere on the system:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
tutorflow --version
|
|
26
|
+
tutorflow auth login
|
|
27
|
+
tutorflow slide create "My first deck"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
> **One-shot use without installing** (npm 5.2+):
|
|
31
|
+
>
|
|
32
|
+
> ```bash
|
|
33
|
+
> npx @tutorflow/cli slide create "My first deck"
|
|
34
|
+
> ```
|
|
35
|
+
|
|
36
|
+
If you publish to a private registry (e.g. GitHub Packages or a self-hosted Verdaccio), users first authenticate to that registry and scope the `@tutorflow` namespace to it:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm config set @tutorflow:registry https://npm.pkg.github.com
|
|
40
|
+
npm login --scope=@tutorflow --registry=https://npm.pkg.github.com
|
|
41
|
+
npm install -g @tutorflow/cli
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### B. Local development (this monorepo)
|
|
45
|
+
|
|
46
|
+
From the monorepo root:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pnpm install
|
|
50
|
+
pnpm --filter @tutorflow/cli build # tsc → ./dist
|
|
51
|
+
pnpm --filter @tutorflow/cli link # npm link → exposes `tutorflow` globally
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Verify:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
tutorflow --version # 0.1.0
|
|
58
|
+
tutorflow --help
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
To unlink later: `npm unlink -g @tutorflow/cli`.
|
|
62
|
+
|
|
63
|
+
## Publishing
|
|
64
|
+
|
|
65
|
+
The package is **scoped** (`@tutorflow/...`), so the first publish needs `--access public` (or a paid org plan for private packages).
|
|
66
|
+
|
|
67
|
+
### One-time setup
|
|
68
|
+
|
|
69
|
+
1. Make sure you're a member of the `@tutorflow` npm org with publish rights.
|
|
70
|
+
2. Login from the CLI:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npm login # public registry
|
|
74
|
+
# or:
|
|
75
|
+
npm login --scope=@tutorflow --registry=<your-private-registry>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
3. Confirm `apps/cli/package.json` has the right metadata. Recommended fields to add before the first release:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"files": ["dist", "README.md"],
|
|
83
|
+
"publishConfig": { "access": "public" },
|
|
84
|
+
"repository": "https://github.com/<org>/tutorflow-monorepo",
|
|
85
|
+
"license": "UNLICENSED"
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The `files` allowlist keeps `src/`, `node_modules/`, and `tsconfig.json` out of the tarball.
|
|
90
|
+
|
|
91
|
+
### Release flow
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
cd apps/cli
|
|
95
|
+
|
|
96
|
+
# 1. bump version (creates a git tag)
|
|
97
|
+
npm version patch # 0.1.0 → 0.1.1
|
|
98
|
+
# or: npm version minor / major / prerelease --preid=beta
|
|
99
|
+
|
|
100
|
+
# 2. clean build
|
|
101
|
+
rm -rf dist
|
|
102
|
+
pnpm build
|
|
103
|
+
|
|
104
|
+
# 3. (optional) inspect what will be published
|
|
105
|
+
npm pack --dry-run
|
|
106
|
+
|
|
107
|
+
# 4. publish
|
|
108
|
+
npm publish --access public
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
For pre-release channels (so end users don't auto-pull untested versions):
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npm version prerelease --preid=beta # 0.1.1 → 0.1.2-beta.0
|
|
115
|
+
npm publish --tag beta --access public
|
|
116
|
+
# users install with: npm install -g @tutorflow/cli@beta
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### CI publishing (sketch)
|
|
120
|
+
|
|
121
|
+
In GitHub Actions, after `pnpm build`:
|
|
122
|
+
|
|
123
|
+
```yaml
|
|
124
|
+
- run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
|
|
125
|
+
- run: npm publish --access public
|
|
126
|
+
working-directory: apps/cli
|
|
127
|
+
env:
|
|
128
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Trigger on tags matching `cli-v*` or use `changesets` for monorepo-aware versioning.
|
|
132
|
+
|
|
133
|
+
## Development
|
|
134
|
+
|
|
135
|
+
Run with `tsx` (no build step):
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
pnpm --filter @tutorflow/cli dev -- <command> [options]
|
|
139
|
+
# e.g. pnpm --filter @tutorflow/cli dev -- slide list --limit 5
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Build artifacts land in `apps/cli/dist/` (entry: `dist/index.js`, declared in `package.json#bin`).
|
|
143
|
+
|
|
144
|
+
## Authentication
|
|
145
|
+
|
|
146
|
+
Generate an API key in your TutorFlow workspace, then:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
tutorflow auth login # interactive password prompt for API key
|
|
150
|
+
tutorflow auth whoami # prints masked API key + base URL
|
|
151
|
+
tutorflow auth logout # confirms then deletes ~/.tutorflow/config.json
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Credentials are persisted to `~/.tutorflow/config.json`:
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"apiKey": "tf_xxxxxxxxxxxxxxxxxxxx",
|
|
159
|
+
"baseUrl": "https://api.tutorflow.io",
|
|
160
|
+
"workspace": "optional-workspace-slug"
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The API key is sent as the `X-API-Key` header on every request.
|
|
165
|
+
|
|
166
|
+
### Config schema
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
interface IConfig {
|
|
170
|
+
apiKey?: string; // required for all non-auth/config commands
|
|
171
|
+
baseUrl?: string; // default: https://api.tutorflow.io
|
|
172
|
+
workspace?: string; // optional workspace slug
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Configuration commands
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
tutorflow config get # print full config (apiKey is masked)
|
|
180
|
+
tutorflow config get <key> # print one key as KEY=VALUE
|
|
181
|
+
tutorflow config set <key> <value> # update one key
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
| Key | Type | Default | Notes |
|
|
185
|
+
| ----------- | -------- | -------------------------- | -------------------------------------- |
|
|
186
|
+
| `apiKey` | `string` | none | Set via `auth login` or `config set` |
|
|
187
|
+
| `baseUrl` | `string` | `https://api.tutorflow.io` | Override for staging / self-hosted |
|
|
188
|
+
| `workspace` | `string` | none | Reserved for future scoping |
|
|
189
|
+
|
|
190
|
+
`config set` rejects unknown keys.
|
|
191
|
+
|
|
192
|
+
## Commands
|
|
193
|
+
|
|
194
|
+
All resource commands require authentication. Without an API key they print:
|
|
195
|
+
`✗ Not authenticated. Run "tutorflow auth login" first.`
|
|
196
|
+
|
|
197
|
+
### `tutorflow slide`
|
|
198
|
+
|
|
199
|
+
Create and manage slide decks.
|
|
200
|
+
|
|
201
|
+
#### `slide create [prompt]`
|
|
202
|
+
|
|
203
|
+
Creates a slide deck synchronously (`mode: 'sync'`).
|
|
204
|
+
|
|
205
|
+
| Argument / Option | Type | Default | Description |
|
|
206
|
+
| ----------------------- | --------------------- | ------- | ------------------------------------ |
|
|
207
|
+
| `[prompt]` | `string` (positional) | — | Prompted interactively if omitted |
|
|
208
|
+
| `-n, --count <number>` | `string → int` | `10` | Slide count (parsed via `parseInt`) |
|
|
209
|
+
| `-l, --language <lang>` | `string` | `en` | ISO 639-1 language code |
|
|
210
|
+
| `-t, --theme <theme>` | `string` | none | Theme identifier |
|
|
211
|
+
|
|
212
|
+
POST `/v1/platform/slides` body:
|
|
213
|
+
|
|
214
|
+
```json
|
|
215
|
+
{
|
|
216
|
+
"prompt": "...",
|
|
217
|
+
"slideCount": 10,
|
|
218
|
+
"language": "en",
|
|
219
|
+
"theme": "...",
|
|
220
|
+
"mode": "sync"
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Output: `id`, `status`, `previewUrl` (if returned). After creation, prompts to open the preview URL in the browser.
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
tutorflow slide create "Photosynthesis for 5th graders" -n 12 -l en -t classic
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
#### `slide list`
|
|
231
|
+
|
|
232
|
+
| Option | Type | Default | Description |
|
|
233
|
+
| ----------------------- | -------- | ------- | -------------------------- |
|
|
234
|
+
| `-l, --limit <number>` | `string` | `10` | Max rows to fetch |
|
|
235
|
+
| `-s, --status <status>` | `string` | none | Server-side status filter |
|
|
236
|
+
|
|
237
|
+
GET `/v1/platform/slides?limit=<n>[&status=<s>]`. Renders an ASCII table with `ID` (8-char prefix), `Title`, `Status`, `Created`.
|
|
238
|
+
|
|
239
|
+
#### `slide open <id>`
|
|
240
|
+
|
|
241
|
+
Fetches `/v1/platform/slides/:id` and opens `previewUrl` in the default browser via [`open`](https://github.com/sindresorhus/open).
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
### `tutorflow video`
|
|
246
|
+
|
|
247
|
+
Create and manage AI-generated videos.
|
|
248
|
+
|
|
249
|
+
#### `video create [prompt]`
|
|
250
|
+
|
|
251
|
+
Creates a video synchronously (`mode: 'sync'`).
|
|
252
|
+
|
|
253
|
+
| Argument / Option | Type | Default | Description |
|
|
254
|
+
| -------------------------- | --------------------- | ------- | ---------------------------------------- |
|
|
255
|
+
| `[prompt]` | `string` (positional) | — | Prompted interactively if omitted |
|
|
256
|
+
| `-s, --scenes <number>` | `string → int` | `5` | Scene count |
|
|
257
|
+
| `-r, --ratio <ratio>` | `string` | `16:9` | Aspect ratio (`16:9`, `9:16`, `1:1`, …) |
|
|
258
|
+
| `-d, --duration <seconds>` | `string → int` | `60` | Target duration in seconds |
|
|
259
|
+
| `-l, --language <lang>` | `string` | `en` | ISO 639-1 language code |
|
|
260
|
+
|
|
261
|
+
POST `/v1/platform/videos` body:
|
|
262
|
+
|
|
263
|
+
```json
|
|
264
|
+
{
|
|
265
|
+
"prompt": "...",
|
|
266
|
+
"sceneCount": 5,
|
|
267
|
+
"aspectRatio": "16:9",
|
|
268
|
+
"targetDurationSeconds": 60,
|
|
269
|
+
"language": "en",
|
|
270
|
+
"mode": "sync"
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Output: `id`, `status`, `sceneCount`, `previewUrl` (if returned). Prompts to open in browser.
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
tutorflow video create "Intro to Newton's laws" -s 6 -r 16:9 -d 90 -l en
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
#### `video list`
|
|
281
|
+
|
|
282
|
+
| Option | Type | Default | Description |
|
|
283
|
+
| ----------------------- | -------- | ------- | -------------------------- |
|
|
284
|
+
| `-l, --limit <number>` | `string` | `10` | Max rows |
|
|
285
|
+
| `-s, --status <status>` | `string` | none | Server-side status filter |
|
|
286
|
+
|
|
287
|
+
GET `/v1/platform/videos?limit=<n>[&status=<s>]`. Table columns: `ID`, `Title`, `Status`, `Scenes`, `Created`.
|
|
288
|
+
|
|
289
|
+
#### `video open <id>`
|
|
290
|
+
|
|
291
|
+
Opens the video preview URL in the browser.
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
### `tutorflow course`
|
|
296
|
+
|
|
297
|
+
Create and manage multi-lesson courses.
|
|
298
|
+
|
|
299
|
+
#### `course create [prompt]`
|
|
300
|
+
|
|
301
|
+
| Argument / Option | Type | Default | Description |
|
|
302
|
+
| -------------------------- | --------------------- | ------- | --------------------------------- |
|
|
303
|
+
| `[prompt]` | `string` (positional) | — | Prompted interactively if omitted |
|
|
304
|
+
| `-l, --lessons <number>` | `string → int` | `8` | Lesson count |
|
|
305
|
+
| `-lang, --language <lang>` | `string` | `en` | ISO 639-1 language code |
|
|
306
|
+
|
|
307
|
+
> Note: `--language` uses the short flag `-lang` (not `-L`), because `-l` is already taken by `--lessons`.
|
|
308
|
+
|
|
309
|
+
POST `/v1/platform/courses` body:
|
|
310
|
+
|
|
311
|
+
```json
|
|
312
|
+
{
|
|
313
|
+
"prompt": "...",
|
|
314
|
+
"lessonCount": 8,
|
|
315
|
+
"language": "en",
|
|
316
|
+
"mode": "sync"
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
tutorflow course create "Beginner Python in Korean" -l 10 -lang ko
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
#### `course list`
|
|
325
|
+
|
|
326
|
+
| Option | Type | Default | Description |
|
|
327
|
+
| ---------------------- | -------- | ------- | ----------- |
|
|
328
|
+
| `-l, --limit <number>` | `string` | `10` | Max rows |
|
|
329
|
+
|
|
330
|
+
GET `/v1/platform/courses?limit=<n>`. Table columns: `ID`, `Title`, `Status`, `Created`.
|
|
331
|
+
|
|
332
|
+
#### `course open <id>`
|
|
333
|
+
|
|
334
|
+
Opens the course preview URL in the browser.
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
### `tutorflow evaluate`
|
|
339
|
+
|
|
340
|
+
Evaluates a single student response. Fully interactive; only the type can be passed as a flag.
|
|
341
|
+
|
|
342
|
+
| Option | Type | Default | Allowed values |
|
|
343
|
+
| ------------------- | -------- | ---------- | ------------------------------- |
|
|
344
|
+
| `-t, --type <type>` | `string` | (prompted) | `exact`, `rubric`, `open-ended` |
|
|
345
|
+
|
|
346
|
+
Interactive flow:
|
|
347
|
+
|
|
348
|
+
1. Type (if not supplied)
|
|
349
|
+
2. Question
|
|
350
|
+
3. Student answer
|
|
351
|
+
4. Expected answer — **skipped** when type is `open-ended`
|
|
352
|
+
|
|
353
|
+
POST `/v1/platform/evaluate` body:
|
|
354
|
+
|
|
355
|
+
```json
|
|
356
|
+
{
|
|
357
|
+
"evaluationType": "rubric",
|
|
358
|
+
"question": "...",
|
|
359
|
+
"studentAnswer": "...",
|
|
360
|
+
"expectedAnswer": "..."
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
`expectedAnswer` is omitted when `evaluationType` is `open-ended`. Response prints `score` and `feedback`.
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
tutorflow evaluate -t open-ended
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
### `tutorflow auth`
|
|
373
|
+
|
|
374
|
+
| Subcommand | Description |
|
|
375
|
+
| ---------- | ---------------------------------------------------- |
|
|
376
|
+
| `login` | Interactive prompt for API key, writes to config |
|
|
377
|
+
| `logout` | Prompts to confirm, then deletes config file |
|
|
378
|
+
| `whoami` | Prints masked API key (first 10 chars) and `baseUrl` |
|
|
379
|
+
|
|
380
|
+
## Exit behavior & errors
|
|
381
|
+
|
|
382
|
+
- 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.
|
|
383
|
+
- `await prompt(...)` from `@clack/prompts` returns a `symbol` when the user cancels (Ctrl+C); commands check for this and print `Cancelled` instead of throwing.
|
|
384
|
+
- HTTP errors from the API are surfaced as `Error: API Error: <status> <body-json>`.
|
|
385
|
+
|
|
386
|
+
## API client
|
|
387
|
+
|
|
388
|
+
Internal HTTP client lives in [src/client.ts](src/client.ts):
|
|
389
|
+
|
|
390
|
+
```ts
|
|
391
|
+
class ApiClient {
|
|
392
|
+
static async create(): Promise<ApiClient>; // loads from ~/.tutorflow/config.json
|
|
393
|
+
request<T>(method: string, endpoint: string, body?: object): Promise<T>;
|
|
394
|
+
setApiKey(apiKey: string): void;
|
|
395
|
+
setBaseUrl(baseUrl: string): void;
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
- Uses [`undici.fetch`](https://github.com/nodejs/undici).
|
|
400
|
+
- Headers always include `Content-Type: application/json` and `X-API-Key: <apiKey>`.
|
|
401
|
+
- Throws `Error('API Error: <status> <body>')` on non-2xx.
|
|
402
|
+
|
|
403
|
+
## Project layout
|
|
404
|
+
|
|
405
|
+
```
|
|
406
|
+
apps/cli/
|
|
407
|
+
├── package.json # bin: { tutorflow: ./dist/index.js }
|
|
408
|
+
├── tsconfig.json
|
|
409
|
+
└── src/
|
|
410
|
+
├── index.ts # commander entrypoint, registers all commands
|
|
411
|
+
├── client.ts # undici-based ApiClient
|
|
412
|
+
├── config.ts # ~/.tutorflow/config.json read/write/update/delete
|
|
413
|
+
├── commands/
|
|
414
|
+
│ ├── auth.ts # login / logout / whoami
|
|
415
|
+
│ ├── config.ts # get / set
|
|
416
|
+
│ ├── course.ts # create / list / open
|
|
417
|
+
│ ├── evaluate.ts # interactive evaluation
|
|
418
|
+
│ ├── slide.ts # create / list / open
|
|
419
|
+
│ └── video.ts # create / list / open
|
|
420
|
+
└── utils/
|
|
421
|
+
├── output.ts # formatTable, formatJson
|
|
422
|
+
└── spinner.ts
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## npm scripts
|
|
426
|
+
|
|
427
|
+
| Script | Command | Purpose |
|
|
428
|
+
| ------- | ------------------- | ---------------------------- |
|
|
429
|
+
| `dev` | `tsx src/index.ts` | Run from source |
|
|
430
|
+
| `build` | `tsc --outDir dist` | Emit JS + `.d.ts` to `dist/` |
|
|
431
|
+
| `link` | `npm link` | Symlink `tutorflow` globally |
|
|
432
|
+
| `test` | `jest` | (No tests committed yet) |
|
|
433
|
+
|
|
434
|
+
## Stack
|
|
435
|
+
|
|
436
|
+
- **CLI framework:** [commander](https://github.com/tj/commander.js) `^11.0.0`
|
|
437
|
+
- **Prompts:** [@clack/prompts](https://github.com/natemoo-re/clack) `^0.7.0`
|
|
438
|
+
- **HTTP:** [undici](https://github.com/nodejs/undici) `^5.27.0`
|
|
439
|
+
- **Color:** [chalk](https://github.com/chalk/chalk) `^5.3.0`
|
|
440
|
+
- **Browser open:** [open](https://github.com/sindresorhus/open) `^9.0.0`
|
|
441
|
+
- **Dev:** TypeScript `^5.0.0`, tsx `^3.0.0`, `@types/node` `^20.0.0`
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface IClientConfig {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
}
|
|
5
|
+
export declare class ApiClient {
|
|
6
|
+
private config;
|
|
7
|
+
constructor(config?: IClientConfig);
|
|
8
|
+
static create(): Promise<ApiClient>;
|
|
9
|
+
request<T>(method: string, endpoint: string, body?: Record<string, unknown>): Promise<T>;
|
|
10
|
+
setApiKey(apiKey: string): void;
|
|
11
|
+
setBaseUrl(baseUrl: string): void;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAgB;gBAElB,MAAM,CAAC,EAAE,aAAa;WAOrB,MAAM,IAAI,OAAO,CAAC,SAAS,CAAC;IAQnC,OAAO,CAAC,CAAC,EACb,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CAAC,CAAC,CAAC;IA0Bb,SAAS,CAAC,MAAM,EAAE,MAAM;IAIxB,UAAU,CAAC,OAAO,EAAE,MAAM;CAG3B"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ApiClient = void 0;
|
|
4
|
+
const undici_1 = require("undici");
|
|
5
|
+
const config_1 = require("./config");
|
|
6
|
+
class ApiClient {
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.config = config || {
|
|
9
|
+
apiKey: '',
|
|
10
|
+
baseUrl: 'https://api.tutorflow.io',
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
static async create() {
|
|
14
|
+
const config = await (0, config_1.readConfig)();
|
|
15
|
+
return new ApiClient({
|
|
16
|
+
apiKey: config.apiKey || '',
|
|
17
|
+
baseUrl: config.baseUrl || 'https://api.tutorflow.io',
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
async request(method, endpoint, body) {
|
|
21
|
+
const url = `${this.config.baseUrl}${endpoint}`;
|
|
22
|
+
const headers = {
|
|
23
|
+
'Content-Type': 'application/json',
|
|
24
|
+
'X-API-Key': this.config.apiKey,
|
|
25
|
+
};
|
|
26
|
+
const init = {
|
|
27
|
+
method,
|
|
28
|
+
headers,
|
|
29
|
+
};
|
|
30
|
+
if (body) {
|
|
31
|
+
init.body = JSON.stringify(body);
|
|
32
|
+
}
|
|
33
|
+
const response = await (0, undici_1.fetch)(url, init);
|
|
34
|
+
const data = await response.json();
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
throw new Error(`API Error: ${response.status} ${JSON.stringify(data)}`);
|
|
37
|
+
}
|
|
38
|
+
return data;
|
|
39
|
+
}
|
|
40
|
+
setApiKey(apiKey) {
|
|
41
|
+
this.config.apiKey = apiKey;
|
|
42
|
+
}
|
|
43
|
+
setBaseUrl(baseUrl) {
|
|
44
|
+
this.config.baseUrl = baseUrl;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.ApiClient = ApiClient;
|
|
48
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":";;;AAAA,mCAAyC;AACzC,qCAAsC;AAOtC,MAAa,SAAS;IAGpB,YAAY,MAAsB;QAChC,IAAI,CAAC,MAAM,GAAG,MAAM,IAAI;YACtB,MAAM,EAAE,EAAE;YACV,OAAO,EAAE,0BAA0B;SACpC,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,MAAM;QACjB,MAAM,MAAM,GAAG,MAAM,IAAA,mBAAU,GAAE,CAAC;QAClC,OAAO,IAAI,SAAS,CAAC;YACnB,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,EAAE;YAC3B,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,0BAA0B;SACtD,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,OAAO,CACX,MAAc,EACd,QAAgB,EAChB,IAA8B;QAE9B,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,GAAG,QAAQ,EAAE,CAAC;QAChD,MAAM,OAAO,GAA2B;YACtC,cAAc,EAAE,kBAAkB;YAClC,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM;SAChC,CAAC;QAEF,MAAM,IAAI,GAAuE;YAC/E,MAAM;YACN,OAAO;SACR,CAAC;QAEF,IAAI,IAAI,EAAE,CAAC;YACT,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QACnC,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,IAAA,cAAM,EAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACzC,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QAEnC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,cAAc,QAAQ,CAAC,MAAM,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3E,CAAC;QAED,OAAO,IAAS,CAAC;IACnB,CAAC;IAED,SAAS,CAAC,MAAc;QACtB,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;IAC9B,CAAC;IAED,UAAU,CAAC,OAAe;QACxB,IAAI,CAAC,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC;IAChC,CAAC;CACF;AAvDD,8BAuDC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,wBAAgB,WAAW,IAAI,OAAO,CAoErC"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.authCommand = authCommand;
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const prompts_1 = require("@clack/prompts");
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
const config_1 = require("../config");
|
|
11
|
+
function authCommand() {
|
|
12
|
+
const auth = new commander_1.Command('auth').description('Manage authentication');
|
|
13
|
+
auth
|
|
14
|
+
.command('login')
|
|
15
|
+
.description('Authenticate with your API key')
|
|
16
|
+
.action(async () => {
|
|
17
|
+
try {
|
|
18
|
+
const apiKey = await (0, prompts_1.password)({
|
|
19
|
+
message: 'Enter your TutorFlow API key:',
|
|
20
|
+
});
|
|
21
|
+
if (typeof apiKey === 'symbol') {
|
|
22
|
+
console.log(chalk_1.default.red('Authentication cancelled'));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
await (0, config_1.updateConfig)({ apiKey });
|
|
26
|
+
console.log(chalk_1.default.green('✓ Authentication successful!'));
|
|
27
|
+
console.log(chalk_1.default.gray(`API key saved to ~/.tutorflow/config.json`));
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
console.error(chalk_1.default.red('✗ Authentication failed'), error);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
auth
|
|
34
|
+
.command('logout')
|
|
35
|
+
.description('Remove stored credentials')
|
|
36
|
+
.action(async () => {
|
|
37
|
+
try {
|
|
38
|
+
const confirmed = await (0, prompts_1.confirm)({
|
|
39
|
+
message: 'Remove stored credentials?',
|
|
40
|
+
});
|
|
41
|
+
if (!confirmed) {
|
|
42
|
+
console.log(chalk_1.default.gray('Cancelled'));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
await (0, config_1.deleteConfig)();
|
|
46
|
+
console.log(chalk_1.default.green('✓ Credentials removed'));
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
console.error(chalk_1.default.red('✗ Failed to logout'), error);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
auth
|
|
53
|
+
.command('whoami')
|
|
54
|
+
.description('Show current authenticated workspace')
|
|
55
|
+
.action(async () => {
|
|
56
|
+
try {
|
|
57
|
+
const config = await (0, config_1.readConfig)();
|
|
58
|
+
if (!config.apiKey) {
|
|
59
|
+
console.log(chalk_1.default.yellow('Not authenticated'));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
console.log(chalk_1.default.green('✓ Authenticated as:'));
|
|
63
|
+
console.log(chalk_1.default.gray(` API Key: ${config.apiKey.slice(0, 10)}...`));
|
|
64
|
+
console.log(chalk_1.default.gray(` Base URL: ${config.baseUrl || 'https://api.tutorflow.io'}`));
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
console.error(chalk_1.default.red('✗ Error:'), error);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
return auth;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,wBAAgB,aAAa,IAAI,OAAO,CAuDvC"}
|