evals-lab 0.11.0 → 0.12.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 (58) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +4 -4
  3. package/bin/evals-lab.js +9 -1
  4. package/bin/lab-file.js +97 -0
  5. package/bin/run.js +1 -1
  6. package/lab/VERSION +1 -1
  7. package/lab/demo/CREDITS.md +37 -3
  8. package/lab/demo/datasets/demo-1.json +2 -2
  9. package/lab/demo/datasets/demo-2.json +2 -2
  10. package/lab/demo/datasets/sentiment.json +383 -0
  11. package/lab/demo/datasets/weather-alerts.json +61 -0
  12. package/lab/demo/datasets/wordings.json +39 -0
  13. package/lab/demo/manifest.json +3 -3
  14. package/lab/demo/pipelines/demo-1.json +4 -4
  15. package/lab/demo/pipelines/demo-2.json +4 -4
  16. package/lab/demo/pipelines/sentiment.json +65 -0
  17. package/lab/demo/pipelines/weather-alerts.json +66 -0
  18. package/lab/demo/pipelines/wordings.json +104 -0
  19. package/lab/demo/sources/Demo 1/source.json +3 -0
  20. package/lab/demo/sources/Demo 2/source.json +3 -0
  21. package/lab/demo/sources/sentiment/bakery-croissant.txt +1 -0
  22. package/lab/demo/sources/sentiment/bus-timetable.txt +1 -0
  23. package/lab/demo/sources/sentiment/cafe-coffee.txt +1 -0
  24. package/lab/demo/sources/sentiment/concert-night.txt +1 -0
  25. package/lab/demo/sources/sentiment/film-sequel.txt +1 -0
  26. package/lab/demo/sources/sentiment/garden-tomatoes.txt +1 -0
  27. package/lab/demo/sources/sentiment/gym-contract.txt +1 -0
  28. package/lab/demo/sources/sentiment/headphones-review.txt +1 -0
  29. package/lab/demo/sources/sentiment/hotel-room.txt +1 -0
  30. package/lab/demo/sources/sentiment/library-hours.txt +1 -0
  31. package/lab/demo/sources/sentiment/meeting-minutes.txt +1 -0
  32. package/lab/demo/sources/sentiment/parcel-lost.txt +1 -0
  33. package/lab/demo/sources/sentiment/phone-update.txt +1 -0
  34. package/lab/demo/sources/sentiment/puppy-home.txt +1 -0
  35. package/lab/demo/sources/sentiment/recipe-note.txt +1 -0
  36. package/lab/demo/sources/sentiment/road-closure.txt +1 -0
  37. package/lab/demo/sources/sentiment/source.json +3 -0
  38. package/lab/demo/sources/sentiment/teacher-thanks.txt +1 -0
  39. package/lab/demo/sources/sentiment/train-delay.txt +1 -0
  40. package/lab/demo/sources/sentiment/volunteer-day.txt +1 -0
  41. package/lab/demo/sources/sentiment/weather-report.txt +1 -0
  42. package/lab/demo/sources/weather-alerts/https-api.weather.gov-alerts-types.txt +116 -0
  43. package/lab/demo/sources/weather-alerts/source.json +8 -0
  44. package/lab/evals-core.mjs +76 -16
  45. package/lab/server.py +484 -54
  46. package/lab/web/dist/assets/gallery-CFntjQFG.js +5 -0
  47. package/lab/web/dist/assets/main-BlTG2i20.css +1 -0
  48. package/lab/web/dist/assets/main-D6MNWqak.js +17 -0
  49. package/lab/web/dist/assets/tokens-DwzzdSIF.css +1 -0
  50. package/lab/web/dist/assets/tokens-bvuNFuYg.js +60 -0
  51. package/lab/web/dist/gallery.html +3 -3
  52. package/lab/web/dist/index.html +4 -4
  53. package/package.json +1 -1
  54. package/lab/web/dist/assets/gallery-DKy8QK3o.js +0 -5
  55. package/lab/web/dist/assets/main-BGmSu7aS.css +0 -1
  56. package/lab/web/dist/assets/main-DdGON-yl.js +0 -16
  57. package/lab/web/dist/assets/tokens-BmytrwxE.js +0 -60
  58. package/lab/web/dist/assets/tokens-DBN7I0ru.css +0 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,53 @@ Newest first. Read a release's **Upgrade notes** before installing it: an
