@novedu/cli 0.4.0 → 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.
Files changed (3) hide show
  1. package/README.md +15 -8
  2. package/dist/main.js +74 -3
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -1,13 +1,14 @@
1
1
  # @novedu/cli
2
2
 
3
3
  Command-line companion for the Novedu chat app (installed command: `novedu-cli`).
4
- Today it validates **tutor YAML** definitions and **fragment libraries**; more
5
- commands will follow. Validating a tutor also fully validates every fragment
6
- library it references; pass `--kind fragment` to validate a fragment library on
7
- its own.
4
+ Today it validates every activity YAML the app accepts — **tutors**, **fragment
5
+ libraries**, **quizzes**, **writing activities**, and **coding activities**; more
6
+ commands will follow. Validating a tutor also fully validates every fragment library
7
+ it references; pass `--kind` to validate any other kind on its own.
8
8
 
9
- It reuses the app's exact validation pipeline (`lib/tutors`), so a tutor that
10
- passes here is the same tutor the app would accept — no separate, drifting rules.
9
+ It reuses the app's exact validation pipeline (`lib/tutors`, `lib/quiz-validate`,
10
+ `lib/writing-validate`, `lib/coding-validate`), so an activity that passes here is
11
+ the same one the app would accept — no separate, drifting rules.
11
12
 
12
13
  ## Usage
13
14
 
@@ -21,13 +22,19 @@ npx @novedu/cli validate https://raw.githubusercontent.com/Teaching-HTL-Leonding
21
22
  # Validate a fragment library on its own
22
23
  npx @novedu/cli validate ./tutors/simple-fragments.yaml --kind fragment
23
24
 
25
+ # Validate a quiz, a writing activity, or a coding activity
26
+ npx @novedu/cli validate ./quizzes/sample-quiz.yaml --kind quiz
27
+ npx @novedu/cli validate ./writings/human-animal-short-story.yaml --kind writing
28
+ npx @novedu/cli validate ./coding/beginner-typescript.yaml --kind coding
29
+
24
30
  # Machine-readable output (the raw validation result)
25
31
  npx @novedu/cli validate ./tutors/simple-tutor.yaml --json
