@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.
- package/README.md +20 -13
- package/dist/main.js +79 -8
- 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
|
|
5
|
-
|
|
6
|
-
|
|
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`
|
|
10
|
-
|
|
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`
|
|
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
|
|
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
|
|
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
|
|
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
|
-
"description": "Command-line companion for the Novedu chat app. Validates tutor, fragment, quiz and
|
|
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.
|
|
34
|
+
"tsdown": "^0.22.3"
|
|
35
35
|
}
|
|
36
36
|
}
|