kelra 0.1.1 → 0.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/AGENT_GUIDE.md +4 -3
- package/dist/index.js +5 -4
- package/package.json +1 -1
package/AGENT_GUIDE.md
CHANGED
|
@@ -8,9 +8,10 @@ kelra measures and compares iOS UI on Xcode simulators. Use it when the user say
|
|
|
8
8
|
1. Get the reference as a file: a path the user gives, `latest_pasted_image` if they pasted a screenshot into the chat, or `clipboard_image` if they copied one.
|
|
9
9
|
2. `analyze_reference` for a measured spec (its `textLines` have the words it read, with estimated sizes and weights; do not read words or guess sizes by eye).
|
|
10
10
|
3. Build the SwiftUI screen from the spec. Install and launch it on the simulator the user has running. `set_status_bar` makes the status bar match the reference.
|
|
11
|
-
4. `compare` the reference against the device.
|
|
12
|
-
5.
|
|
13
|
-
6.
|
|
11
|
+
4. `compare` the reference against the device. Read `summary` and `scores.match` (text and layout matter; the pixel score flatters flat backgrounds), then follow `status`: while `critical` items exist you are not done. Work through `fixes` from the top: a whole-screen shift, a vertical spacing factor, one text scale or typeface fix for all lines, groups of identical elements, then single items. Apply one round, rebuild, and run `compare` again with `summary=true`. Do not apply per-line numbers that a global fix above them already explains. `compare` and `inspect_app` wait for the screen to stop changing before they capture it, so a launch animation is not mistaken for your build; a blank capture is reported as critical, so retake it. The status bar is found from the clock and left out, a watermark strip under the reference is cut off, and the part of the render a cropped reference does not reach is not compared (the warning says so; it is not a layout error).
|
|
12
|
+
5. Low-resolution references (a 299px Mobbin preview, a thumbnail): `analyze_reference` warns and suggests the point scale; sizes are then good to about ±10-15%, colours and weights are unreliable, and moves under 3pt are noise. Prefer a 2x or 3x screenshot when one exists.
|
|
13
|
+
6. `extract_assets` crops photos out of a reference for local prototyping; pass `referenceTrimBottomPx` for watermark strips and `ignoreTopPt` / `ignoreBottomPt` for areas that must not be compared (live content, glass).
|
|
14
|
+
7. If the app runs the KelraInspector package, `inspect_app` gives its real frames, fills, radii and font sizes, and `compare` then says the size to set.
|
|
14
15
|
|
|
15
16
|
Never change a number by guess: measure it first.
|
|
16
17
|
|
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ import { callService } from "./service.js";
|
|
|
19
19
|
import { guide } from "./guide.js";
|
|
20
20
|
// The guide for AI agents (usage and how to handle the helper) is sent to the model whenever this server is connected.
|
|
21
21
|
const instructions = guide;
|
|
22
|
-
const server = new McpServer({ name: "kelra", version: "0.1.
|
|
22
|
+
const server = new McpServer({ name: "kelra", version: "0.1.2" }, { instructions });
|
|
23
23
|
// the same guide, readable on demand
|
|
24
24
|
server.resource("kelra-guide", "kelra://guide", { mimeType: "text/markdown", description: "How to use kelra and how to handle the helper and the license" }, async (uri) => ({ contents: [{ uri: uri.href, mimeType: "text/markdown", text: guide }] }));
|
|
25
25
|
const device = z.string().describe("Device name, UDID or ECID from list_devices");
|
|
@@ -195,7 +195,7 @@ server.tool("read_text", "Read the text in a screenshot with Apple Vision (OCR):
|
|
|
195
195
|
throw new Error("Reading text needs a Mac with Xcode installed (Apple Vision).");
|
|
196
196
|
return text(await callService("/v1/read-text", { image: b64(png), pointScale, minConfidence, includeIcons, ocr }));
|
|
197
197
|
}));
|
|
198
|
-
server.tool("compare", "Compare a reference screenshot against the current render and
|
|
198
|
+
server.tool("compare", "Compare a reference screenshot against the current render. Returns `summary` and `scores` (match, text, layout, pixels: watch `match`, not the pixel score), `status` (done or not, and the next step: follow it), `fixes` (what to change, in order: whole-screen shift, vertical spacing factor, one text scale or typeface fix for all lines, groups of identical elements, then single items), `critical` (text invisible in the render, a blank capture: fix these first), then the detail lists `textAdjustments`, `adjustments`, `missingInRender`, `extraInRender`, and a red diff overlay. The status bar is found from the clock and left out, a watermark strip under the reference is cut off, and the part of the render a cropped reference does not reach is not compared. Render comes from a live device screenshot, or renderPath for a saved PNG. Pass summary=true after the first round to get only scores, status, fixes and criticals.", {
|
|
199
199
|
referencePath: z.string().describe("Absolute path to the reference PNG"),
|
|
200
200
|
device: device.optional().describe("Device to capture as the render"),
|
|
201
201
|
renderPath: z.string().optional().describe("Saved PNG to use instead of a device capture"),
|
|
@@ -208,7 +208,8 @@ server.tool("compare", "Compare a reference screenshot against the current rende
|
|
|
208
208
|
ignoreRegions: z.array(z.object({ x: z.number(), y: z.number(), w: z.number(), h: z.number() })).default([]).describe("Rectangles in render points to leave out of the comparison (live content, glass effects, product photos)"),
|
|
209
209
|
inspectPort: z.number().int().optional().describe("Port of KelraInspector in the running app. With a device, port 8765 is tried automatically. Adds the app's current exact font size and the size to set to each text adjustment."),
|
|
210
210
|
align: z.enum(["auto", "stretch"]).default("auto").describe("auto: fit the reference to the render by scale and offset (handles crops, footers, other device sizes); stretch: force it to the render's exact size"),
|
|
211
|
-
|
|
211
|
+
summary: z.boolean().default(false).describe("Return only summary, scores, status, fixes, critical, warnings and alignment (about a fifth of the tokens). Use it for every round after the first."),
|
|
212
|
+
}, ({ referencePath, device, renderPath, pointScale, tolerance, align, inspectPort, referenceTrimTopPx, referenceTrimBottomPx, ignoreTopPt, ignoreBottomPt, ignoreRegions, summary }) => wrap(async () => {
|
|
212
213
|
if (!device && !renderPath)
|
|
213
214
|
throw new Error("Provide device or renderPath");
|
|
214
215
|
const renderPng = renderPath ? await readPng(renderPath, "render image") : await settledScreenshotPng(device);
|
|
@@ -222,7 +223,7 @@ server.tool("compare", "Compare a reference screenshot against the current rende
|
|
|
222
223
|
]);
|
|
223
224
|
const r = await callService("/v1/compare", {
|
|
224
225
|
reference: b64(refPng), render: b64(renderPng), pointScale, tolerance, ignoreTopPt, ignoreBottomPt, ignoreRegions, align,
|
|
225
|
-
refOcr: refOcr && renderOcr ? refOcr : null, refOcrPixelsPerPoint: refPpp, renderOcr: refOcr && renderOcr ? renderOcr : null, live,
|
|
226
|
+
refOcr: refOcr && renderOcr ? refOcr : null, refOcrPixelsPerPoint: refPpp, renderOcr: refOcr && renderOcr ? renderOcr : null, live, summary,
|
|
226
227
|
});
|
|
227
228
|
const shape = align === "stretch" ? null : shapeWarning(ref, render);
|
|
228
229
|
if (shape)
|