5
5
  upgrade can rewrite what the lab keeps in your data directory, and an older
6
6
  version cannot always read it back.
7
7
 
8
+ ## 0.12.0
9
+
10
+ ### Upgrade notes
11
+
12
+ - **Copy your data directory before upgrading** (see "User data" in the
13
+ README). A pipeline 0.12.0 saves is pipeline version 17, which 0.11.x
14
+ cannot read. To go back, reinstall 0.11.0 and restore the copy.
15
+ - An HTTP Request step no longer picks an API of its own: it uses its job's
16
+ flow step and connection. A step that named a different API keeps the
17
+ request it would have sent, and a request in a job with no flow step
18
+ becomes a Prompt keeping its words. Pipelines read as version 17, and are
19
+ saved as it at their next edit.
20
+
21
+ ### Added
22
+
23
+ - `evals-lab export --lab <url>` writes a lab's settings to a file:
24
+ workspaces, connections with who each is shared with, profiles and the
25
+ grader, never a key. `--workspace <slug>` exports one workspace's.
26
+ `evals-lab import <file> --lab <url>` adds what the lab lacks, giving each
27
+ connection the key in `$EVALSLAB_API_KEY_<SLUG>`, and sends keys only to
28
+ an https or loopback lab.
29
+ - `LAB_IMPORT=<file>` imports a lab file each time the lab starts.
30
+ - A new lab opens on five demos: describe images (twice), classify
31
+ sentiment, compare wordings, and sort weather alerts from a live HTTP GET
32
+ Source. A pack can now carry a dynamic Source.
33
+ - Create test… on a dynamic Source's Calls makes a dataset that is graded
34
+ and ready to run.
35
+
36
+ ### Changed
37
+
38
+ - A new lab starts in about half the time.
39
+ - Names, slugs and model ids get the full width of their field, the tables
40
+ in one dialog share one label column, and Undo is amber and labelled.
41
+ - A dynamic Source names its provider once, and the grader picker says
42
+ Connection.
43
+
44
+ ### Fixed
45
+
46
+ - An API call Source sends an HTTP endpoint connection's key in the header
47
+ the connection names, not always `Authorization: Bearer`.
48
+ - An API call Source can use only a connection shared with its workspace.
49
+ - A run is refused when two connections take their key from the same
50
+ variable, on a re-run too.
51
+ - Seeding from production reads a gateway flow's replies in its
52
+ connection's request format.
53
+ - The Runs stop keeps its icon on Home.
54
+
8
55
  ## 0.11.0
9
56
 
10
57
  ### Upgrade notes
package/README.md CHANGED
@@ -45,8 +45,8 @@ npm install -g evals-lab
45
45
  evals-lab
46
46
  ```
47
47
 
48
- Open <http://localhost:8080>. A new lab starts with two demo datasets and
49
- their pipelines. Note the first start takes longer while it installs them. Without
48
+ Open <http://localhost:8080>. A new lab starts with five demos, each a dataset
49
+ and its pipeline, which run once you add an AI Connection in Setup. Note the first start takes longer while it installs them. Without
50
50
  installing: `npx evals-lab`.
51
51
 
52
52
  | Needs | Windows | Linux |
@@ -164,7 +164,7 @@ CI can run a lab's own pipeline there instead, so the run shows in that lab's
164
164
  History and its keys never leave it:
165
165
 
166
166
  ```bash
167
- LAB_PASSWORD=... evals-lab run --lab https://lab.example --pipeline "Demo 1" --wait
167
+ LAB_PASSWORD=... evals-lab run --lab https://lab.example --pipeline "Demo: describe images" --wait
168
168
  ```
169
169
 
170
170
  ```text
