@novedu/cli 0.24.1 → 0.25.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.
Files changed (3) hide show
  1. package/README.md +2 -1
  2. package/dist/main.js +26 -24
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -331,7 +331,8 @@ images list [--search <q>] [--all]
331
331
  shareable `url`. `--start`/`--end` must be ISO 8601 **with an explicit
332
332
  offset or `Z`** (e.g. `2026-07-07T08:00:00Z`); the
333
333
  `--llm-provider`/`--llm-model` override pair is both-or-nothing, and
334
- `--llm-reasoning <level>` (`minimal`, `low`, `medium` or `high`) rides on top of
334
+ `--llm-reasoning <level>` (`none`, `minimal`, `low`, `medium`, `high` or
335
+ `xhigh`) rides on top of
335
336
  the pair — it is rejected without it. The override replaces the activity's whole
336
337
  `llm:` block, so leaving the level out also drops the file's.
337
338
  - `codes sync <registry-file>` mints codes for a whole **course** at once — see
package/dist/main.js CHANGED
@@ -2,15 +2,35 @@
2
2
  import { Command } from "commander";
3
3
  import { readFile, writeFile } from "node:fs/promises";
4
4
  import { basename, dirname, join, resolve } from "node:path";
5
+ import { z } from "zod";
5
6
  import { spawn } from "node:child_process";
