@novedu/cli 0.18.0 → 0.20.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 +147 -7
- package/dist/main.js +2796 -763
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
# @novedu/cli
|
|
2
2
|
|
|
3
3
|
Command-line companion for the Novedu chat app (installed command: `novedu-cli`;
|
|
4
|
-
requires Node >=
|
|
4
|
+
requires Node >= 22 — `eval`'s glob expansion uses the built-in `fs.globSync`,
|
|
5
|
+
and Node 20 is end-of-life). It covers two jobs:
|
|
5
6
|
|
|
6
7
|
- **Validate activity YAML** — tutors, fragment libraries, quizzes, writing
|
|
7
|
-
activities,
|
|
8
|
-
offline and without signing in.
|
|
8
|
+
activities, coding activities, and golden-answer evals — with the app's exact
|
|
9
|
+
validation pipeline, offline and without signing in. `prompts` dumps the exact
|
|
10
|
+
system prompts an activity produces, the same way.
|
|
9
11
|
- **Manage the app as a teacher** — sign in with Microsoft Entra ID, then mint
|
|
10
|
-
activity codes, upload app-hosted YAML files and images,
|
|
11
|
-
reports,
|
|
12
|
+
activity codes, upload app-hosted YAML files and images, triage student
|
|
13
|
+
reports, and **measure a quiz's grading rubric** (`eval`), straight from the
|
|
14
|
+
terminal (or from a coding agent, see below).
|
|
12
15
|
|
|
13
16
|
No install needed:
|
|
14
17
|
|
|
@@ -29,12 +32,15 @@ npx @novedu/cli validate https://raw.githubusercontent.com/Teaching-HTL-Leonding
|
|
|
29
32
|
npx @novedu/cli validate ./activities/examples/shared/general-fragments.yaml --kind fragment
|
|
30
33
|
npx @novedu/cli validate ./activities/examples/sorting-algorithms/sorting-quiz.yaml --kind quiz
|
|
31
34
|
|
|
35
|
+
# A golden-answer eval (also strict-checks the quiz it targets)
|
|
36
|
+
npx @novedu/cli validate ./sorting-quiz.eval.yaml --kind eval
|
|
37
|
+
|
|
32
38
|
# Machine-readable output (the raw validation result)
|
|
33
39
|
npx @novedu/cli validate ./my-quiz.yaml --kind quiz --json
|
|
34
40
|
```
|
|
35
41
|
|
|
36
|
-
- `--kind` accepts `tutor` (default), `fragment`, `quiz`, `writing`, or
|
|
37
|
-
`
|
|
42
|
+
- `--kind` accepts `tutor` (default), `fragment`, `quiz`, `writing`, `coding`, or
|
|
43
|
+
`eval`; it is caller-declared, not auto-detected.
|
|
38
44
|
- The CLI reuses the app's exact validation pipeline (`lib/prompt-fragments`,
|
|
39
45
|
`lib/tutors`, `lib/quiz-validate`, `lib/writing-validate`,
|
|
40
46
|
`lib/coding-validate`), so an activity that passes here is the same one the
|
|
@@ -42,6 +48,140 @@ npx @novedu/cli validate ./my-quiz.yaml --kind quiz --json
|
|
|
42
48
|
validates every fragment library it references.
|
|
43
49
|
- Exit code `0` = valid, `1` = errors found — usable as a pre-commit / CI gate.
|
|
44
50
|
|
|
51
|
+
## Seeing the exact prompts: `prompts`
|
|
52
|
+
|
|
53
|
+
`prompts` prints the **exact system prompts** an activity YAML produces — the
|
|
54
|
+
strings the app really sends to the model. Offline and sign-in-free, exactly like
|
|
55
|
+
`validate`.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# A tutor's assembled system prompt (summary: kind, id, model, size per prompt)
|
|
59
|
+
npx @novedu/cli prompts ./activities/examples/sorting-algorithms/sorting-tutor.yaml
|
|
60
|
+
|
|
61
|
+
# A quiz: one grading prompt per question + the discussion prompt, full text as JSON
|
|
62
|
+
npx @novedu/cli prompts ./sorting-quiz.yaml --kind quiz --json
|
|
63
|
+
|
|
64
|
+
# A writing activity's coach prompt, a coding activity's injected system prompt
|
|
65
|
+
npx @novedu/cli prompts ./my-writing.yaml --kind writing
|
|
66
|
+
npx @novedu/cli prompts ./my-coding.yaml --kind coding
|
|
67
|
+
|
|
68
|
+
# A published activity by URL (same argument as `validate`)
|
|
69
|
+
npx @novedu/cli prompts https://raw.githubusercontent.com/Teaching-HTL-Leonding/novedu-chat-mvp/refs/heads/main/activities/examples/sorting-algorithms/sorting-tutor.yaml
|
|
70
|
+
|
|
71
|
+
# Pull out one question's grading prompt
|
|
72
|
+
npx @novedu/cli prompts ./sorting-quiz.yaml --kind quiz --json \
|
|
73
|
+
| jq -r '.grading.questions[] | select(.id=="q3") | .system'
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
- The argument is a **local path or a public `http(s)` URL**, exactly like
|
|
77
|
+
`validate`'s; relative `fragment_files` / `quiz_files` / `text_files` resolve
|
|
78
|
+
against the activity's own location (sibling file, or sibling URL). "Offline"
|
|
79
|
+
means no app server, no database and no LLM call — not "no network".
|
|
80
|
+
- `--kind` accepts `tutor` (default), `quiz`, `writing` or `coding` — the same
|
|
81
|
+
caller-declared flag as `validate`. There is no `fragment` kind: a library has
|
|
82
|
+
no prompt of its own; its fragments appear **rendered in place** inside the
|
|
83
|
+
activity that places them.
|
|
84
|
+
- Every dump comes out of the app's own prompt builders and runtime loaders (no
|
|
85
|
+
re-implementation), so what you see is what the model gets: fragments resolved,
|
|
86
|
+
and — for a compound quiz — every `quiz_files` include fetched, each imported
|
|
87
|
+
question carrying its source quiz's preamble.
|
|
88
|
+
- Every dump carries `{ kind, id, llm: { provider, model } }`. A **quiz** adds
|
|
89
|
+
`grading` (a `system` prompt per question, the user-message templates and the
|
|
90
|
+
grader's JSON-Schema response contract) and `discussion` (the chat's `system`
|
|
91
|
+
prompt, the three seed-message templates and the verdict wording). A **coding**
|
|
92
|
+
activity also reports `upstreamSystemMessage` — what the proxy puts on the wire.
|
|
93
|
+
- The **activity's own** `llm` block is reported; a code's per-code LLM override
|
|
94
|
+
is not applied (a dump describes a file, and a file has no code).
|
|
95
|
+
- This runs the runtime load path, so a file that cannot be loaded exits `1` with
|
|
96
|
+
JSON errors on stderr. Use `validate` for the strict authoring check — the two
|
|
97
|
+
are complementary.
|
|
98
|
+
|
|
99
|
+
## Measuring a quiz's grading rubric: `eval`
|
|
100
|
+
|
|
101
|
+
A quiz's `evaluation` prompt is a rubric, and a rubric is only as good as its
|
|
102
|
+
behavior on real answers. Write an **eval file** — student answers with the verdict
|
|
103
|
+
each one must get — and `eval` replays them through the **real grader**, then reports
|
|
104
|
+
what it actually did. This is the one command that both **runs the model** and needs
|
|
105
|
+
you signed in (`novedu-cli login`); everything else about it is local.
|
|
106
|
+
|
|
107
|
+
```yaml
|
|
108
|
+
# sorting-quiz.eval.yaml
|
|
109
|
+
# yaml-language-server: $schema=https://raw.githubusercontent.com/Teaching-HTL-Leonding/novedu-chat-mvp/refs/heads/main/activities/evals/eval-yaml.schema.json
|
|
110
|
+
id: sorting-quiz-eval
|
|
111
|
+
target: ./sorting-quiz.yaml # relative to THIS file, or an http(s) URL
|
|
112
|
+
questions:
|
|
113
|
+
- question: bubble-sort-complexity # the quiz's question id
|
|
114
|
+
answers:
|
|
115
|
+
- expect: correct
|
|
116
|
+
answer: |
|
|
117
|
+
O(n²) in the average and worst case.
|
|
118
|
+
- expect: [partial, incorrect] # more than one grading is defensible
|
|
119
|
+
answer: |
|
|
120
|
+
It's quadratic-ish.
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
# Run it (grading prompts are assembled locally, so an unpushed file works)
|
|
125
|
+
npx @novedu/cli eval ./sorting-quiz.eval.yaml
|
|
126
|
+
|
|
127
|
+
# A whole course part — quote the pattern so the CLI expands it (** included)
|
|
128
|
+
npx @novedu/cli eval "./part-1/**/*.eval.yaml"
|
|
129
|
+
|
|
130
|
+
# Is the grader stable? 3 runs per answer, majority verdict
|
|
131
|
+
npx @novedu/cli eval ./sorting-quiz.eval.yaml --repeats 3
|
|
132
|
+
|
|
133
|
+
# How would this rubric do on another model? (both flags, always together)
|
|
134
|
+
npx @novedu/cli eval ./sorting-quiz.eval.yaml \
|
|
135
|
+
--llm-provider "Azure Foundry" --llm-model gpt-5-mini
|
|
136
|
+
|
|
137
|
+
# Machine-readable, for CI
|
|
138
|
+
npx @novedu/cli eval ./sorting-quiz.eval.yaml --json --out eval-report.json
|
|
139
|
+
|
|
140
|
+
# A readable Markdown report to share or commit
|
|
141
|
+
npx @novedu/cli eval ./sorting-quiz.eval.yaml --report eval-report.md
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
- Check the file first, for free: `npx @novedu/cli validate ./x.eval.yaml --kind eval`
|
|
145
|
+
(offline; it also strict-checks the quiz the eval targets).
|
|
146
|
+
- **`expect`** is one of `correct` / `partial` / `incorrect`, or a list of the
|
|
147
|
+
acceptable ones. **`question`** must be a question id of the resolved quiz — for a
|
|
148
|
+
question imported via `quiz_files` that is the namespaced `"<alias>/<id>"` id.
|
|
149
|
+
- **Report semantics.** A **case** is one golden answer; `--repeats` are repeated
|
|
150
|
+
observations of that case, and the case's verdict is the **majority** (a tie passes
|
|
151
|
+
only if every tied verdict is expected). Totals, mismatches, the confusion matrix
|
|
152
|
+
and the exit code are all over case verdicts, so `--repeats 3` is never harsher
|
|
153
|
+
than `--repeats 1`. Cases whose repeats disagreed are reported as **`unstable`** —
|
|
154
|
+
the interesting `--repeats` signal — but never fail the run.
|
|
155
|
+
- The **false-correct rate** counts answers you marked as not acceptable that the
|
|
156
|
+
grader called `correct` — the dangerous direction. The confusion matrix is keyed by
|
|
157
|
+
the sorted expected set (`correct|partial`), so list order never matters.
|
|
158
|
+
- **Exit code** `0` only when every file is valid, `failed = 0`, `errored = 0` and
|
|
159
|
+
`skipped = 0` — a CI gate like `validate`. Progress and the run's scope go to
|
|
160
|
+
stderr; stdout stays clean for `--json`.
|
|
161
|
+
- **Multi-file runs** grade files one after another (`--concurrency`, default 4,
|
|
162
|
+
bounds cases *within* a file), print a per-file summary + grand totals, and isolate
|
|
163
|
+
a broken file instead of aborting the batch. `--json` / `--out` always carry the
|
|
164
|
+
same batch shape `{ files: [...], passed, totals }`, single file or not — `passed`
|
|
165
|
+
is the exit-code verdict, per batch and per file.
|
|
166
|
+
- **`--report <file.md>`** additionally writes a readable **Markdown** report — an
|
|
167
|
+
overview table over the files, then the question, the golden answer and the grader's
|
|
168
|
+
feedback for every mismatched, errored or unstable case (passing cases stay in the
|
|
169
|
+
JSON). It composes with `--json` / `--out` and leaves stdout untouched.
|
|
170
|
+
- **Token totals.** The reports show what a run cost —
|
|
171
|
+
`tokens: 15,420 in (12,300 cached) / 2,810 out` — summed over the grading calls that
|
|
172
|
+
**succeeded**, so it is a lower bound (a retried or failed call reports nothing), and
|
|
173
|
+
nothing at all is printed when the server reports no usage.
|
|
174
|
+
- **Failure handling**: a 5xx or network hiccup is retried (4 attempts, linear
|
|
175
|
+
backoff); any 4xx is terminal; an auth failure aborts the run with one message; and
|
|
176
|
+
three consecutive errored cases trip a circuit breaker so a down server fails fast.
|
|
177
|
+
After an abort, untried cases are reported as **`skipped`**, not errored. If EVERY
|
|
178
|
+
case errors at once, suspect the server, not the rubric: the target must actually
|
|
179
|
+
offer `/api/eval/grade` (point `--server` at one that does), and a 1-case smoke
|
|
180
|
+
run localises the problem for the cost of a single grading call.
|
|
181
|
+
- **Caveat**: a green run certifies **the file you ran it on**, not the app-hosted
|
|
182
|
+
copy a live code serves — upload it (`files upload`) afterwards. An override run
|
|
183
|
+
certifies the override pair, not the quiz's configured `llm`.
|
|
184
|
+
|
|
45
185
|
## Authentication
|
|
46
186
|
|
|
47
187
|
Commands that talk to the running app authenticate with Microsoft Entra ID:
|