@@ -249,5 +249,5 @@ Copyright 2026 Grainbox Limited, under the
249
249
  [PolyForm Internal Use License 1.0.0](https://polyformproject.org/licenses/internal-use/1.0.0)
250
250
  (`LICENSE.md`): you and your company may use and change it for your own
251
251
  internal business, but not give it, or a changed copy, to anyone else, sell
252
- it, or offer it as a service. The demo images are public domain; each is
252
+ it, or offer it as a service. The demo files are public domain; each is
253
253
  credited in `lab/demo/CREDITS.md`.
package/bin/evals-lab.js CHANGED
@@ -30,6 +30,8 @@ const LOCAL_OLLAMA = "http://127.0.0.1:11434";
30
30
  const USAGE = `Usage: evals-lab [options]
31
31
  evals-lab run <bundle> [options] (evals-lab run --help)
32
32
  evals-lab run --lab <url> --pipeline <name> [--wait] [options]
33
+ evals-lab export --lab <url> [--workspace <slug>] [--out <file>]
34
+ evals-lab import <file> --lab <url> (evals-lab import --help)
33
35
 
34
36
  --port <n> the port to listen on. Default: $PORT, else 8080.
35
37
  --host <address> the address to listen on. Default: $LAB_HOST, else
@@ -43,7 +45,9 @@ Environment: LAB_PASSWORD puts a password in front of the lab, OLLAMA_URL is
43
45
  the connection a new lab starts with, and PYTHON names the Python to run.
44
46
  SOURCE_ALLOW_LOCAL_FILES=1 and SOURCE_ALLOW_LOCAL_COMMANDS=1 let a Source read a
45
47
  file or run a command on this machine; both are off unless set.
46
- SOURCE_LOCAL_ROOT=<dir> lets a file Source read only under that directory.`;
48
+ SOURCE_LOCAL_ROOT=<dir> lets a file Source read only under that directory.
49
+ LAB_IMPORT=<file> imports a lab file each time the lab starts, each
50
+ connection's key from $EVALSLAB_API_KEY_<SLUG>.`;
47
51
 
48
52
  // Where a lab keeps its store when nobody said: the platform's per-user data
49
53
  // directory, so it survives the package being upgraded or reinstalled.
@@ -159,6 +163,10 @@ function fail(message) {
159
163
  }
160
164
 
161
165
  async function main() {
166
+ if (process.argv[2] === "export" || process.argv[2] === "import") {
167
+ process.exitCode = await require("./lab-file.js").labFile(process.argv[2], process.argv.slice(3));
168
+ return;
169
+ }
162
170
  if (process.argv[2] === "run") {
163
171
  process.exitCode = await require("./run.js").run(process.argv.slice(3), { lab: LAB });
164
172
  return;
@@ -0,0 +1,97 @@
1
+ // `evals-lab export` and `evals-lab import` (docs/lab-export.md): a running
2
+ // lab's settings -- its AI Connections, Connection Profiles, grader and
3
+ // workspaces -- as a lab file, and a lab file into a lab, adding what it
4
+ // lacks. Setup › Workspaces › Data does the same through the same two routes.
5
+ //
6
+ // THE KEYS. A lab file holds none, and the lab serves none. `import` sends
7
+ // each connection's from $EVALSLAB_API_KEY_<SLUG>, as a CI run reads it, and
8
+ // only to an https or loopback lab: anything else would put the key on the
9
+ // wire in the clear.
10
+ const fs = require("fs");
11
+ const path = require("path");
12
+ const { labApi, Refused } = require("./run.js");
13
+
14
+ const EXIT = { broken: 3 };
15
+
16
+ const USAGE = `Usage: evals-lab export --lab <url> [--workspace <slug>] [--out <file>]
17
+ evals-lab import <file> --lab <url>
18
+
19
+ --lab <url> the running lab. LAB_PASSWORD is sent when it is set.
20
+ --workspace <slug> export only the workspace at /w/<slug>. Default: every one.
21
+ --out <file> where the export goes. Default: stdout.
22
+
23
+ A lab file holds no key. import gives each connection the key in
24
+ $EVALSLAB_API_KEY_<SLUG> when it is set, and sends keys only to an https or
25
+ loopback lab. An import only adds: what the lab has already is left as it is.`;
26
+
27
+ function options(argv, verb) {
28
+ const o = {};
29
+ const value = { "--lab": "lab", "--workspace": "workspace", "--out": "out" };
30
+ for (let i = 0; i < argv.length; i++) {
31
+ const a = argv[i];
32
+ if (a === "--help" || a === "-h") o.help = true;
33
+ else if (value[a]) {
34
+ if (argv[i + 1] === undefined) throw new Refused(`${a} needs a value`, true);
35
+ o[value[a]] = argv[++i];
36
+ } else if (a.startsWith("-")) throw new Refused(`no option ${a}`, true);
37
+ else if (verb === "import" && o.file === undefined) o.file = a;
38
+ else throw new Refused(`one lab file, not ${a} as well`, true);
39
+ }
40
+ if (o.help) return o;
41
+ if (o.lab == null) throw new Refused("name the lab with --lab", true);
42
+ if (!/^https?:\/\/[^/]/i.test(o.lab)) throw new Refused(`--lab ${o.lab} is not an http or https address`, true);
43
+ if (verb === "import") {
44
+ if (o.file == null) throw new Refused("name the lab file to import", true);
45
+ if (o.workspace != null || o.out != null) throw new Refused("--workspace and --out are export's", true);
46
+ }
47
+ return o;
48
+ }
49
+
50
+ const keyVar = (id, slug) => "EVALSLAB_API_KEY_"
51
+ + String(slug || id).toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
52
+
53
+ // Loopback or https: where a key may go.
54
+ function keySafe(base) {
55
+ const u = new URL(base);
56
+ return u.protocol === "https:" || ["localhost", "127.0.0.1", "[::1]"].includes(u.hostname);
57
+ }
58
+
59
+ async function labFile(verb, argv, { env = process.env, stdout = process.stdout, stderr = process.stderr } = {}) {
60
+ const say = text => stderr.write(text + "\n");
61
+ try {
62
+ const o = options(argv, verb);
63
+ if (o.help) { stdout.write(USAGE + "\n"); return 0; }
64
+ const base = o.lab.replace(/\/+$/, "");
65
+ const api = labApi(base, env);
66
+ if (verb === "export") {
67
+ const out = await api("GET", "/api/lab-export" + (o.workspace ? `?workspace=${encodeURIComponent(o.workspace)}` : ""));
68
+ const text = JSON.stringify(out, null, 2) + "\n";
69
+ if (o.out) fs.writeFileSync(path.resolve(o.out), text);
70
+ else stdout.write(text);
71
+ if (o.out) say(`${out.connections.length} connections, ${out.workspaces.length} workspaces to ${o.out}`);
72
+ return 0;
73
+ }
74
+ let lab;
75
+ try { lab = JSON.parse(fs.readFileSync(path.resolve(o.file), "utf8")); }
76
+ catch (e) { throw new Refused(`${o.file} is not a lab file: ${e.message}`); }
77
+ const keys = {};
78
+ for (const c of Array.isArray(lab?.connections) ? lab.connections : []) {
79
+ const name = keyVar(c?.id, c?.slug);
80
+ if (env[name]) keys[name] = env[name];
81
+ }
82
+ if (Object.keys(keys).length && !keySafe(base)) {
83
+ throw new Refused(`${base} is neither https nor loopback, so no key goes to it: `
84
+ + `unset ${Object.keys(keys).join(", ")} to import without them`);
85
+ }
86
+ const { said } = await api("POST", "/api/lab-import", { lab, keys });
87
+ for (const line of said) say(line);
88
+ return 0;
89
+ } catch (e) {
90
+ if (!(e instanceof Refused)) throw e;
91
+ say(`evals-lab ${verb}: ${e.message}`);
92
+ if (e.usage) say(`\n${USAGE}`);
93
+ return EXIT.broken;
94
+ }
95
+ }
96
+
97
+ module.exports = { labFile, options, keyVar, keySafe, USAGE };
package/bin/run.js CHANGED
@@ -573,4 +573,4 @@ async function run(argv, { lab, env = process.env, stdout = process.stdout, stde
573
573
  }
574
574
  }
575
575
 
576
- module.exports = { run, runOptions, USAGE };
576
+ module.exports = { run, runOptions, USAGE, labApi, Refused };
package/lab/VERSION CHANGED
@@ -1 +1 @@
1
- 0.11.0 (2026.10.11-671)
1
+ 0.12.0 (2026.10.12-718)
@@ -1,9 +1,13 @@
1
1
  # Credits
2
2
 
3
- Every file in `images/`, where it came from and under what terms. All
3
+ Every file in `sources/`, where it came from and under what terms. All
4
4
  need no attribution; the sources are listed so each one can be traced.
5
- Files were resized to about 3 megapixels and their metadata stripped;
6
- the panorama is a crop, and the PNG and TIFF are conversions.
5
+
6
+ ## Images
7
+
8
+ The images of `Demo 1/` and `Demo 2/`. Files were resized to about 3
9
+ megapixels and their metadata stripped; the panorama is a crop, and the PNG
10
+ and TIFF are conversions.
7
11
 
8
12
  | File | Source | Author | Licence |
9
13
  |---|---|---|---|
@@ -90,3 +94,33 @@ the panorama is a crop, and the PNG and TIFF are conversions.
90
94
  | `whiteboard-meeting-notes.jpg` | https://www.flickr.com/photo.gne?id=186873704 | | Public domain |
91
95
  | `whiteboard-messages.jpg` | https://www.flickr.com/photo.gne?id=23162308061 | | Public domain |
92
96
  | `wooden-house-fence.jpg` | https://www.flickr.com/photo.gne?id=26595748497 | | Public domain |
97
+
98
+ ## Texts
99
+
100
+ The short texts of `sentiment/`, written for this pack, and the alert
101
+ types of `weather-alerts/`, fetched from the National Weather Service's API
102
+ on 2026-10-11. A work of the US federal government is in the public domain.
103
+
104
+ | File | Source | Author | Licence |
105
+ |---|---|---|---|
106
+ | `bakery-croissant.txt` | Written for this pack | Evals Lab | Public domain |
107
+ | `bus-timetable.txt` | Written for this pack | Evals Lab | Public domain |
108
+ | `cafe-coffee.txt` | Written for this pack | Evals Lab | Public domain |
109
+ | `concert-night.txt` | Written for this pack | Evals Lab | Public domain |
110
+ | `film-sequel.txt` | Written for this pack | Evals Lab | Public domain |
111
+ | `garden-tomatoes.txt` | Written for this pack | Evals Lab | Public domain |
112
+ | `gym-contract.txt` | Written for this pack | Evals Lab | Public domain |
113
+ | `headphones-review.txt` | Written for this pack | Evals Lab | Public domain |
114
+ | `hotel-room.txt` | Written for this pack | Evals Lab | Public domain |
115
+ | `library-hours.txt` | Written for this pack | Evals Lab | Public domain |
116
+ | `meeting-minutes.txt` | Written for this pack | Evals Lab | Public domain |
117
+ | `parcel-lost.txt` | Written for this pack | Evals Lab | Public domain |
118
+ | `phone-update.txt` | Written for this pack | Evals Lab | Public domain |
119
+ | `puppy-home.txt` | Written for this pack | Evals Lab | Public domain |
120
+ | `recipe-note.txt` | Written for this pack | Evals Lab | Public domain |
121
+ | `road-closure.txt` | Written for this pack | Evals Lab | Public domain |
122
+ | `teacher-thanks.txt` | Written for this pack | Evals Lab | Public domain |
123
+ | `train-delay.txt` | Written for this pack | Evals Lab | Public domain |
124
+ | `volunteer-day.txt` | Written for this pack | Evals Lab | Public domain |
125
+ | `weather-report.txt` | Written for this pack | Evals Lab | Public domain |
126
+ | `https-api.weather.gov-alerts-types.txt` | https://api.weather.gov/alerts/types | National Weather Service | Public domain |
@@ -2,12 +2,12 @@
2
2
  "format": "evals-lab/dataset",
3
3
  "version": 6,
4
4
  "dataset": {
5
- "name": "Demo 1",
5
+ "name": "Demo: describe images",
6
6
  "body": {
7
7
  "version": 6,
8
8
  "source": {
9
9
  "id": "Demo 1",
10
- "name": "Demo 1"
10
+ "name": "Demo: describe images"
11
11
  },
12
12
  "cases": [
13
13
  {
@@ -2,12 +2,12 @@
2
2
  "format": "evals-lab/dataset",
3
3
  "version": 6,
4
4
  "dataset": {
5
- "name": "Demo 2",
5
+ "name": "Demo: describe images 2",
6
6
  "body": {
7
7
  "version": 6,
8
8
  "source": {
9
9
  "id": "Demo 2",
10
- "name": "Demo 2"
10
+ "name": "Demo: describe images 2"
11
11
  },
12
12
  "cases": [
13
13
  {