6
7
  import { globSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
7
8
  import { createServer } from "node:http";
8
9
  import { homedir } from "node:os";
9
10
  import { CryptoProvider, PublicClientApplication } from "@azure/msal-node";
10
11
  import { parse, stringify } from "yaml";
11
- import { z } from "zod";
12
12
  import Handlebars from "handlebars";
13
13
  import { fileURLToPath, pathToFileURL } from "node:url";
14
+ //#region ../lib/llm/provider.ts
15
+ const LLM_PROVIDERS = ["SCCH", "Azure Foundry"];
16
+ const DEFAULT_PROVIDER = "SCCH";
17
+ const providerSchema = z.enum(LLM_PROVIDERS).default(DEFAULT_PROVIDER).meta({ description: "The LLM provider serving the model. For Azure Foundry, model is the deployment name." });
18
+ const REASONING_LEVELS = [
19
+ "none",
20
+ "minimal",
21
+ "low",
22
+ "medium",
23
+ "high",
24
+ "xhigh"
25
+ ];
26
+ const reasoningLevelSchema = z.enum(REASONING_LEVELS).optional().meta({ description: "Optional reasoning effort for reasoning models. Not every model accepts every level. Omit to let the model decide (the parameter is then not sent); \"none\" instead turns a reasoning model off." });
27
+ function parseLenientProvider(value) {
28
+ return value === "SCCH" || value === "Azure Foundry" ? value : void 0;
29
+ }
30
+ function parseLenientReasoningLevel(value) {
31
+ return typeof value === "string" && REASONING_LEVELS.includes(value) ? value : void 0;
32
+ }
33
+ //#endregion
14
34
  //#region src/auth.ts
15
35
  const DEFAULT_TENANT_ID = "91fc072c-edef-4f97-bdc5-cfb67718ae3a";
16
36
  const DEFAULT_CLIENT_ID = "4d44fc4b-0434-4981-9765-62e2074ceecb";
@@ -309,24 +329,6 @@ async function runApiRequest(options) {
309
329
  if (result.ok) printJson(result.payload ?? null);
310
330
  }
311
331
  //#endregion
312
- //#region ../lib/llm/provider.ts
313
- const LLM_PROVIDERS = ["SCCH", "Azure Foundry"];
314
- const DEFAULT_PROVIDER = "SCCH";
315
- const providerSchema = z.enum(LLM_PROVIDERS).default(DEFAULT_PROVIDER).meta({ description: "The LLM provider serving the model. For Azure Foundry, model is the deployment name." });
316
- const REASONING_LEVELS = [
317
- "minimal",
318
- "low",
319
- "medium",
320
- "high"
321
- ];
322
- const reasoningLevelSchema = z.enum(REASONING_LEVELS).optional().meta({ description: "Optional reasoning effort for reasoning models. Omit to let the model decide (the parameter is then not sent)." });
323
- function parseLenientProvider(value) {
324
- return value === "SCCH" || value === "Azure Foundry" ? value : void 0;
325
- }
326
- function parseLenientReasoningLevel(value) {
327
- return typeof value === "string" && REASONING_LEVELS.includes(value) ? value : void 0;
328
- }
329
- //#endregion
330
332
  //#region ../lib/registry-schema.ts
331
333
  /** The fixed group names and the code module each one mints for. */
332
334
  const GROUP_MODULES = {
@@ -896,7 +898,7 @@ async function readLock(lockPath) {
896
898
  }
897
899
  function registerCodes(program) {
898
900
  const codes = program.command("codes").description("Manage activity codes on the Novedu server");
899
- codes.command("create").description("Create a code for an activity YAML (validated server-side before storing)").requiredOption("--module <module>", "activity module: tutor, quiz, writing or coding").requiredOption("--file <url>", "public http(s) URL of the activity YAML").option("--start <iso>", "window start, ISO 8601 with explicit offset (e.g. 2026-07-07T08:00:00Z)").option("--end <iso>", "window end, ISO 8601 with explicit offset").option("--note <text>", "note shown in the codes list").option("--llm-provider <provider>", "LLM override provider (\"SCCH\" or \"Azure Foundry\"; needs --llm-model)").option("--llm-model <model>", "LLM override model id (needs --llm-provider)").option("--llm-reasoning <level>", "LLM override reasoning effort (\"minimal\", \"low\", \"medium\" or \"high\"; needs the provider/model pair)").option(...SERVER_OPTION$3).action(async (options) => {
901
+ codes.command("create").description("Create a code for an activity YAML (validated server-side before storing)").requiredOption("--module <module>", "activity module: tutor, quiz, writing or coding").requiredOption("--file <url>", "public http(s) URL of the activity YAML").option("--start <iso>", "window start, ISO 8601 with explicit offset (e.g. 2026-07-07T08:00:00Z)").option("--end <iso>", "window end, ISO 8601 with explicit offset").option("--note <text>", "note shown in the codes list").option("--llm-provider <provider>", "LLM override provider (\"SCCH\" or \"Azure Foundry\"; needs --llm-model)").option("--llm-model <model>", "LLM override model id (needs --llm-provider)").option("--llm-reasoning <level>", `LLM override reasoning effort (${REASONING_LEVELS.join(", ")}; needs the provider/model pair)`).option(...SERVER_OPTION$3).action(async (options) => {
900
902
  const llmGiven = options.llmProvider !== void 0 || options.llmModel !== void 0 || options.llmReasoning !== void 0;
901
903
  await runApiRequest({
902
904
  server: options.server,
@@ -1168,7 +1170,7 @@ function parseCoding(content) {
1168
1170
  const reasoning = llm?.reasoning === void 0 ? void 0 : parseLenientReasoningLevel(llm.reasoning);
1169
1171
  if (llm?.reasoning !== void 0 && !reasoning) return {
1170
1172
  ok: false,
1171
- message: "This coding activity uses an unsupported llm.reasoning (use \"minimal\", \"low\", \"medium\" or \"high\")."
1173
+ message: `This coding activity uses an unsupported llm.reasoning (one of ${REASONING_LEVELS.join(", ")}).`
1172
1174
  };
1173
1175
  const instructions = asString$2(root.instructions);
1174
1176
  if (!instructions) return {
@@ -2477,7 +2479,7 @@ function parseQuiz(content) {
2477
2479
  const reasoning = llm?.reasoning === void 0 ? void 0 : parseLenientReasoningLevel(llm.reasoning);
2478
2480
  if (llm?.reasoning !== void 0 && !reasoning) return {
2479
2481
  ok: false,
2480
- message: "This quiz uses an unsupported llm.reasoning (use \"minimal\", \"low\", \"medium\" or \"high\")."
2482
+ message: `This quiz uses an unsupported llm.reasoning (one of ${REASONING_LEVELS.join(", ")}).`
2481
2483
  };
2482
2484
  const quizFiles = Array.isArray(root.quiz_files) ? root.quiz_files : [];
2483
2485
  const rawQuestions = Array.isArray(root.questions) ? root.questions : [];
@@ -2858,7 +2860,7 @@ function parseWriting(content) {
2858
2860
  const reasoning = llm?.reasoning === void 0 ? void 0 : parseLenientReasoningLevel(llm.reasoning);
2859
2861
  if (llm?.reasoning !== void 0 && !reasoning) return {
2860
2862
  ok: false,
2861
- message: "This writing activity uses an unsupported llm.reasoning (use \"minimal\", \"low\", \"medium\" or \"high\")."
2863
+ message: `This writing activity uses an unsupported llm.reasoning (one of ${REASONING_LEVELS.join(", ")}).`
2862
2864
  };
2863
2865
  const instructions = asString(root.instructions);
2864
2866
  if (!instructions) return {
@@ -5810,7 +5812,7 @@ async function runEvalCommand(pathsOrUrls, options, seams = {}) {
5810
5812
  process.exitCode = batchPassed(batch) ? 0 : 1;
5811
5813
  }
5812
5814
  function registerEval(program) {
5813
- program.command("eval").description("Run an eval file (quiz golden answers, or tutor conversations) against the real activity path and report the result").argument("<evalPathOrUrl...>", "one or more eval YAML files (paths, http(s)/file URLs, or a quoted glob pattern)").option("--server <url>", "Novedu server base URL (defaults to the NOVEDU_SERVER env var, then production)").option("--concurrency <n>", "cases in flight per file", String(CONCURRENCY_DEFAULT)).option("--repeats <n>", "run every case N times (quiz: take the majority verdict)", "1").option("--llm-provider <provider>", "run with this provider instead of the activity's (\"SCCH\" or \"Azure Foundry\"; needs --llm-model)").option("--llm-model <model>", "run with this model instead of the activity's (needs --llm-provider)").option("--llm-reasoning <level>", "run at this reasoning effort (\"minimal\", \"low\", \"medium\" or \"high\"); on its own it keeps the activity's model").option("--no-judge-feedback", "skip the LLM audit of what the model wrote (halves the LLM calls)").option("--judge-llm-provider <provider>", "judge with this provider (\"SCCH\" or \"Azure Foundry\"; needs --judge-llm-model)").option("--judge-llm-model <model>", "judge with this model instead of the one under test (needs --judge-llm-provider)").option("--judge-llm-reasoning <level>", "judge at this reasoning effort (\"minimal\", \"low\", \"medium\" or \"high\"); on its own it keeps the judge's model").option("--json", "print the machine-readable batch report on stdout").option("--out <file>", "additionally write the machine-readable batch report to a file").option("--report <file>", "additionally write a readable Markdown report to a file").addHelpText("after", `
5815
+ program.command("eval").description("Run an eval file (quiz golden answers, or tutor conversations) against the real activity path and report the result").argument("<evalPathOrUrl...>", "one or more eval YAML files (paths, http(s)/file URLs, or a quoted glob pattern)").option("--server <url>", "Novedu server base URL (defaults to the NOVEDU_SERVER env var, then production)").option("--concurrency <n>", "cases in flight per file", String(CONCURRENCY_DEFAULT)).option("--repeats <n>", "run every case N times (quiz: take the majority verdict)", "1").option("--llm-provider <provider>", "run with this provider instead of the activity's (\"SCCH\" or \"Azure Foundry\"; needs --llm-model)").option("--llm-model <model>", "run with this model instead of the activity's (needs --llm-provider)").option("--llm-reasoning <level>", `run at this reasoning effort (${REASONING_LEVELS.join(", ")}); on its own it keeps the activity's model`).option("--no-judge-feedback", "skip the LLM audit of what the model wrote (halves the LLM calls)").option("--judge-llm-provider <provider>", "judge with this provider (\"SCCH\" or \"Azure Foundry\"; needs --judge-llm-model)").option("--judge-llm-model <model>", "judge with this model instead of the one under test (needs --judge-llm-provider)").option("--judge-llm-reasoning <level>", `judge at this reasoning effort (${REASONING_LEVELS.join(", ")}); on its own it keeps the judge's model`).option("--json", "print the machine-readable batch report on stdout").option("--out <file>", "additionally write the machine-readable batch report to a file").option("--report <file>", "additionally write a readable Markdown report to a file").addHelpText("after", `
5814
5816
  Examples:
5815
5817
  # Evaluate one quiz's golden answers
5816
5818
  $ novedu-cli eval ./0010-welcome-quiz.eval.yaml
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@novedu/cli",
3
- "version": "0.24.1",
3
+ "version": "0.25.0",
4
4
  "description": "Command-line companion for the Novedu chat app. Validates tutor, fragment, quiz, writing, coding and eval YAML definitions, dumps the exact LLM prompts an activity produces, evaluates a quiz's grading rubric against golden answers and replays scripted conversations against a tutor; signs in with Entra ID and manages codes, app-hosted files and images over the app's API.",
5
5
  "type": "module",
6
6
  "repository": {