26
32
  ```
27
33
 
28
- `--kind` defaults to `tutor`; it is caller-declared, not auto-detected.
34
+ `--kind` accepts `tutor` (default), `fragment`, `quiz`, `writing`, or `coding`; it
35
+ is caller-declared, not auto-detected.
29
36
 
30
- Exit code is `0` when the tutor is valid and `1` when it has errors, so it works
37
+ Exit code is `0` when the activity is valid and `1` when it has errors, so it works
31
38
  as a pre-commit / CI gate.
32
39
 
33
40
  ## Development
package/dist/main.js CHANGED
@@ -628,6 +628,52 @@ async function loadAndCheckFragmentFile(url, fetchImpl, opts = {}) {
628
628
  return checkFragmentFileValue(yaml.value, url);
629
629
  }
630
630
  //#endregion
631
+ //#region ../lib/coding-schema.ts
632
+ const CodingYamlSchema = z.strictObject({
633
+ id: z.string().min(1),
634
+ name: z.string().optional(),
635
+ title: z.string().optional(),
636
+ llm: z.strictObject({ model: z.string().min(1) }),
637
+ instructions: z.string().min(1)
638
+ });
639
+ //#endregion
640
+ //#region ../lib/coding-validate.ts
641
+ /**
642
+ * Validate an already-parsed coding value against its schema, then extract metadata.
643
+ * Pure (the parsed value is passed in); `loadAndCheckCoding` wraps it with fetch +
644
+ * YAML parse.
645
+ */
646
+ function checkCodingValue(parsed, url) {
647
+ const valid = validate(parsed, CodingYamlSchema, "CODING_SCHEMA_ERROR", url);
648
+ if (!valid.ok) return {
649
+ ok: false,
650
+ errors: [valid.error],
651
+ warnings: []
652
+ };
653
+ const coding = valid.data;
654
+ return {
655
+ ok: true,
656
+ codingId: coding.id,
657
+ model: coding.llm.model,
658
+ title: coding.title ?? null,
659
+ warnings: []
660
+ };
661
+ }
662
+ /**
663
+ * Validate a coding FILE: scheme-gate + fetch + parse (shared `loadYaml`), then the
664
+ * pure `checkCodingValue`. The web app passes the default http(s)-only schemes; the
665
+ * CLI adds `file:` so a local coding YAML on disk validates too.
666
+ */
667
+ async function loadAndCheckCoding(url, fetchImpl, opts = {}) {
668
+ const yaml = await loadYaml(url, fetchImpl, opts);
669
+ if (!yaml.ok) return {
670
+ ok: false,
671
+ errors: [yaml.error],
672
+ warnings: []
673
+ };
674
+ return checkCodingValue(yaml.value, url);
675
+ }
676
+ //#endregion
631
677
  //#region ../lib/quiz-schema.ts
632
678
  /** An optional content image attached to a question (carries no secret). */
633
679
  const ImageRefSchema = z.strictObject({
@@ -920,6 +966,24 @@ function formatWritingResult(result, source) {
920
966
  }
921
967
  return lines.join("\n");
922
968
  }
969
+ /**
970
+ * Renderer for a coding-activity check (`--kind coding`). Coding is ALWAYS anonymous
971
+ * (the API path carries no per-student identity), so — unlike quiz/writing — that is
972
+ * shown as a fixed note, not a per-file value.
973
+ */
974
+ function formatCodingResult(result, source) {
975
+ if (!result.ok) return renderFailureAndWarnings(result, "coding activity", source);
976
+ const lines = [green(`✔ Valid coding activity`) + dim(` — ${source}`)];
977
+ lines.push(` id: ${result.codingId}`);
978
+ lines.push(` model: ${result.model}`);
979
+ lines.push(` anonymous: true ${dim("(always — the API path carries no identity)")}`);
980
+ if (result.warnings.length) {
981
+ lines.push("");
982
+ lines.push(yellow(`${result.warnings.length} warning(s):`));
983
+ lines.push(...renderWarnings(result.warnings));
984
+ }
985
+ return lines.join("\n");
986
+ }
923
987
  //#endregion
924
988
  //#region src/commands/validate.ts
925
989
  /** Every kind the `--kind` flag accepts (used for the option help + guard). */
@@ -927,7 +991,8 @@ const VALIDATE_KINDS = [
927
991
  "tutor",
928
992
  "fragment",
929
993
  "quiz",
930
- "writing"
994
+ "writing",
995
+ "coding"
931
996
  ];
932
997
  /**
933
998
  * Turn the CLI argument into a URL the tutor core understands: an http(s) URL is
@@ -965,6 +1030,10 @@ function runValidate(pathOrUrl, kind) {
965
1030
  kind,
966
1031
  result
967
1032
  }));
1033
+ case "coding": return loadAndCheckCoding(url, cliFetcher, { allowedSchemes }).then((result) => ({
1034
+ kind,
1035
+ result
1036
+ }));
968
1037
  default: return loadAndBuildTutorPrompt(url, cliFetcher, {
969
1038
  allowedSchemes,
970
1039
  validateLibraries: true
@@ -975,7 +1044,7 @@ function runValidate(pathOrUrl, kind) {
975
1044
  }
976
1045
  }
977
1046
  function registerValidate(program) {
978
- program.command("validate").description("Validate a tutor (default), fragment library, quiz or writing YAML by local path or public http(s) URL").argument("<pathOrUrl>", "path to a tutor, fragment, quiz or writing YAML file, or a public http(s) URL").option("--kind <kind>", `what the file is: ${VALIDATE_KINDS.map((k) => `'${k}'`).join(", ")} ('tutor' is the default)`, "tutor").option("--json", "print the raw validation result as JSON").addHelpText("after", `
1047
+ program.command("validate").description("Validate a tutor (default), fragment library, quiz, writing or coding YAML by local path or public http(s) URL").argument("<pathOrUrl>", "path to a tutor, fragment, quiz, writing or coding YAML file, or a public http(s) URL").option("--kind <kind>", `what the file is: ${VALIDATE_KINDS.map((k) => `'${k}'`).join(", ")} ('tutor' is the default)`, "tutor").option("--json", "print the raw validation result as JSON").addHelpText("after", `
979
1048
  Examples:
980
1049
  # Validate a tutor (also strict-renders every fragment in every referenced library)
981
1050
  $ novedu-cli validate ./tutors/my-tutor.yaml
@@ -983,9 +1052,10 @@ Examples:
983
1052
  # Validate a fragment library on its own
984
1053
  $ novedu-cli validate ./tutors/my-fragments.yaml --kind fragment
985
1054
 
986
- # Validate a quiz or a writing activity
1055
+ # Validate a quiz, a writing activity, or a coding activity
987
1056
  $ novedu-cli validate ./quizzes/my-quiz.yaml --kind quiz
988
1057
  $ novedu-cli validate ./writings/my-writing.yaml --kind writing
1058
+ $ novedu-cli validate ./coding/my-coding.yaml --kind coding
989
1059
 
990
1060
  # Machine-readable output for CI
991
1061
  $ novedu-cli validate https://example.com/tutor.yaml --json`).action(async (pathOrUrl, options) => {
@@ -1006,6 +1076,7 @@ function formatOutcome(outcome, source) {
1006
1076
  case "fragment": return formatFragmentResult(outcome.result, source);
1007
1077
  case "quiz": return formatQuizResult(outcome.result, source);
1008
1078
  case "writing": return formatWritingResult(outcome.result, source);
1079
+ case "coding": return formatCodingResult(outcome.result, source);
1009
1080
  default: return formatResult(outcome.result, source);
1010
1081
  }
1011
1082
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@novedu/cli",
3
- "version": "0.4.0",
4
- "description": "Command-line companion for the Novedu chat app. Validates tutor, fragment, quiz and writing YAML definitions (more commands to follow).",
3
+ "version": "0.5.0",
4
+ "description": "Command-line companion for the Novedu chat app. Validates tutor, fragment, quiz, writing and coding YAML definitions (more commands to follow).",
5
5
  "type": "module",
6
6
  "repository": {
7
7
  "type": "git",
@@ -31,6 +31,6 @@
31
31
  "zod": "^4.4.3"
32
32
  },
33
33
  "devDependencies": {
34
- "tsdown": "^0.22.2"
34
+ "tsdown": "^0.22.3"
35
35
  }
36
36
  }