@jkudish/jev-browser 0.1.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ Initial release, published to npm as `@jkudish/jev-browser` (the unscoped `jev-browser` name belongs to another project).
6
+
7
+ - `jev_navigate` MCP tool: task plus start URL in, final page plus step trace, captured console and network errors, usage and estimated cost, and a final screenshot out.
8
+ - One primary Jev call per step: an action Choice over up to 240 page elements plus scroll, back, and done; a goal judgment; a stuck judgment. A select action adds one second-stage Choice for its option.
9
+ - Stop gates run before action execution: agent `done`, goal probability, stuck probability, step budget (24), time budget (180 s), caller cancellation.
10
+ - Typing cascade through the Vercel AI SDK: OpenAI, OpenRouter, Anthropic, Google, or any OpenAI-compatible endpoint, with a keyword fallback that is labeled in the trace.
11
+ - CLI (`jev-browser run`) and library import (`dist/navigate.js`) alongside the MCP server.
12
+ - Page payload formats: text, markdown, html, aria snapshot, with per-format caps and truncation flags.
13
+
14
+ No versioning policy has been declared yet; treat 0.x APIs as unstable.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Joey Kudish
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 ADDED
@@ -0,0 +1,206 @@
1
+ # Jev Browser
2
+
3
+ [![CI](https://github.com/jkudish/jev-browser/actions/workflows/ci.yml/badge.svg)](https://github.com/jkudish/jev-browser/actions/workflows/ci.yml)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
5
+
6
+ Fast and very cheap browser use using TypeSafe's Jev model.
7
+
8
+ Give jev-browser a task and a URL.
9
+
10
+ It drives a real headless browser through an MCP server, CLI, or library. TypeSafe's Jev model picks one action per step from the page's clickable, typeable, and selectable elements, and scores how likely it is that the goal is met or the run is stuck. Code owns the loop: budgets, recovery, stop gates. You get the final page, a step trace with confidences, console errors, and a screenshot.
11
+
12
+ Things it has done on real sites, not demos:
13
+
14
+ - Navigated Wikipedia from the Coffee article to Espresso in about 4 seconds, for $0.0016.
15
+ - Filled a contact form and stopped without submitting it.
16
+ - Pulled the price off a live pricing page.
17
+ - Returned a full guide page as markdown.
18
+ - Produced an accessibility-tree breakdown of a WordPress site.
19
+
20
+ This is early software. Expect rough edges on harder sites. Issues and pull requests are welcome; see [CONTRIBUTING.md](CONTRIBUTING.md).
21
+
22
+ ## Install
23
+
24
+ Requires Node.js 20 or newer, a TypeSafe API key from [console.typesafe.ai/settings/keys](https://console.typesafe.ai/settings/keys), and optionally a key for a typing provider (see [the typing model](#the-typing-model)). Playwright's Chromium downloads automatically on install; set `JEV_BROWSER_SKIP_BROWSER_DOWNLOAD=1` to opt out.
25
+
26
+ ### Let an agent install it for you
27
+
28
+ Paste this into your coding agent:
29
+
30
+ ```text
31
+ Install the Jev Browser MCP server for me. The package is @jkudish/jev-browser on npm and the server
32
+ command is `npx -y @jkudish/jev-browser`; register it as an MCP server with your client. Check whether
33
+ TYPESAFE_API_KEY is already set in the server environment; if not, walk me through setting it up without
34
+ pasting the key into the chat (I can create one at console.typesafe.ai/settings/keys). When it's
35
+ registered, ask if I'd like to run a first navigation task, and when we do, show me the step trace and cost.
36
+ Full instructions: https://github.com/jkudish/jev-browser#readme
37
+ ```
38
+
39
+ From npm:
40
+
41
+ ```bash
42
+ npx -y @jkudish/jev-browser --help
43
+ ```
44
+
45
+ ### Amp
46
+
47
+ ```bash
48
+ amp mcp add jev-browser -- npx -y @jkudish/jev-browser
49
+ ```
50
+
51
+ ### Claude Code
52
+
53
+ ```bash
54
+ claude mcp add jev-browser -- npx -y @jkudish/jev-browser
55
+ ```
56
+
57
+ ### Codex (`~/.codex/config.toml`)
58
+
59
+ ```toml
60
+ [mcp_servers.jev-browser]
61
+ command = "npx"
62
+ args = ["-y", "@jkudish/jev-browser"]
63
+ ```
64
+
65
+ ### OpenCode (`opencode.json`)
66
+
67
+ ```json
68
+ {
69
+ "mcp": {
70
+ "jev-browser": {
71
+ "type": "local",
72
+ "command": ["npx", "-y", "@jkudish/jev-browser"],
73
+ "environment": { "TYPESAFE_API_KEY": "ts_..." }
74
+ }
75
+ }
76
+ }
77
+ ```
78
+
79
+ ### Any other MCP client
80
+
81
+ ```json
82
+ {
83
+ "mcpServers": {
84
+ "jev-browser": {
85
+ "command": "npx",
86
+ "args": ["-y", "@jkudish/jev-browser"],
87
+ "env": { "TYPESAFE_API_KEY": "ts_..." }
88
+ }
89
+ }
90
+ }
91
+ ```
92
+
93
+ Some MCP clients filter the environment before spawning servers, which silently drops `TYPESAFE_API_KEY`. If the server reports a missing key, pass it explicitly as shown above.
94
+
95
+ ## The tool
96
+
97
+ Every run makes paid TypeSafe API calls, typically a fraction of a cent, plus one small LLM call per typed field when a typing provider is configured. The example below is a real run.
98
+
99
+ ```jsonc
100
+ // arguments
101
+ {
102
+ "task": "Search Wikipedia for the espresso-based drink called Ristretto and stop when you are on that article",
103
+ "start_url": "https://en.wikipedia.org/wiki/Main_Page"
104
+ }
105
+ ```
106
+
107
+ ```jsonc
108
+ // live result, abridged
109
+ {
110
+ "status": "done",
111
+ "final_url": "https://en.wikipedia.org/wiki/Ristretto",
112
+ "elapsed_ms": 5894,
113
+ "steps": [
114
+ { "step": 1, "proposed_action": "click_e2", "executed_action": "click_e2", "detail": "a \"Search Wikipedia [f]\" -> /wiki/Special:Search", "confidence": 1.0 },
115
+ { "step": 2, "proposed_action": "type_e1", "executed_action": "type_e1", "detail": "typed \"Ristretto\" via openrouter", "confidence": 0.99 },
116
+ { "step": 3, "proposed_action": "done", "executed_action": null, "detail": "done proposed; not executed", "confidence": 1.0 }
117
+ ],
118
+ "usage": { "jev_calls": 3, "input_tokens": 45810, "est_cost_usd": 0.0021 }
119
+ }
120
+ ```
121
+
122
+ Parameters: `max_steps` (default 24), `max_seconds` (default 180), `allow_typing` (default true), `format` (`text`, `markdown`, `html`, `aria`), `max_chars` (override the cap), `screenshot` (`final`, default, or `none`).
123
+
124
+ ## What you get back
125
+
126
+ **The final page, in the format you ask for.** Every payload reports `truncated` and `true_length`, and `max_chars` overrides any default.
127
+
128
+ | Format | Default cap | Best for |
129
+ | --- | --- | --- |
130
+ | `text` | 8,000 chars | Feeding the page to Jev or an LLM next; quick reads |
131
+ | `markdown` | 16,000 chars | Readable artifacts and notes; carries navigation chrome |
132
+ | `html` | 1 MB | Parsing the page yourself with your own selectors |
133
+ | `aria` | 16,000 chars | The accessibility tree as YAML; what screen readers and agents see |
134
+
135
+ **A final screenshot.** A viewport JPEG that renders inline in MCP clients, or lands as a file with the CLI's `--screenshot path.jpg`. Pass `screenshot: "none"` to skip it.
136
+
137
+ **A debug trace you can audit.** One record per step: the proposed action versus the action actually executed, why a recovery fired, action errors, the Choice confidence, the top option's probability, and the goal and stuck probabilities for that step. Stop statuses say which gate fired. Alongside the trace: console errors, page errors, and failed network requests captured per step and tagged with the page they came from, up to 200 events, plus Jev call counts, token usage, and estimated cost. If the final payload or screenshot could not be extracted, the run still returns and lists the problem under `extraction_problems`.
138
+
139
+ ## How it decides
140
+
141
+ Each step makes one primary Jev call with three questions over the same state: an action Choice over the page's interactive elements plus scroll/back/done, a goal Noul, and a stuck Noul ([fan-out pattern](https://docs.typesafe.ai/patterns/fan-out.md)). The state includes a short excerpt of the page's visible text, so the goal judgment can see content, not just URLs and links. A select action adds one second-stage Choice for its option. Elements come from the DOM directly, not the accessibility tree, because accessibility trees under-report inputs; the agent found DuckDuckGo's search box only after this switch. Actions: click, type, select a native dropdown, scroll, back, done.
142
+
143
+ Stop conditions, in code, checked before executing the step's proposed action: the agent chooses `done`, goal probability > 0.85, stuck probability > 0.85, the step budget, or the time budget. A repeated action with no effect switches to the next-best option from the Choice distribution. There is deliberately no low-confidence override: split probability across several similar elements is usually several acceptable alternatives, not uncertainty.
144
+
145
+ Statuses: `done` (agent chose to stop), `goal_achieved` (the goal watcher fired), `stuck`, `max_steps`, `timeout`, `error`. `done` and `goal_achieved` are two independent judgments; agreement between them is what a trustworthy finish looks like, and the trace shows both at every step.
146
+
147
+ ## The typing model
148
+
149
+ Jev never generates text. It returns typed decisions only: which option, with what probabilities. So when a task needs a string, typing a search query or filling a field, that string comes from a small model you choose. This is the only place a second model is involved, and it runs at most once or twice per task, about 48 tokens per call.
150
+
151
+ Configuration is automatic when possible. The server picks the first provider whose key it recognizes, in this order:
152
+
153
+ | Provider | Recognized by | Default model |
154
+ | --- | --- | --- |
155
+ | OpenAI | `OPENAI_API_KEY` starting with `sk-` | `gpt-5.6-luna` |
156
+ | OpenRouter | `OPENROUTER_API_KEY` starting with `sk-or-` | `openai/gpt-5.6-luna` |
157
+ | Anthropic | `ANTHROPIC_API_KEY` starting with `sk-ant-` | `claude-haiku-4.5` |
158
+ | Google | `GEMINI_API_KEY` or `GOOGLE_GENERATIVE_AI_API_KEY` starting with `AIza` | `gemini-2.5-flash` |
159
+
160
+ Overrides:
161
+
162
+ - `JEV_BROWSER_TYPE_MODEL` picks any model the resolved provider offers, for example `openrouter:anthropic/claude-haiku-4.5` style ids.
163
+ - `JEV_BROWSER_TYPE_PROVIDER` forces one of `openai`, `openrouter`, `anthropic`, `google`, skipping auto-detection.
164
+ - `JEV_BROWSER_TYPE_BASE_URL` (plus `JEV_BROWSER_TYPE_API_KEY` if it needs one) points at any OpenAI-compatible endpoint: Ollama, LM Studio, vLLM, a gateway. This wins over provider detection.
165
+
166
+ Local example, no cloud key at all:
167
+
168
+ ```bash
169
+ JEV_BROWSER_TYPE_BASE_URL=http://localhost:11434/v1 JEV_BROWSER_TYPE_MODEL=qwen2.5:7b \
170
+ npx -y @jkudish/jev-browser run "Search Wikipedia for Ristretto and stop on the article" https://en.wikipedia.org/wiki/Main_Page
171
+ ```
172
+
173
+ With no provider at all, typing falls back to a keyword heuristic built from the task text. It is labeled honestly in the trace (`via keyword-heuristic`), and it is meaningfully worse: in testing its queries buried a target article eight results pages deep. Give it a real model if your tasks type anything.
174
+
175
+ ## Limits
176
+
177
+ - Up to 240 elements per step; Jev's Choice supports 255 options. Beyond that the list is truncated and the state says so, which can hide the needed element on very dense pages.
178
+ - The markdown format converts the whole body, so it carries navigation chrome and can include inline script text; a readability pass is a candidate improvement, not a committed one.
179
+ - Password and file inputs are never offered. Hover-revealed menus, keyboard actions (Escape, Enter on unstaged fields), multi-field form sequencing, shadow DOM, and iframes are out of scope for v0.1.
180
+ - Thresholds (0.85 goal, 0.85 stuck, budgets) are starting points measured on Wikipedia and DuckDuckGo tasks. Tune them for your sites.
181
+ - Jev is calibrated, not infallible. Treat the trace as evidence, not proof.
182
+
183
+ ## Configuration
184
+
185
+ | Env var | Default | Purpose |
186
+ | --- | --- | --- |
187
+ | `TYPESAFE_API_KEY` | none | Required. |
188
+ | `JEV_BROWSER_MODEL` | `jev-latest` | Pin a Jev version. |
189
+ | `JEV_BROWSER_TYPE_*` | see above | Typing provider, model, and endpoint. |
190
+ | `JEV_BROWSER_HEADED` | unset | Set to `1` to watch the browser. |
191
+ | `JEV_BROWSER_SKIP_BROWSER_DOWNLOAD` | unset | Set to `1` to skip the Chromium postinstall. |
192
+
193
+ ## Development
194
+
195
+ ```bash
196
+ npm install
197
+ npm run build
198
+ npm test # unit tests, offline
199
+ npm run test:e2e # live navigation tests; requires TYPESAFE_API_KEY
200
+ ```
201
+
202
+ See [CONTRIBUTING.md](CONTRIBUTING.md). To report a vulnerability, see [SECURITY.md](SECURITY.md).
203
+
204
+ ## License
205
+
206
+ [MIT](LICENSE)
package/SECURITY.md ADDED
@@ -0,0 +1,17 @@
1
+ # Security policy
2
+
3
+ ## Reporting a vulnerability
4
+
5
+ Email **joey@jkudish.com** with "jev-browser security" in the subject. Include:
6
+
7
+ - the package version and how you installed it;
8
+ - a minimal reproduction (task, start URL, environment);
9
+ - the impact you observed or expect.
10
+
11
+ Please do not open public issues for vulnerabilities. There is no bug bounty and no committed response time; reports are handled as maintainer time allows.
12
+
13
+ ## Scope
14
+
15
+ jev-browser runs a headless browser and makes network calls to the TypeSafe API and, when configured, one typing provider. It navigates to URLs you supply and can follow links from those pages. Treat the service environment it runs in as reachable by the pages it visits: run it in a container or restricted network if your environment has private endpoints you do not want touched.
16
+
17
+ Only the latest released version receives fixes. There is no support policy for older versions yet.
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function runCli(argv: string[]): Promise<number>;
package/dist/cli.js ADDED
@@ -0,0 +1,78 @@
1
+ // CLI: `jev-browser run "<task>" <start-url> [options]`
2
+ // Everything else (no args) starts the MCP stdio server (src/index.ts).
3
+ import { mkdir, writeFile } from "node:fs/promises";
4
+ import { dirname } from "node:path";
5
+ import { navigate } from "./navigate.js";
6
+ function parseArgs(argv) {
7
+ const args = { task: undefined, startUrl: undefined };
8
+ const positional = [];
9
+ for (let i = 0; i < argv.length; i++) {
10
+ const arg = argv[i];
11
+ switch (arg) {
12
+ case "--format":
13
+ args.format = argv[++i];
14
+ break;
15
+ case "--max-chars":
16
+ args.maxChars = Number(argv[++i]);
17
+ break;
18
+ case "--max-steps":
19
+ args.maxSteps = Number(argv[++i]);
20
+ break;
21
+ case "--max-seconds":
22
+ args.maxSeconds = Number(argv[++i]);
23
+ break;
24
+ case "--no-typing":
25
+ args.allowTyping = false;
26
+ break;
27
+ case "--screenshot":
28
+ args.screenshotPath = argv[++i];
29
+ break;
30
+ case "--no-screenshot":
31
+ args.screenshot = "none";
32
+ break;
33
+ case "--help":
34
+ case "-h":
35
+ args.help = true;
36
+ break;
37
+ default:
38
+ positional.push(arg);
39
+ }
40
+ }
41
+ const [task, startUrl] = positional;
42
+ return { ...args, task, startUrl };
43
+ }
44
+ const HELP = `jev-browser run "<task>" <start-url> [options]
45
+
46
+ Options:
47
+ --format <text|markdown|html|aria> Final page payload (default text)
48
+ --max-chars <n> Override the format's character cap
49
+ --max-steps <n> Hard step cap (default 24)
50
+ --max-seconds <n> Wall-clock cap (default 180)
51
+ --no-typing Disable typing into fields
52
+ --screenshot <path> Write the final JPEG to this path
53
+ --no-screenshot Skip the screenshot entirely
54
+ -h, --help Show this help
55
+
56
+ Result JSON is printed to stdout. Environment: TYPESAFE_API_KEY required;
57
+ JEV_BROWSER_* vars configure models and the typing provider.
58
+
59
+ Without "run", this binary starts the MCP stdio server.`;
60
+ export async function runCli(argv) {
61
+ const args = parseArgs(argv);
62
+ if (args.help || !args.task || !args.startUrl) {
63
+ console.log(HELP);
64
+ return args.help ? 0 : 1;
65
+ }
66
+ const { screenshotPath, ...navigateArgs } = args;
67
+ const result = (await navigate({ ...navigateArgs, screenshot: screenshotPath ? "final" : (args.screenshot ?? "final") }));
68
+ if (screenshotPath && result.screenshot_base64_jpeg) {
69
+ await mkdir(dirname(screenshotPath), { recursive: true });
70
+ await writeFile(screenshotPath, Buffer.from(result.screenshot_base64_jpeg, "base64"));
71
+ result.screenshot_path = screenshotPath;
72
+ }
73
+ // The CLI prints JSON; base64 screenshots belong in files, not terminals.
74
+ delete result.screenshot_base64_jpeg;
75
+ console.log(JSON.stringify(result, null, 2));
76
+ return result.status === "error" ? 1 : 0;
77
+ }
78
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ // jev-browser: a Jev-driven browser agent.
3
+ // jev-browser run "<task>" <url> [options] CLI
4
+ // jev-browser MCP stdio server
5
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
+ import { z } from "zod";
8
+ import { navigate } from "./navigate.js";
9
+ import { runCli } from "./cli.js";
10
+ if (process.argv[2] === "run") {
11
+ process.exit(await runCli(process.argv.slice(3)));
12
+ }
13
+ if (process.argv[2] === "--help" || process.argv[2] === "-h") {
14
+ process.exit(await runCli(["--help"]));
15
+ }
16
+ const server = new McpServer({ name: "jev-browser", version: "0.1.0" });
17
+ server.registerTool("jev_navigate", {
18
+ title: "Navigate a browser with Jev",
19
+ description: "Give a task and a start URL; a Jev-driven agent navigates a real headless browser until the goal is met, " +
20
+ "the stuck gate fires, or a budget (steps/seconds) is exhausted. Returns the final page in a chosen format " +
21
+ "(text, markdown, html, or an aria snapshot), the full step trace with confidences, console/page/network " +
22
+ "errors captured along the way, token usage with estimated cost, and a final screenshot.",
23
+ inputSchema: {
24
+ task: z.string().min(1).describe("What the agent should accomplish, in natural language."),
25
+ start_url: z
26
+ .string()
27
+ .url()
28
+ .refine((v) => /^https?:\/\//.test(v), "start_url must be an http(s) URL")
29
+ .describe("Where to start."),
30
+ max_steps: z.number().int().min(1).max(100).optional().describe("Hard step cap. Default 24."),
31
+ max_seconds: z.number().min(10).max(600).optional().describe("Wall-clock cap in seconds. Default 180."),
32
+ allow_typing: z
33
+ .boolean()
34
+ .optional()
35
+ .describe("Whether the agent may type into fields (uses the configured small model, or a keyword fallback). Default true."),
36
+ format: z
37
+ .enum(["text", "markdown", "html", "aria"])
38
+ .optional()
39
+ .describe("Final page payload format: text (default, 8k chars), markdown (16k, via turndown), " +
40
+ "html (1MB, for app-side parsing), aria (16k, Playwright aria snapshot YAML)."),
41
+ max_chars: z.number().int().min(100).optional().describe("Override the format's default character cap."),
42
+ screenshot: z.enum(["final", "none"]).optional().describe("Final viewport JPEG. Default 'final'."),
43
+ },
44
+ }, async ({ task, start_url, ...rest }, extra) => {
45
+ const result = await navigate({
46
+ task,
47
+ startUrl: start_url,
48
+ maxSteps: rest.max_steps,
49
+ maxSeconds: rest.max_seconds,
50
+ allowTyping: rest.allow_typing,
51
+ format: rest.format,
52
+ maxChars: rest.max_chars,
53
+ screenshot: rest.screenshot,
54
+ }, extra.signal);
55
+ const { screenshot_base64_jpeg, ...json } = result;
56
+ const content = [
57
+ { type: "text", text: JSON.stringify(json, null, 2) },
58
+ ];
59
+ if (typeof screenshot_base64_jpeg === "string") {
60
+ content.push({ type: "image", data: screenshot_base64_jpeg, mimeType: "image/jpeg" });
61
+ }
62
+ return { content, isError: json.status === "error" };
63
+ });
64
+ await server.connect(new StdioServerTransport());
65
+ console.error(`[jev-browser] ready — Jev model ${process.env.JEV_BROWSER_MODEL ?? "jev-latest"}`);
66
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/lib.d.ts ADDED
@@ -0,0 +1,39 @@
1
+ /** Max elements offered to Jev per step. TypeSafe Choice supports up to 255 options. */
2
+ export declare const MAX_ELEMENTS = 240;
3
+ /** Raw interactive candidate as extracted from the page (already stamped with an attr). */
4
+ export interface RawElement {
5
+ attr: string;
6
+ tag: string;
7
+ role: string;
8
+ text: string;
9
+ href: string;
10
+ typeAttr: string;
11
+ clickable: boolean;
12
+ typeable: boolean;
13
+ selectable?: boolean;
14
+ options?: string[];
15
+ }
16
+ /** Pruned action-space element. */
17
+ export interface PageElement {
18
+ id: string;
19
+ attr: string;
20
+ kind: "click" | "type" | "select";
21
+ description: string;
22
+ options?: string[];
23
+ }
24
+ export declare function isNoiseName(name: string): boolean;
25
+ export declare function isNoiseHref(href: string): boolean;
26
+ /** Filter, dedupe by destination, cap, and describe the action space for one step. */
27
+ export declare function buildActionSpace(raw: RawElement[]): {
28
+ elements: PageElement[];
29
+ truncated: boolean;
30
+ };
31
+ /** Jev Choice criteria for one step: element actions plus loop controls. */
32
+ export declare function buildCriteria(elements: PageElement[]): Record<string, string>;
33
+ export declare function selectorFor(el: PageElement): string;
34
+ /** Next-best action from a Choice distribution, excluding known-bad options. */
35
+ export declare function pickAlternate(probabilities: Record<string, number> | undefined, exclude: Set<string>): string | null;
36
+ /** Deterministic typing fallback when no small-LLM key is available. */
37
+ export declare function heuristicQuery(task: string): string;
38
+ /** jev-1.12 published pricing: input $0.042 per M tokens, output free. */
39
+ export declare const PRICE_PER_MTOK_IN = 0.042;
package/dist/lib.js ADDED
@@ -0,0 +1,117 @@
1
+ // Pure helpers — no browser, no API, fully unit-testable.
2
+ /** Max elements offered to Jev per step. TypeSafe Choice supports up to 255 options. */
3
+ export const MAX_ELEMENTS = 240;
4
+ const JUNK_NAMES = new Set([
5
+ "jump up", "jump up to", "jump up to:", "jump to content", "edit", "permalink",
6
+ "permanent link", "cite this page", "donate", "create account", "log in", "talk",
7
+ "contributions", "view history", "read", "source", "hide", "show", "skip to content",
8
+ ]);
9
+ export function isNoiseName(name) {
10
+ const lowered = name.trim().toLowerCase();
11
+ if (lowered.length === 0 || lowered.length > 80)
12
+ return true;
13
+ if (JUNK_NAMES.has(lowered))
14
+ return true;
15
+ if (/^[\d\s.,:;()[\]-]+$/.test(lowered))
16
+ return true; // citation numbers, lone brackets
17
+ return false;
18
+ }
19
+ export function isNoiseHref(href) {
20
+ if (!href)
21
+ return false; // buttons and inputs legitimately have no href
22
+ if (href.startsWith("#"))
23
+ return true;
24
+ if (href.startsWith("javascript:"))
25
+ return true;
26
+ if (href.startsWith("mailto:") || href.startsWith("tel:"))
27
+ return true;
28
+ if (href.includes("action=edit"))
29
+ return true;
30
+ return false;
31
+ }
32
+ /** Filter, dedupe by destination, cap, and describe the action space for one step. */
33
+ export function buildActionSpace(raw) {
34
+ const seenHrefs = new Set();
35
+ const elements = [];
36
+ for (const el of raw) {
37
+ if (elements.length >= MAX_ELEMENTS)
38
+ break;
39
+ if (isNoiseName(el.text))
40
+ continue;
41
+ if (isNoiseHref(el.href))
42
+ continue;
43
+ // Never offer to type into password or file inputs.
44
+ if (el.typeable && (el.typeAttr === "password" || el.typeAttr === "file"))
45
+ continue;
46
+ if (el.href) {
47
+ const key = el.href.split("#")[0];
48
+ if (seenHrefs.has(key))
49
+ continue;
50
+ seenHrefs.add(key);
51
+ }
52
+ if (!el.clickable && !el.typeable && !el.selectable)
53
+ continue;
54
+ const id = `e${elements.length + 1}`;
55
+ const label = el.text.slice(0, 60);
56
+ const kind = el.typeable ? "type" : el.selectable ? "select" : "click";
57
+ const hrefTail = el.href ? ` -> ${el.href.replace(/^https?:\/\//, "").slice(0, 70)}` : "";
58
+ elements.push({
59
+ id,
60
+ attr: el.attr,
61
+ kind,
62
+ description: kind === "type"
63
+ ? `${el.tag} "${label}" (type into this field)`
64
+ : kind === "select"
65
+ ? `${el.tag} "${label}" (dropdown; a follow-up picks the option)`
66
+ : `${el.tag} "${label}"${hrefTail}`,
67
+ options: kind === "select" ? (el.options ?? []) : undefined,
68
+ });
69
+ }
70
+ return { elements, truncated: elements.length >= MAX_ELEMENTS };
71
+ }
72
+ /** Jev Choice criteria for one step: element actions plus loop controls. */
73
+ export function buildCriteria(elements) {
74
+ const criteria = {};
75
+ for (const el of elements) {
76
+ criteria[`${el.kind}_${el.id}`] = el.description;
77
+ }
78
+ criteria["scroll_down"] = "Scroll down one screen to reveal more of the page";
79
+ criteria["scroll_up"] = "Scroll up one screen";
80
+ criteria["back"] = "Go back to the previous page; this branch is wrong";
81
+ criteria["done"] = "The task is already complete; stop here";
82
+ return criteria;
83
+ }
84
+ export function selectorFor(el) {
85
+ return `[data-jev-id="${el.attr}"]`;
86
+ }
87
+ /** Next-best action from a Choice distribution, excluding known-bad options. */
88
+ export function pickAlternate(probabilities, exclude) {
89
+ const ranked = Object.entries(probabilities ?? {}).sort((a, b) => b[1] - a[1]);
90
+ for (const [option, p] of ranked) {
91
+ if (option === "back")
92
+ continue;
93
+ if (exclude.has(option))
94
+ continue;
95
+ if (p <= 0)
96
+ continue;
97
+ return option;
98
+ }
99
+ return null;
100
+ }
101
+ const STOP_WORDS = new Set([
102
+ "search", "wikipedia", "the", "a", "an", "article", "about", "for", "find", "stop", "when",
103
+ "you", "are", "on", "it", "and", "to", "of", "called", "page", "site", "website", "navigate",
104
+ "go", "open", "that", "this",
105
+ ]);
106
+ /** Deterministic typing fallback when no small-LLM key is available. */
107
+ export function heuristicQuery(task) {
108
+ const words = task
109
+ .toLowerCase()
110
+ .replace(/[^a-z0-9\s-]/g, " ")
111
+ .split(/\s+/)
112
+ .filter((w) => w && !STOP_WORDS.has(w));
113
+ return words.slice(0, 6).join(" ");
114
+ }
115
+ /** jev-1.12 published pricing: input $0.042 per M tokens, output free. */
116
+ export const PRICE_PER_MTOK_IN = 0.042;
117
+ //# sourceMappingURL=lib.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lib.js","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"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"}