@crowandraven/evalkeeper 0.0.0-stage → 0.2.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 (151) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +6 -2
  3. package/bin/evalkeeper.mjs +9 -0
  4. package/dist/attention.d.ts +49 -0
  5. package/dist/attention.js +49 -0
  6. package/dist/attention.js.map +1 -0
  7. package/dist/board.d.ts +57 -0
  8. package/dist/board.js +2 -0
  9. package/dist/board.js.map +1 -0
  10. package/dist/cli/addPages.d.ts +79 -0
  11. package/dist/cli/addPages.js +181 -0
  12. package/dist/cli/addPages.js.map +1 -0
  13. package/dist/cli/main.d.ts +18 -0
  14. package/dist/cli/main.js +115 -0
  15. package/dist/cli/main.js.map +1 -0
  16. package/dist/common.d.ts +72 -0
  17. package/dist/common.js +276 -0
  18. package/dist/common.js.map +1 -0
  19. package/dist/compare.d.ts +85 -0
  20. package/dist/compare.js +185 -0
  21. package/dist/compare.js.map +1 -0
  22. package/dist/decisions.d.ts +84 -0
  23. package/dist/decisions.js +146 -0
  24. package/dist/decisions.js.map +1 -0
  25. package/dist/feedback.d.ts +114 -0
  26. package/dist/feedback.js +116 -0
  27. package/dist/feedback.js.map +1 -0
  28. package/dist/format.d.ts +6 -0
  29. package/dist/format.js +22 -0
  30. package/dist/format.js.map +1 -0
  31. package/dist/glossary.d.ts +46 -0
  32. package/dist/glossary.js +45 -0
  33. package/dist/glossary.js.map +1 -0
  34. package/dist/health.d.ts +40 -0
  35. package/dist/health.js +101 -0
  36. package/dist/health.js.map +1 -0
  37. package/dist/index.d.ts +31 -0
  38. package/dist/index.js +34 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/labels.d.ts +8 -0
  41. package/dist/labels.js +15 -0
  42. package/dist/labels.js.map +1 -0
  43. package/dist/legs.d.ts +23 -0
  44. package/dist/legs.js +22 -0
  45. package/dist/legs.js.map +1 -0
  46. package/dist/memoryStore.d.ts +21 -0
  47. package/dist/memoryStore.js +228 -0
  48. package/dist/memoryStore.js.map +1 -0
  49. package/dist/models.d.ts +76 -0
  50. package/dist/models.js +56 -0
  51. package/dist/models.js.map +1 -0
  52. package/dist/prune.d.ts +48 -0
  53. package/dist/prune.js +77 -0
  54. package/dist/prune.js.map +1 -0
  55. package/dist/quickSet.d.ts +26 -0
  56. package/dist/quickSet.js +44 -0
  57. package/dist/quickSet.js.map +1 -0
  58. package/dist/runner.d.ts +202 -0
  59. package/dist/runner.js +734 -0
  60. package/dist/runner.js.map +1 -0
  61. package/dist/settings.d.ts +196 -0
  62. package/dist/settings.js +272 -0
  63. package/dist/settings.js.map +1 -0
  64. package/dist/stableJson.d.ts +11 -0
  65. package/dist/stableJson.js +15 -0
  66. package/dist/stableJson.js.map +1 -0
  67. package/dist/stats.d.ts +98 -0
  68. package/dist/stats.js +119 -0
  69. package/dist/stats.js.map +1 -0
  70. package/dist/store.d.ts +151 -0
  71. package/dist/store.js +3 -0
  72. package/dist/store.js.map +1 -0
  73. package/dist/suggest.d.ts +75 -0
  74. package/dist/suggest.js +152 -0
  75. package/dist/suggest.js.map +1 -0
  76. package/dist/summary.d.ts +51 -0
  77. package/dist/summary.js +61 -0
  78. package/dist/summary.js.map +1 -0
  79. package/dist/testing.d.ts +26 -0
  80. package/dist/testing.js +362 -0
  81. package/dist/testing.js.map +1 -0
  82. package/dist/timeout.d.ts +2 -0
  83. package/dist/timeout.js +15 -0
  84. package/dist/timeout.js.map +1 -0
  85. package/dist/types.d.ts +225 -0
  86. package/dist/types.js +46 -0
  87. package/dist/types.js.map +1 -0
  88. package/dist/upkeepRules.d.ts +28 -0
  89. package/dist/upkeepRules.js +40 -0
  90. package/dist/upkeepRules.js.map +1 -0
  91. package/dist/util.d.ts +8 -0
  92. package/dist/util.js +18 -0
  93. package/dist/util.js.map +1 -0
  94. package/dist/weekly.d.ts +80 -0
  95. package/dist/weekly.js +113 -0
  96. package/dist/weekly.js.map +1 -0
  97. package/package.json +81 -4
  98. package/src/attention.ts +89 -0
  99. package/src/board.ts +58 -0
  100. package/src/cli/addPages.ts +256 -0
  101. package/src/cli/main.ts +125 -0
  102. package/src/common.ts +338 -0
  103. package/src/compare.ts +261 -0
  104. package/src/decisions.ts +230 -0
  105. package/src/feedback.ts +225 -0
  106. package/src/format.ts +19 -0
  107. package/src/glossary.ts +75 -0
  108. package/src/health.ts +149 -0
  109. package/src/index.ts +33 -0
  110. package/src/labels.ts +18 -0
  111. package/src/legs.ts +38 -0
  112. package/src/memoryStore.ts +268 -0
  113. package/src/models.ts +127 -0
  114. package/src/prune.ts +95 -0
  115. package/src/quickSet.ts +55 -0
  116. package/src/runner.ts +1010 -0
  117. package/src/settings.ts +441 -0
  118. package/src/stableJson.ts +24 -0
  119. package/src/stats.ts +215 -0
  120. package/src/store.ts +172 -0
  121. package/src/suggest.ts +247 -0
  122. package/src/summary.ts +109 -0
  123. package/src/testing.ts +415 -0
  124. package/src/timeout.ts +14 -0
  125. package/src/types.ts +258 -0
  126. package/src/upkeepRules.ts +46 -0
  127. package/src/util.ts +17 -0
  128. package/src/weekly.ts +169 -0
  129. package/starter/index.ts +86 -0
  130. package/templates/app/api/cron/evals-weekly/route.ts +24 -0
  131. package/templates/app/evals/[suite]/page.tsx +449 -0
  132. package/templates/app/evals/[suite]/runs/[runId]/page.tsx +95 -0
  133. package/templates/app/evals/feedback/page.tsx +219 -0
  134. package/templates/app/evals/page.tsx +313 -0
  135. package/templates/components/evalkeeper/AddCase.tsx +62 -0
  136. package/templates/components/evalkeeper/AnswerKey.tsx +360 -0
  137. package/templates/components/evalkeeper/CaseMenu.tsx +122 -0
  138. package/templates/components/evalkeeper/Fields.tsx +110 -0
  139. package/templates/components/evalkeeper/RefreshWhile.tsx +25 -0
  140. package/templates/components/evalkeeper/SettingsHistory.tsx +191 -0
  141. package/templates/components/evalkeeper/Submit.tsx +58 -0
  142. package/templates/components/evalkeeper/Thumbs.tsx +117 -0
  143. package/templates/components/evalkeeper/Tip.tsx +98 -0
  144. package/templates/components/evalkeeper/WeeklyCheck.tsx +124 -0
  145. package/templates/components/evalkeeper/evalkeeper.css +71 -0
  146. package/templates/components/evalkeeper/ui.tsx +103 -0
  147. package/templates/lib/evalkeeper/actions.ts +263 -0
  148. package/templates/lib/evalkeeper/cronAuth.ts +18 -0
  149. package/templates/lib/evalkeeper/paths.ts +11 -0
  150. package/templates/lib/evalkeeper/time.ts +4 -0
  151. package/templates/lib/evalkeeper/weekly.ts +51 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sean Hudson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,7 @@
