@novedu/cli 0.4.0 → 0.5.1

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 +20 -13
  2. package/dist/main.js +79 -8
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -1,33 +1,40 @@
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
 
14
15
  ```bash
15
16
  # Validate a local file (relative fragment_files resolve from the same folder)
16
- npx @novedu/cli validate ./tutors/simple-tutor.yaml
17
+ npx @novedu/cli validate ./activities/tutors/simple-tutor.yaml
17
18
 
18
19
  # Validate a published tutor by URL
19
- npx @novedu/cli validate https://raw.githubusercontent.com/Teaching-HTL-Leonding/novedu-chat-mvp/refs/heads/main/tutors/simple-tutor.yaml
20
+ npx @novedu/cli validate https://raw.githubusercontent.com/Teaching-HTL-Leonding/novedu-chat-mvp/refs/heads/main/activities/tutors/simple-tutor.yaml
20
21
 
21
22
  # Validate a fragment library on its own
22
- npx @novedu/cli validate ./tutors/simple-fragments.yaml --kind fragment
23
+ npx @novedu/cli validate ./activities/tutors/simple-fragments.yaml --kind fragment
24
+
25
+ # Validate a quiz, a writing activity, or a coding activity
26
+ npx @novedu/cli validate ./activities/quizzes/sample-quiz.yaml --kind quiz
27
+ npx @novedu/cli validate ./activities/writings/human-animal-short-story.yaml --kind writing
28
+ npx @novedu/cli validate ./activities/coding/beginner-typescript.yaml --kind coding
23
29
 
24
30
  # Machine-readable output (the raw validation result)
25
- npx @novedu/cli validate ./tutors/simple-tutor.yaml --json
31
+ npx @novedu/cli validate ./activities/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
@@ -35,7 +42,7 @@ as a pre-commit / CI gate.
35
42
  The CLI lives in the app repo as an npm workspace.
36
43
 
37
44
  ```bash
38
- npm run cli -- validate ./tutors/simple-tutor.yaml # run from source via tsx
45
+ npm run cli -- validate ./activities/tutors/simple-tutor.yaml # run from source via tsx
39
46
  npm run cli:build # bundle to cli/dist via tsdown
40
47
  npm run test:cli # build + integration tests (local & live URLs)
41
48
  ```
package/dist/main.js CHANGED
@@ -446,7 +446,7 @@ function resolveRelativeUrl(ref, baseUrl) {
446
446
  * Resolve a fragment-file reference to an absolute URL. An absolute http(s) ref is used
447
447
  * as-is; anything else is treated as relative to the tutor URL — standard URL resolution
448
448
  * drops the tutor's filename and appends the relative path (so `general-fragments.yaml`
449
- * next to `.../tutors/linked-list-tutor.yaml` becomes `.../tutors/general-fragments.yaml`,
449
+ * next to `.../activities/tutors/linked-list-tutor.yaml` becomes `.../activities/tutors/general-fragments.yaml`,
450
450
  * and `./` / `../` segments work too). Throws if a relative ref is unparseable; the schema
451
451
  * already guarantees the only inputs here are http(s) URLs or relative paths.
452
452
  */
@@ -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,17 +1044,18 @@ 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
- $ novedu-cli validate ./tutors/my-tutor.yaml
1050
+ $ novedu-cli validate ./activities/tutors/my-tutor.yaml
982
1051
 
983
1052
  # Validate a fragment library on its own
984
- $ novedu-cli validate ./tutors/my-fragments.yaml --kind fragment
1053
+ $ novedu-cli validate ./activities/tutors/my-fragments.yaml --kind fragment
985
1054
 
986
- # Validate a quiz or a writing activity
987
- $ novedu-cli validate ./quizzes/my-quiz.yaml --kind quiz
988
- $ novedu-cli validate ./writings/my-writing.yaml --kind writing
1055
+ # Validate a quiz, a writing activity, or a coding activity
1056
+ $ novedu-cli validate ./activities/quizzes/my-quiz.yaml --kind quiz
1057
+ $ novedu-cli validate ./activities/writings/my-writing.yaml --kind writing
1058
+ $ novedu-cli validate ./activities/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.1",
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
  }