@jrpool/kilotest 63.1.0 → 63.1.2
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 +1 -0
- package/docs/AI-TOOLS.md +1 -0
- package/index.ts +1 -2
- package/lhm.plugin.json +1 -1
- package/media/README.md +47 -0
- package/media/demo-chatgpt-src/demo_list.txt +27 -0
- package/media/demo-chatgpt-src/labeled/frame00.png +0 -0
- package/media/demo-chatgpt-src/labeled/frame01.png +0 -0
- package/media/demo-chatgpt-src/labeled/frame02.png +0 -0
- package/media/demo-chatgpt-src/labeled/frame03.png +0 -0
- package/media/demo-chatgpt-src/labeled/frame04.png +0 -0
- package/media/demo-chatgpt-src/labeled/frame05.png +0 -0
- package/media/demo-chatgpt-src/labeled/frame06.png +0 -0
- package/media/demo-chatgpt-src/labeled/frame07.png +0 -0
- package/media/demo-chatgpt-src/labeled/frame08.png +0 -0
- package/media/demo-chatgpt-src/labeled/frame09.png +0 -0
- package/media/demo-chatgpt-src/labeled/frame10.png +0 -0
- package/media/demo-chatgpt-src/labeled/frame11.png +0 -0
- package/media/demo-chatgpt-src/labeled/frame12.png +0 -0
- package/media/demo-chatgpt.mp4 +0 -0
- package/openapi-3.0.yaml +1 -1
- package/openapi.yaml +1 -1
- package/package.json +2 -1
- package/scripts/buildDemoVideo.ts +121 -0
- package/server.json +1 -1
- package/well-known/sealkeeper-claim.txt +1 -0
package/README.md
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
[](https://github.com/jrpool/kilotest/stargazers)
|
|
7
7
|
[](https://tensorblock.co/mcp/servers/github-jrpool-kilotest-f518ba36)
|
|
8
8
|
[](https://mcptry.com/servers/io.github.jrpool/kilotest)
|
|
9
|
+
[](https://sealkeeper.run/agents/kilotest.com/kilotest-com)
|
|
9
10
|
|
|
10
11
|
An ensemble testing and reporting service for front-end web quality
|
|
11
12
|
|
package/docs/AI-TOOLS.md
CHANGED
|
@@ -30,6 +30,7 @@ The internal features that make Kilotest a collection of tools for language mode
|
|
|
30
30
|
- A `capability.md` file with a URL, summarizing what Kilotest tools can do.
|
|
31
31
|
- `mcp`, `mcp-server`, and `modelcontextprotocol` keywords in `package.json`, supporting discovery of the `@jrpool/kilotest` npm package by tools and humans searching the npm registry for MCP servers.
|
|
32
32
|
- An `lhm.plugin.json` file, conforming to the [LobeHub plugin manifest format](https://market.lobehub.com/s/publish-mcp/references/manifest), declaring Kilotest as a plugin in the [LobeHub MCP Market](https://lobehub.com/mcp). Publishing and updating are non-automated (`lhm plugin publish` for the initial listing; thereafter `npm run publish:lhm`, which wraps `lhm plugin update` and is run by `npm run postPR`), because LobeHub requires browser-based OAuth and does not support machine-to-machine publishing.
|
|
33
|
+
- Files in the `well-known` directory for ownership verification to AI registries, including OpenAI and Sealkeeper.
|
|
33
34
|
- A `sitemap.xml` file.
|
|
34
35
|
- Documentation in the `README.md` file.
|
|
35
36
|
- This `AI-TOOLS.md` file.
|
package/index.ts
CHANGED
|
@@ -933,8 +933,7 @@ const handleRequest = async (request: IncomingMessage, response: ServerResponse)
|
|
|
933
933
|
await serveError({message: 'Invalid service request'}, response, false);
|
|
934
934
|
}
|
|
935
935
|
}
|
|
936
|
-
// Otherwise, if it is for a well-known resource
|
|
937
|
-
// challenge token):
|
|
936
|
+
// Otherwise, if it is for a well-known resource:
|
|
938
937
|
else if (pathname.startsWith('/.well-known/')) {
|
|
939
938
|
const knownFile = pathname.slice('/.well-known/'.length);
|
|
940
939
|
const knownPath = path.join(import.meta.dirname, 'well-known', knownFile);
|
package/lhm.plugin.json
CHANGED
package/media/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Media
|
|
2
|
+
|
|
3
|
+
This directory holds the ChatGPT demonstration video, `demo-chatgpt.mp4`, which the web page `/demo-chatgpt.html` offers as an alternative to its step-through slideshow. The video is served at `/media/demo-chatgpt.mp4`.
|
|
4
|
+
|
|
5
|
+
## Source
|
|
6
|
+
|
|
7
|
+
The video is built from `demo-chatgpt-src/`:
|
|
8
|
+
|
|
9
|
+
- `demo_list.txt` lists the slides in order. Each slide is a frame file followed by the number of seconds it stays on screen.
|
|
10
|
+
- `demo-chatgpt-src/labeled/` holds the 800×700 PNG frames that the list references.
|
|
11
|
+
|
|
12
|
+
The final line of `demo_list.txt` repeats the last frame without a duration. ffmpeg's concat format requires this line, and the build script handles it.
|
|
13
|
+
|
|
14
|
+
## Changing durations
|
|
15
|
+
|
|
16
|
+
1. Edit the `duration` lines in `demo-chatgpt-src/demo_list.txt`. Durations must be whole numbers of seconds, because the video has one frame per second.
|
|
17
|
+
2. To change the slides themselves, add or replace frame files in `demo-chatgpt-src/labeled/` and update the `file` lines to match.
|
|
18
|
+
3. Rebuild the video from the repository root:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm run build:media
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The script writes `media/demo-chatgpt.mp4`. Its length is the sum of the durations, and it is encoded at one frame per second.
|
|
25
|
+
|
|
26
|
+
To write the video somewhere else, pass a path:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm run build:media -- /path/to/output.mp4
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Requirements
|
|
33
|
+
|
|
34
|
+
- [ffmpeg](https://ffmpeg.org/) on the PATH. On macOS, install it with `brew install ffmpeg`.
|
|
35
|
+
|
|
36
|
+
## Verification
|
|
37
|
+
|
|
38
|
+
The committed `demo-chatgpt.mp4` is byte-identical to the output of `npm run build:media` with the sources in this folder. After a rebuild, check that the video length matches the sum of the durations:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
ffprobe -v error -show_entries format=duration -of default=nw=1 media/demo-chatgpt.mp4
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Related files
|
|
45
|
+
|
|
46
|
+
- `scripts/buildDemoVideo.ts` contains the build script.
|
|
47
|
+
- `web/demo-chatgpt/` contains the slideshow version of the same demonstration, which is independent of this video.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
file 'labeled/frame00.png'
|
|
2
|
+
duration 3
|
|
3
|
+
file 'labeled/frame01.png'
|
|
4
|
+
duration 12
|
|
5
|
+
file 'labeled/frame02.png'
|
|
6
|
+
duration 4
|
|
7
|
+
file 'labeled/frame03.png'
|
|
8
|
+
duration 10
|
|
9
|
+
file 'labeled/frame04.png'
|
|
10
|
+
duration 8
|
|
11
|
+
file 'labeled/frame05.png'
|
|
12
|
+
duration 11
|
|
13
|
+
file 'labeled/frame06.png'
|
|
14
|
+
duration 5
|
|
15
|
+
file 'labeled/frame07.png'
|
|
16
|
+
duration 25
|
|
17
|
+
file 'labeled/frame08.png'
|
|
18
|
+
duration 22
|
|
19
|
+
file 'labeled/frame09.png'
|
|
20
|
+
duration 14
|
|
21
|
+
file 'labeled/frame10.png'
|
|
22
|
+
duration 18
|
|
23
|
+
file 'labeled/frame11.png'
|
|
24
|
+
duration 26
|
|
25
|
+
file 'labeled/frame12.png'
|
|
26
|
+
duration 12
|
|
27
|
+
file 'labeled/frame12.png'
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/media/demo-chatgpt.mp4
CHANGED
|
Binary file
|
package/openapi-3.0.yaml
CHANGED
|
@@ -2,7 +2,7 @@ openapi: 3.0.3
|
|
|
2
2
|
info:
|
|
3
3
|
title: Kilotest API
|
|
4
4
|
description: 'Kilotest is an ensemble testing and reporting service for front-end web quality. It uses an ensemble of 12 rule engines to test web pages for front-end quality (accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG). Kilotest acts as a web server for human users, an MCP server for AI platforms, and an API for programmatic access. With this API you can request that a page be tested or retested, discover available reports, incrementally retrieve facts from a report: from what reports are available, to what issues were reported in one report, to what elements violated one issue, to what diagnoses rule engines gave for one violation.'
|
|
5
|
-
version: 63.1.
|
|
5
|
+
version: 63.1.2
|
|
6
6
|
license:
|
|
7
7
|
name: MIT
|
|
8
8
|
url: https://github.com/jrpool/kilotest/blob/main/LICENSE
|
package/openapi.yaml
CHANGED
|
@@ -2,7 +2,7 @@ openapi: 3.1.0
|
|
|
2
2
|
info:
|
|
3
3
|
title: Kilotest API
|
|
4
4
|
description: 'Kilotest is an ensemble testing and reporting service for front-end web quality. It uses an ensemble of 12 rule engines to test web pages for front-end quality (accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG). Kilotest acts as a web server for human users, an MCP server for AI platforms, and an API for programmatic access. With this API you can request that a page be tested or retested, discover available reports, incrementally retrieve facts from a report: from what reports are available, to what issues were reported in one report, to what elements violated one issue, to what diagnoses rule engines gave for one violation.'
|
|
5
|
-
version: 63.1.
|
|
5
|
+
version: 63.1.2
|
|
6
6
|
license:
|
|
7
7
|
name: MIT
|
|
8
8
|
identifier: MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jrpool/kilotest",
|
|
3
|
-
"version": "63.1.
|
|
3
|
+
"version": "63.1.2",
|
|
4
4
|
"description": "Ensemble testing for front-end web quality",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"test:smoke": "node smokeTest.ts",
|
|
16
16
|
"checkinfo": "node checkinfo.ts",
|
|
17
17
|
"generate": "node scripts/generate-openapi.ts && node scripts/update-versions.ts",
|
|
18
|
+
"build:media": "node scripts/buildDemoVideo.ts",
|
|
18
19
|
"publish:lhm": "npx -y @lobehub/market-cli plugin update --dir .",
|
|
19
20
|
"prePR": "node scripts/prePR.ts",
|
|
20
21
|
"postPR": "node scripts/postPR.ts"
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/*
|
|
2
|
+
buildDemoVideo.ts
|
|
3
|
+
Rebuilds the ChatGPT demonstration video, media/demo-chatgpt.mp4, from the slide frames and durations listed in media/demo-chatgpt-src/demo_list.txt. Each listed frame is shown for its duration in whole seconds. The video is encoded at one frame per second, which keeps the file small because the slides are static, and its length is set to the sum of the durations.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
npm run build:media [outputPath]
|
|
7
|
+
|
|
8
|
+
outputPath defaults to media/demo-chatgpt.mp4. Requires ffmpeg on the PATH, for example from Homebrew (brew install ffmpeg). See media/README.md.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// IMPORTS
|
|
12
|
+
|
|
13
|
+
import {spawnSync} from 'node:child_process';
|
|
14
|
+
import fs from 'node:fs';
|
|
15
|
+
import os from 'node:os';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
|
|
18
|
+
// CONSTANTS
|
|
19
|
+
|
|
20
|
+
const repoDir = path.join(import.meta.dirname, '..');
|
|
21
|
+
const sourceDir = path.join(repoDir, 'media', 'demo-chatgpt-src');
|
|
22
|
+
const listPath = path.join(sourceDir, 'demo_list.txt');
|
|
23
|
+
const outputPath = path.resolve(process.argv[2] ?? path.join(repoDir, 'media', 'demo-chatgpt.mp4'));
|
|
24
|
+
// One frame per second suffices because durations are whole seconds and the slides do not animate.
|
|
25
|
+
const FRAME_RATE = 1;
|
|
26
|
+
|
|
27
|
+
// FUNCTIONS
|
|
28
|
+
|
|
29
|
+
// Reads the concat list into entries, each with a frame file name and, except for a final repeat, a duration in seconds.
|
|
30
|
+
const readEntries = (): {fileName: string; seconds?: number}[] => {
|
|
31
|
+
const entries: {fileName: string; seconds?: number}[] = [];
|
|
32
|
+
const lines = fs.readFileSync(listPath, 'utf8').split('\n').map(line => line.trim()).filter(line => line !== '');
|
|
33
|
+
for (const line of lines) {
|
|
34
|
+
const fileMatch = line.match(/^file '(.+)'$/);
|
|
35
|
+
const durationMatch = line.match(/^duration (\d+)$/);
|
|
36
|
+
const previous = entries.at(-1);
|
|
37
|
+
if (fileMatch) {
|
|
38
|
+
if (previous && previous.seconds === undefined) {
|
|
39
|
+
throw new Error(`No duration follows ${previous.fileName} in ${listPath}`);
|
|
40
|
+
}
|
|
41
|
+
entries.push({fileName: fileMatch[1] ?? ''});
|
|
42
|
+
}
|
|
43
|
+
else if (durationMatch && previous && previous.seconds === undefined) {
|
|
44
|
+
previous.seconds = Number(durationMatch[1]);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
throw new Error(`Unexpected line in ${listPath}: ${line}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return entries;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Returns the slides to show, each with an absolute frame path and a duration. The final file may repeat without a duration, as the concat format requires, and must then repeat the file before it.
|
|
54
|
+
const readSlides = (): {framePath: string; seconds: number}[] => {
|
|
55
|
+
const entries = readEntries();
|
|
56
|
+
const last = entries.at(-1);
|
|
57
|
+
const timed = last?.seconds === undefined ? entries.slice(0, -1) : entries;
|
|
58
|
+
if (last?.seconds === undefined && last?.fileName !== timed.at(-1)?.fileName) {
|
|
59
|
+
throw new Error(`The final file without a duration must repeat the file before it in ${listPath}`);
|
|
60
|
+
}
|
|
61
|
+
const slides = timed.map(entry => ({
|
|
62
|
+
framePath: path.resolve(sourceDir, entry.fileName),
|
|
63
|
+
seconds: entry.seconds ?? 0
|
|
64
|
+
}));
|
|
65
|
+
if (!slides.length) {
|
|
66
|
+
throw new Error(`No slides found in ${listPath}`);
|
|
67
|
+
}
|
|
68
|
+
const missing = slides.filter(slide => !fs.existsSync(slide.framePath)).map(slide => slide.framePath);
|
|
69
|
+
if (missing.length) {
|
|
70
|
+
throw new Error(`Missing frame files:\n${missing.join('\n')}`);
|
|
71
|
+
}
|
|
72
|
+
return slides;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Serializes slides as an ffmpeg concat list. The final frame is listed again without a duration, which ffmpeg needs to show it for its full duration.
|
|
76
|
+
const getConcatList = (slides: {framePath: string; seconds: number}[]): string => {
|
|
77
|
+
const lines = slides.flatMap(slide => [
|
|
78
|
+
`file '${slide.framePath.replaceAll("'", "'\\''")}'`,
|
|
79
|
+
`duration ${slide.seconds}`
|
|
80
|
+
]);
|
|
81
|
+
const last = slides.at(-1);
|
|
82
|
+
if (last) {
|
|
83
|
+
lines.push(`file '${last.framePath.replaceAll("'", "'\\''")}'`);
|
|
84
|
+
}
|
|
85
|
+
return `${lines.join('\n')}\n`;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Runs ffmpeg with the given arguments and throws on failure.
|
|
89
|
+
const runFfmpeg = (args: string[]): void => {
|
|
90
|
+
const result = spawnSync('ffmpeg', ['-v', 'error', '-y', ...args], {encoding: 'utf8'});
|
|
91
|
+
if (result.error) {
|
|
92
|
+
throw new Error('ffmpeg is not available. Install it (for example, with brew install ffmpeg) and try again.');
|
|
93
|
+
}
|
|
94
|
+
if (result.status !== 0) {
|
|
95
|
+
throw new Error(`ffmpeg failed: ${result.stderr.trim()}`);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// OPERATION
|
|
100
|
+
|
|
101
|
+
const slides = readSlides();
|
|
102
|
+
const totalSeconds = slides.reduce((sum, slide) => sum + slide.seconds, 0);
|
|
103
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'demo-chatgpt-'));
|
|
104
|
+
try {
|
|
105
|
+
const concatPath = path.join(tempDir, 'concat.txt');
|
|
106
|
+
fs.writeFileSync(concatPath, getConcatList(slides));
|
|
107
|
+
runFfmpeg([
|
|
108
|
+
'-f', 'concat',
|
|
109
|
+
'-safe', '0',
|
|
110
|
+
'-i', concatPath,
|
|
111
|
+
'-vf', `fps=${FRAME_RATE},format=yuv420p`,
|
|
112
|
+
'-t', String(totalSeconds),
|
|
113
|
+
'-c:v', 'libx264',
|
|
114
|
+
'-movflags', '+faststart',
|
|
115
|
+
outputPath
|
|
116
|
+
]);
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
fs.rmSync(tempDir, {recursive: true, force: true});
|
|
120
|
+
}
|
|
121
|
+
console.log(`Wrote ${outputPath}: ${slides.length} slides, ${totalSeconds} seconds`);
|
package/server.json
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
_N05CxIKKypvOFgBLNYjPw_BP5DHgMVPVv1mBFKU2lk
|