1
- # Temporary Holding Version
1
+ # @crowandraven/evalkeeper
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The core of [Evalkeeper](https://github.com/seanhudson/evalkeeper), an eval harness for the AI features in TypeScript apps: suite and check types, stats,
4
+ paired comparisons, model suggestions, suite health, grader helpers, and
5
+ the runner. Pure TypeScript; the runner (`@crowandraven/evalkeeper/runner`) is
6
+ server-only, and `@crowandraven/evalkeeper/memory` is an in-memory store for tests and
7
+ demos.
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ // The `evalkeeper` command: `npx @crowandraven/evalkeeper add pages`. See --help.
3
+ import { run } from "../dist/cli/main.js";
4
+
5
+ process.exitCode = run(process.argv.slice(2), {
6
+ cwd: process.cwd(),
7
+ log: (line) => console.log(line),
8
+ error: (line) => console.error(line),
9
+ });
@@ -0,0 +1,49 @@
1
+ import type { BoardRow } from "./board.js";
2
+ /**
3
+ * What needs attention across suites, for the evals hub: the suite with
4
+ * the most room to improve, the slowest and costliest live models, and the
5
+ * answers waiting for review. From the same boards the suite pages show;
6
+ * only current, complete tests count. Pure.
7
+ */
8
+ /** One suite's board, as attention reads it. */
9
+ export type SuiteBoard = {
10
+ key: string;
11
+ label: string;
12
+ rows: BoardRow[];
13
+ /** Cases waiting for a person: drafts, and provisional answers. */
14
+ toReview: number;
15
+ };
16
+ /**
17
+ * What needs a look across suites: the most room to improve, the slowest and costliest
18
+ * live models, and the answers waiting for review.
19
+ */
20
+ export type Attention = {
21
+ improve: {
22
+ key: string;
23
+ label: string;
24
+ best: number;
25
+ model: string;
26
+ } | null;
27
+ slowest: {
28
+ key: string;
29
+ label: string;
30
+ model: string;
31
+ ms: number;
32
+ } | null;
33
+ costliest: {
34
+ key: string;
35
+ label: string;
36
+ model: string;
37
+ usd: number;
38
+ } | null;
39
+ review: {
40
+ total: number;
41
+ key: string | null;
42
+ label: string | null;
43
+ most: number;
44
+ };
45
+ };
46
+ /**
47
+ * Finds what needs a look across suites, from their boards. Only current, complete tests count.
48
+ */
49
+ export declare function attention(boards: SuiteBoard[]): Attention;
@@ -0,0 +1,49 @@
1
+ const current = (r) => r.test && !r.test.stale && !r.test.failedAnswers ? r.test : null;
2
+ /**
3
+ * Finds what needs a look across suites, from their boards. Only current, complete tests count.
4
+ */
5
+ export function attention(boards) {
6
+ let improve = null;
7
+ let slowest = null;
8
+ let costliest = null;
9
+ for (const b of boards) {
10
+ // The best model's score: a baseline is code, not a model to switch to.
11
+ const tested = b.rows.filter((r) => !r.baseline && current(r));
12
+ if (tested.length) {
13
+ const rateOf = (r) => {
14
+ const t = current(r);
15
+ return t ? t.right / t.total : -1;
16
+ };
17
+ const best = tested.reduce((a, r) => (rateOf(r) > rateOf(a) ? r : a));
18
+ const rate = rateOf(best);
19
+ if (!improve || rate < improve.best)
20
+ improve = { key: b.key, label: b.label, best: rate, model: best.label };
21
+ }
22
+ const live = b.rows.find((r) => r.live && current(r));
23
+ const t = live && current(live);
24
+ if (live && t?.avgMs && (!slowest || t.avgMs > slowest.ms))
25
+ slowest = { key: b.key, label: b.label, model: live.label, ms: t.avgMs };
26
+ if (live &&
27
+ t?.costPerAnswer &&
28
+ (!costliest || t.costPerAnswer > costliest.usd))
29
+ costliest = {
30
+ key: b.key,
31
+ label: b.label,
32
+ model: live.label,
33
+ usd: t.costPerAnswer,
34
+ };
35
+ }
36
+ const most = boards.reduce((a, b) => (b.toReview > (a?.toReview ?? 0) ? b : a), null);
37
+ return {
38
+ improve,
39
+ slowest,
40
+ costliest,
41
+ review: {
42
+ total: boards.reduce((t, b) => t + b.toReview, 0),
43
+ key: most?.key ?? null,
44
+ label: most?.label ?? null,
45
+ most: most?.toReview ?? 0,
46
+ },
47
+ };
48
+ }
49
+ //# sourceMappingURL=attention.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attention.js","sourceRoot":"","sources":["../src/attention.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import type { Verdict } from "./compare.js";
2
+ import type { SuiteMetrics } from "./stats.js";
3
+ /**
4
+ * One row of a suite's model board: a model (and reasoning setting) with
5
+ * its latest test on every case in use, and how it compares with the live
6
+ * model. Storage adapters build these; health, attention, and suggest read
7
+ * them.
8
+ */
9
+ export type BoardRow = {
10
+ /** Model and reasoning setting together. */
11
+ key: string;
12
+ model: string;
13
+ /** The reasoning setting; null: the model's own default. */
14
+ effort: string | null;
15
+ /** At a setting other than the operation's (a "Test a setting" row). */
16
+ variant: boolean;
17
+ label: string;
18
+ live: boolean;
19
+ fallback: boolean;
20
+ /** Code, not a model (`ModelInfo.baseline`): compared, never made live or the fallback. */
21
+ baseline: boolean;
22
+ testing: boolean;
23
+ /** The newest test failed: why, in a line. Older results still show. */
24
+ failed: string | null;
25
+ /** Estimated from real usage, for models not tested yet. */
26
+ estCostPerAnswer: number | null;
27
+ test: {
28
+ runId: string;
29
+ at: Date;
30
+ /** Parts right of parts expected (or answers, where a suite has no parts). */
31
+ right: number;
32
+ total: number;
33
+ /** Answers with every check passed. */
34
+ answersRight: number;
35
+ answers: number;
36
+ /** Pass rate of each check, for the breakdown table. */
37
+ byCheck: SuiteMetrics["byCheck"];
38
+ /** Per check, probability measures (decision suites); empty otherwise. */
39
+ measures: NonNullable<SuiteMetrics["measures"]>;
40
+ avgMs: number | null;
41
+ costPerAnswer: number | null;
42
+ /** Cases, answers, or instructions changed since: test again. */
43
+ stale: boolean;
44
+ /** Answers that failed (no score), and the usual reason. */
45
+ failedAnswers: number;
46
+ failureReason: string | null;
47
+ } | null;
48
+ /** Against the live model's test, when both are current. */
49
+ vsLive: Verdict | null;
50
+ canMakeLive: boolean;
51
+ /** Tested, current, and from another provider than the live model. */
52
+ canMakeFallback: boolean;
53
+ /** Can be live but not the fallback (a model that only answers typed questions). */
54
+ liveOnly: boolean;
55
+ /** Can be the fallback but not live. */
56
+ fallbackOnly: boolean;
57
+ };
package/dist/board.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=board.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"board.js","sourceRoot":"","sources":["../src/board.ts"],"names":[],"mappings":""}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * `npx @crowandraven/evalkeeper add pages`: copies the eval pages, components, and their
3
+ * glue into a Next.js app, fitted to its folders and import alias. It never
4
+ * overwrites a file unless told to, and writes the app's wiring module only
5
+ * when there isn't one. Node-only (it reads and writes files).
6
+ */
7
+ /** What gets copied, from the template (the example app's layout). */
8
+ export declare const TEMPLATE_FILES: {
9
+ /** Copied as they are, with imports rewritten to the app's alias. */
10
+ readonly always: readonly ["components/evalkeeper", "app/evals", "lib/evalkeeper/actions.ts", "lib/evalkeeper/weekly.ts", "lib/evalkeeper/cronAuth.ts", "lib/evalkeeper/time.ts", "lib/evalkeeper/paths.ts"];
11
+ /** The weekly check's cron route; skipped with --no-cron. */
12
+ readonly cron: readonly ["app/api/cron/evals-weekly/route.ts"];
13
+ };
14
+ /**
15
+ * What `addPages` needs: the app's folder, the templates, and the choices from the command line.
16
+ */
17
+ export type AddPagesOptions = {
18
+ /** The app's folder. */
19
+ cwd: string;
20
+ /** The built templates (the example app's files). */
21
+ templatesDir: string;
22
+ /** The starter wiring module. */
23
+ starterFile: string;
24
+ /** Where the pages live, under the app: "evals", "admin/evals". */
25
+ base?: string;
26
+ /** The import alias (`@`); read from tsconfig.json when absent. */
27
+ alias?: string;
28
+ /** Where the alias points ("src", "."); read from tsconfig.json. */
29
+ root?: string;
30
+ cron?: boolean;
31
+ force?: boolean;
32
+ dryRun?: boolean;
33
+ };
34
+ /** One file the command would write, overwrite, or leave alone. */
35
+ export type PlannedFile = {
36
+ /** Relative to the app's folder. */
37
+ path: string;
38
+ action: "write" | "overwrite" | "skip";
39
+ contents: string;
40
+ };
41
+ /**
42
+ * JSON with comments and trailing commas (what tsconfig.json allows) as
43
+ * plain JSON. Scans character by character so "/*" inside a string, like
44
+ * `"~/*"` or `"**\/*.ts"`, is never mistaken for a comment.
45
+ */
46
+ export declare function withoutJsonComments(text: string): string;
47
+ /** The app's import alias and where it points, from tsconfig.json. */
48
+ export declare function readAlias(cwd: string): {
49
+ alias: string;
50
+ root: string;
51
+ } | null;
52
+ /** "admin/evals" from "/admin/evals/" or "admin/evals". */
53
+ export declare function cleanBase(base: string): string;
54
+ /** A template file's text, fitted to the app. */
55
+ export declare function fitFile(templatePath: string, text: string, o: {
56
+ alias: string;
57
+ base: string;
58
+ }): string;
59
+ /** Works out every file to write, without writing anything. */
60
+ export declare function planAddPages(o: AddPagesOptions): {
61
+ files: PlannedFile[];
62
+ alias: string;
63
+ root: string;
64
+ base: string;
65
+ };
66
+ /** Writes the plan (unless it's a dry run) and returns it. */
67
+ export declare function addPages(o: AddPagesOptions): {
68
+ files: PlannedFile[];
69
+ alias: string;
70
+ root: string;
71
+ base: string;
72
+ };
73
+ /** What to do next, in order, for the summary the CLI prints. */
74
+ export declare function nextSteps(r: {
75
+ root: string;
76
+ alias: string;
77
+ base: string;
78
+ files: PlannedFile[];
79
+ }): string[];
@@ -0,0 +1,181 @@
1
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync, } from "node:fs";
2
+ import { dirname, join, relative } from "node:path";
3
+ /**
4
+ * `npx @crowandraven/evalkeeper add pages`: copies the eval pages, components, and their
5
+ * glue into a Next.js app, fitted to its folders and import alias. It never
6
+ * overwrites a file unless told to, and writes the app's wiring module only
7
+ * when there isn't one. Node-only (it reads and writes files).
8
+ */
9
+ /** What gets copied, from the template (the example app's layout). */
10
+ export const TEMPLATE_FILES = {
11
+ /** Copied as they are, with imports rewritten to the app's alias. */
12
+ always: [
13
+ "components/evalkeeper",
14
+ "app/evals",
15
+ "lib/evalkeeper/actions.ts",
16
+ "lib/evalkeeper/weekly.ts",
17
+ "lib/evalkeeper/cronAuth.ts",
18
+ "lib/evalkeeper/time.ts",
19
+ "lib/evalkeeper/paths.ts",
20
+ ],
21
+ /** The weekly check's cron route; skipped with --no-cron. */
22
+ cron: ["app/api/cron/evals-weekly/route.ts"],
23
+ };
24
+ /**
25
+ * JSON with comments and trailing commas (what tsconfig.json allows) as
26
+ * plain JSON. Scans character by character so "/*" inside a string, like
27
+ * `"~/*"` or `"**\/*.ts"`, is never mistaken for a comment.
28
+ */
29
+ export function withoutJsonComments(text) {
30
+ let out = "";
31
+ let i = 0;
32
+ while (i < text.length) {
33
+ const c = text.charAt(i);
34
+ const next = text.charAt(i + 1);
35
+ if (c === '"') {
36
+ // A string, copied as it is, escapes included.
37
+ let j = i + 1;
38
+ while (j < text.length && text[j] !== '"')
39
+ j += text[j] === "\\" ? 2 : 1;
40
+ out += text.slice(i, j + 1);
41
+ i = j + 1;
42
+ }
43
+ else if (c === "/" && next === "/") {
44
+ while (i < text.length && text[i] !== "\n")
45
+ i++;
46
+ }
47
+ else if (c === "/" && next === "*") {
48
+ const end = text.indexOf("*/", i + 2);
49
+ i = end < 0 ? text.length : end + 2;
50
+ }
51
+ else {
52
+ out += c;
53
+ i++;
54
+ }
55
+ }
56
+ // Trailing commas, now that no comment can hide one.
57
+ return out.replace(/,(\s*[}\]])/g, "$1");
58
+ }
59
+ /** The app's import alias and where it points, from tsconfig.json. */
60
+ export function readAlias(cwd) {
61
+ const file = join(cwd, "tsconfig.json");
62
+ if (!existsSync(file))
63
+ return null;
64
+ const text = withoutJsonComments(readFileSync(file, "utf8"));
65
+ let parsed;
66
+ try {
67
+ parsed = JSON.parse(text);
68
+ }
69
+ catch {
70
+ return null;
71
+ }
72
+ const paths = parsed
73
+ .compilerOptions?.paths;
74
+ if (!paths || typeof paths !== "object")
75
+ return null;
76
+ for (const [key, value] of Object.entries(paths)) {
77
+ const target = Array.isArray(value) ? value[0] : undefined;
78
+ if (!key.endsWith("/*") || typeof target !== "string")
79
+ continue;
80
+ if (!target.endsWith("/*"))
81
+ continue;
82
+ const root = target.slice(0, -2).replace(/^\.\//, "") || ".";
83
+ return { alias: key.slice(0, -2), root };
84
+ }
85
+ return null;
86
+ }
87
+ /** "admin/evals" from "/admin/evals/" or "admin/evals". */
88
+ export function cleanBase(base) {
89
+ const b = base.trim().replace(/^\/+|\/+$/g, "");
90
+ if (!b || !/^[a-z0-9_\-/[\]()]+$/i.test(b))
91
+ throw new Error(`"${base}" isn't a usable path for the eval pages`);
92
+ return b;
93
+ }
94
+ function listFiles(dir) {
95
+ return readdirSync(dir, { withFileTypes: true }).flatMap((e) => e.isDirectory() ? listFiles(join(dir, e.name)) : [join(dir, e.name)]);
96
+ }
97
+ /** A template file's text, fitted to the app. */
98
+ export function fitFile(templatePath, text, o) {
99
+ let out = text;
100
+ if (/\.(ts|tsx)$/.test(templatePath) && o.alias !== "@")
101
+ out = out.replace(/(["'])@\//g, `$1${o.alias}/`);
102
+ if (templatePath === "lib/evalkeeper/paths.ts")
103
+ out = out.replace(/export const EVALS_BASE = "[^"]*";/, `export const EVALS_BASE = "/${o.base}";`);
104
+ return out;
105
+ }
106
+ /** Where a template file goes in the app, relative to the app's root folder. */
107
+ function destinationOf(templatePath, base) {
108
+ return templatePath.startsWith("app/evals/")
109
+ ? `app/${base}/${templatePath.slice("app/evals/".length)}`
110
+ : templatePath;
111
+ }
112
+ /** Works out every file to write, without writing anything. */
113
+ export function planAddPages(o) {
114
+ const base = cleanBase(o.base ?? "evals");
115
+ const found = readAlias(o.cwd);
116
+ const alias = o.alias ?? found?.alias;
117
+ const root = o.root ?? found?.root ?? (alias ? "." : undefined);
118
+ if (!alias || root === undefined)
119
+ throw new Error('No import alias found. Add one to tsconfig.json ("paths": { "@/*": ["./*"] }), or pass --alias and --root.');
120
+ const rootDir = join(o.cwd, root);
121
+ if (!existsSync(join(rootDir, "app")))
122
+ throw new Error(`No app folder at ${relative(o.cwd, join(rootDir, "app")) || "app"}: Evalkeeper's pages are for the Next.js App Router.`);
123
+ const sources = [
124
+ ...TEMPLATE_FILES.always,
125
+ ...(o.cron === false ? [] : TEMPLATE_FILES.cron),
126
+ ].flatMap((entry) => {
127
+ const full = join(o.templatesDir, entry);
128
+ if (!existsSync(full))
129
+ throw new Error(`The templates are missing ${entry}; rebuild the package.`);
130
+ return entry.endsWith(".ts") || entry.endsWith(".tsx")
131
+ ? [entry]
132
+ : listFiles(full).map((f) => relative(o.templatesDir, f));
133
+ });
134
+ const plan = (path, contents, alwaysSkipExisting = false) => {
135
+ const exists = existsSync(join(o.cwd, path));
136
+ const action = !exists
137
+ ? "write"
138
+ : o.force && !alwaysSkipExisting
139
+ ? "overwrite"
140
+ : "skip";
141
+ return { path, action, contents };
142
+ };
143
+ const files = sources.map((t) => plan(join(root, destinationOf(t, base)), fitFile(t, readFileSync(join(o.templatesDir, t), "utf8"), {
144
+ alias,
145
+ base,
146
+ })));
147
+ // The wiring module is the app's own: written once, never overwritten.
148
+ files.push(plan(join(root, "lib/evalkeeper/index.ts"), fitFile("lib/evalkeeper/index.ts", readFileSync(o.starterFile, "utf8"), {
149
+ alias,
150
+ base,
151
+ }), true));
152
+ return { files, alias, root, base };
153
+ }
154
+ /** Writes the plan (unless it's a dry run) and returns it. */
155
+ export function addPages(o) {
156
+ const result = planAddPages(o);
157
+ if (!o.dryRun)
158
+ for (const f of result.files) {
159
+ if (f.action === "skip")
160
+ continue;
161
+ const dest = join(o.cwd, f.path);
162
+ mkdirSync(dirname(dest), { recursive: true });
163
+ writeFileSync(dest, f.contents);
164
+ }
165
+ return result;
166
+ }
167
+ /** What to do next, in order, for the summary the CLI prints. */
168
+ export function nextSteps(r) {
169
+ const wiring = r.files.find((f) => f.path.endsWith("lib/evalkeeper/index.ts"));
170
+ const css = join(r.root, "components/evalkeeper/evalkeeper.css");
171
+ return [
172
+ "npm install @crowandraven/evalkeeper @crowandraven/evalkeeper-prisma @crowandraven/evalkeeper-ai-sdk server-only lucide-react",
173
+ "Add Evalkeeper's models to your Prisma schema (node_modules/@crowandraven/evalkeeper-prisma/schema/evalkeeper.prisma), then write a migration from the schema file.",
174
+ `Import the colors into your global CSS, right after Tailwind: @import "${css}"; (a path relative to that CSS file).`,
175
+ wiring?.action === "skip"
176
+ ? `Check ${wiring.path} exports what the pages use: MODELS, availableModels, modelsFor, SUITES, SUITE_BY_KEY, store, feedbackStore, settings, routeFor, runner, and requireAdmin.`
177
+ : `Fill in ${wiring?.path ?? "lib/evalkeeper/index.ts"}: your Prisma client, models, suites, and requireAdmin (until then, nobody can open the pages).`,
178
+ `Open /${r.base} in your app.`,
179
+ ];
180
+ }
181
+ //# sourceMappingURL=addPages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"addPages.js","sourceRoot":"","sources":["../../src/cli/addPages.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import { type AddPagesOptions } from "./addPages.js";
2
+ /** The `evalkeeper` command. */
3
+ /** The command's help text. */
4
+ export declare const HELP = "Usage: npx @crowandraven/evalkeeper add pages [options]\n\nCopies Evalkeeper's eval pages, components, and their glue into your\nNext.js app, fitted to its folders and import alias.\n\nOptions:\n --base <path> Where the pages live in your app (default: evals)\n --alias <name> Your import alias, e.g. \"@\" or \"~\" (default: from tsconfig.json)\n --root <dir> Where the alias points, e.g. \"src\" (default: from tsconfig.json)\n --no-cron Leave out the weekly check's cron route\n --force Overwrite files that already exist (never your lib/evalkeeper/index.ts)\n --dry-run Show what would be written, and write nothing\n --help Show this\n";
5
+ /**
6
+ * Reads the command line into a command and options. Throws on an unknown option or a missing value.
7
+ */
8
+ export declare function parseArgs(argv: string[]): Partial<AddPagesOptions> & {
9
+ command: string[];
10
+ help: boolean;
11
+ };
12
+ /** Runs the command; returns the exit code. */
13
+ export declare function run(argv: string[], io: {
14
+ cwd: string;
15
+ log: (line: string) => void;
16
+ error: (line: string) => void;
17
+ pkgDir?: string;
18
+ }): number;
@@ -0,0 +1,115 @@
1
+ /**
2
+ * The `evalkeeper` command: `npx @crowandraven/evalkeeper add pages`.
3
+ *
4
+ * @module evalkeeper/cli
5
+ */
6
+ import { dirname, join } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { addPages, nextSteps } from "./addPages.js";
9
+ /** The `evalkeeper` command. */
10
+ /** The command's help text. */
11
+ export const HELP = `Usage: npx @crowandraven/evalkeeper add pages [options]
12
+
13
+ Copies Evalkeeper's eval pages, components, and their glue into your
14
+ Next.js app, fitted to its folders and import alias.
15
+
16
+ Options:
17
+ --base <path> Where the pages live in your app (default: evals)
18
+ --alias <name> Your import alias, e.g. "@" or "~" (default: from tsconfig.json)
19
+ --root <dir> Where the alias points, e.g. "src" (default: from tsconfig.json)
20
+ --no-cron Leave out the weekly check's cron route
21
+ --force Overwrite files that already exist (never your lib/evalkeeper/index.ts)
22
+ --dry-run Show what would be written, and write nothing
23
+ --help Show this
24
+ `;
25
+ /** The package's own folder, where the templates and starter ship. */
26
+ function packageDir() {
27
+ // dist/cli/main.js → the package root.
28
+ return join(dirname(fileURLToPath(import.meta.url)), "..", "..");
29
+ }
30
+ /**
31
+ * Reads the command line into a command and options. Throws on an unknown option or a missing value.
32
+ */
33
+ export function parseArgs(argv) {
34
+ const out = {
35
+ command: [],
36
+ help: false,
37
+ };
38
+ for (let i = 0; i < argv.length; i++) {
39
+ const a = argv[i] ?? "";
40
+ const value = () => {
41
+ const v = argv[++i];
42
+ if (v === undefined || v.startsWith("--"))
43
+ throw new Error(`${a} needs a value`);
44
+ return v;
45
+ };
46
+ if (a === "--help" || a === "-h")
47
+ out.help = true;
48
+ else if (a === "--base")
49
+ out.base = value();
50
+ else if (a === "--alias")
51
+ out.alias = value();
52
+ else if (a === "--root")
53
+ out.root = value();
54
+ else if (a === "--no-cron")
55
+ out.cron = false;
56
+ else if (a === "--force")
57
+ out.force = true;
58
+ else if (a === "--dry-run")
59
+ out.dryRun = true;
60
+ else if (a.startsWith("--"))
61
+ throw new Error(`Unknown option ${a}`);
62
+ else
63
+ out.command.push(a);
64
+ }
65
+ return out;
66
+ }
67
+ /** Runs the command; returns the exit code. */
68
+ export function run(argv, io) {
69
+ let args;
70
+ try {
71
+ args = parseArgs(argv);
72
+ }
73
+ catch (e) {
74
+ io.error(e instanceof Error ? e.message : String(e));
75
+ io.error(HELP);
76
+ return 2;
77
+ }
78
+ if (args.help || args.command.length === 0) {
79
+ io.log(HELP);
80
+ return 0;
81
+ }
82
+ if (args.command.join(" ") !== "add pages") {
83
+ io.error(`Unknown command "${args.command.join(" ")}".`);
84
+ io.error(HELP);
85
+ return 2;
86
+ }
87
+ const pkg = io.pkgDir ?? packageDir();
88
+ try {
89
+ const { command: _command, help: _help, ...rest } = args;
90
+ const result = addPages({
91
+ ...rest,
92
+ cwd: io.cwd,
93
+ templatesDir: join(pkg, "templates"),
94
+ starterFile: join(pkg, "starter", "index.ts"),
95
+ });
96
+ const verb = { write: "wrote", overwrite: "overwrote", skip: "skipped" };
97
+ io.log(args.dryRun
98
+ ? "Dry run: nothing written. This would happen:"
99
+ : `Added Evalkeeper's pages at /${result.base}:`);
100
+ for (const f of result.files)
101
+ io.log(` ${(args.dryRun ? f.action : verb[f.action]).padEnd(10)} ${f.path}${f.action === "skip" ? " (exists)" : ""}`);
102
+ const skipped = result.files.filter((f) => f.action === "skip").length;
103
+ if (skipped && !args.force)
104
+ io.log(`\n${skipped} file${skipped === 1 ? "" : "s"} already existed and stayed as they are. --force overwrites them (your lib/evalkeeper/index.ts never).`);
105
+ io.log("\nNext:");
106
+ for (const [i, step] of nextSteps(result).entries())
107
+ io.log(` ${i + 1}. ${step}`);
108
+ return 0;
109
+ }
110
+ catch (e) {
111
+ io.error(e instanceof Error ? e.message : String(e));
112
+ return 1;
113
+ }
114
+ }
115
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":"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"}