ego-jev 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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,25 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0 (2026-10-12)
4
+
5
+ - `--require "<page state>"` (repeatable): states the run has to reach, in order. Each round Jev scores them and works
6
+ on the first one not reached yet. With requirements, outcome-only goals went from 8/12 to 12/12 correct in the
7
+ experiments.
8
+ - Batch fill: when the next action types a value and more values have empty fields, all are typed in the same round.
9
+ The httpbin form dropped from 14.3 to 9.3 Jev requests.
10
+ - Requirement scores ride in the action request on pages that fit one stage (`--no-merge-require` turns it off).
11
+ - Click errors name the element that covers the target; blocked clicks retry after Escape.
12
+ - Elements that only open a popup or help text carry `opens`.
13
+ - Enter is pressed only on fields and buttons, otherwise on the field typed last.
14
+ - The done check accepts what an action leads to (the page after logging in) and rejects a filled but unsent form
15
+ for goals that order, send or submit.
16
+ - With requirements, a weak `none` next to a confident target becomes an action.
17
+ - Pages open at `domcontentloaded` instead of `load`.
18
+ - The CLI writes its own lines to stdout even when piped (the ego runtime sends `console.log` to stderr then).
19
+ - Results report `jev_tokens`.
20
+ - `experiments/`: task definitions, runner, summariser and 156 recorded runs.
21
+
22
+ ## 0.1.0 (2026-10-11)
23
+
24
+ - First release: Jev decision loop from jev-browser 0.1.2 on top of ego lite, `do`, `check`, `read`, done
25
+ verification, label-edge clicks, state-preserving page fitting, FIFO key hand-off.
package/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kim Jisub
4
+
5
+ Portions of src/ego-jev.mjs and the files in src/jev/ are derived from jev-browser 0.1.2,
6
+ Copyright (c) 2026 Ying-Kai Liao, also under the MIT License (see src/jev/LICENSE).
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ of this software and associated documentation files (the "Software"), to deal
10
+ in the Software without restriction, including without limitation the rights
11
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ copies of the Software, and to permit persons to whom the Software is
13
+ furnished to do so, subject to the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included in all
16
+ copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,187 @@
1
- # Temporary Holding Version
1
+ # ego-jev
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
+ Let **Jev** click through multi-step browser flows inside **ego lite**, your real browser, while your agent keeps the
4
+ goal and the values.
5
+
6
+ - [ego lite](https://github.com/citrolabs/ego-lite) runs a Chromium browser on your Mac with your logins, split into
7
+ isolated TaskSpaces that agents can share.
8
+ - **Jev** (TypeSafe System One) is a judgment model: it answers yes/no, multiple-choice and score questions over a
9
+ state object. **It never generates text.**
10
+ - **ego-jev** reads the page, asks Jev "what next, on which element, with which of the given values", and performs
11
+ the action through ego. Strings to type always come from the caller as `key=value`; Jev only decides where they go.
12
+
13
+ Your LLM agent stays in charge of what to do and why. ego-jev takes the clicking off its hands and keeps page
14
+ snapshots out of its context.
15
+
16
+ ## Requirements
17
+
18
+ - macOS with ego lite installed and onboarded (`ego-browser` on your PATH or in `~/.local/bin`)
19
+ - Node.js 20 or later
20
+ - A TypeSafe API key in `TYPESAFE_API_KEY`
21
+
22
+ No other dependencies: no Playwright, no bundled browser.
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ npm install -g github:kimjisub/ego-jev#v0.2.0
28
+ # or the release tarball
29
+ npm install -g https://github.com/kimjisub/ego-jev/releases/download/v0.2.0/ego-jev-0.2.0.tgz
30
+ # or from source
31
+ git clone https://github.com/kimjisub/ego-jev.git && cd ego-jev && npm link
32
+ ```
33
+
34
+ ## Quick start
35
+
36
+ ```bash
37
+ export TYPESAFE_API_KEY=...
38
+
39
+ ego-jev do "I want to buy the Sauce Labs Backpack and the Sauce Labs Bike Light" \
40
+ username=standard_user password=secret_sauce first=Ada last=Lovelace zip=12345 \
41
+ --url https://www.saucedemo.com/ --max 18 --allow-irreversible \
42
+ --require "Logged in to the store" \
43
+ --require "Sauce Labs Backpack and Sauce Labs Bike Light are both in the cart" \
44
+ --require "The checkout form has the given first name, last name and zip code" \
45
+ --require "The order is complete (a thank-you confirmation is shown)"
46
+ ```
47
+
48
+ One line per round goes to stdout, and the last line is the result:
49
+
50
+ ```text
51
+ r0: require=[0.04, 0.03, 0.03, 0.02]
52
+ r0: type(1) -> #0 input:text "Username" (0.99) value=username done=0.03 ...
53
+ also typed password -> #1 input:password "Password" (0.99)
54
+ ...
55
+ EGO_JEV_RESULT {"status":"likely_done","url":".../checkout-complete.html","actions":[...],"requirements":[...],"jev_calls":15,"space":12,"page":"p1"}
56
+ ```
57
+
58
+ ## Usage
59
+
60
+ ```bash
61
+ ego-jev do "<goal>" [key=value ...] [--require "<page state>" ...] [--url U] [--space N] [--page p1] [--max 10] [--allow-irreversible] [--finish] [--name NAME]
62
+ ego-jev check "<yes/no question>" [--space N] [--page p1]
63
+ ego-jev read "<question>" [--space N] [--page p1]
64
+ ```
65
+
66
+ - `do` works toward a goal. `key=value` pairs are the only strings Jev may type or select.
67
+ - `check` answers a yes/no question about the current page as a probability.
68
+ - `read` returns the page text relevant to a question (Jev ranks blocks, code keeps the best).
69
+ - `space` in the result is the ego TaskSpace id. Pass it to `--space` to continue in the same tab, or open it from
70
+ your own `ego-browser nodejs` script with `taskSpace(12)` and take over by hand.
71
+ - `--finish` closes the TaskSpace when the command ends. Otherwise close it with `task.finish({ keep: [] })`.
72
+ - With a secret manager, wrap the call: `op run --env-file keys.env -- ego-jev do ...`.
73
+
74
+ ## How to phrase a task
75
+
76
+ Jev picks one action at a time from what is on the screen. It does not plan. How you phrase the task decides whether
77
+ it finishes. Measured on six sites, 12 runs per row ([experiments](experiments/README.md)):
78
+
79
+ | Phrasing | Example | Correct | Mean time |
80
+ |---|---|---|---|
81
+ | outcome only | "I want the cheapest 3-person IH pressure cookers" | 8/12 | 7.4 s |
82
+ | procedure | "search, tick IH and 3-person in the filters, sort by lowest price" | 12/12 | 8.9 s |
83
+ | **outcome + `--require`** | the outcome, plus "the IH filter is selected", "the list is sorted by lowest price", ... | **12/12** | 10.4 s |
84
+
85
+ None of the 36 runs reported `done` for a wrong result.
86
+
87
+ Rules that came out of the runs:
88
+
89
+ 1. **Give the parts as `--require` page states, in order.** Write what you could see on the page when that part is
90
+ done ("the 3-person capacity filter is selected"), not the click ("click 3-person"). Each round Jev scores every
91
+ requirement and works on the first one not reached yet. You do not need to know the site.
92
+ 2. **Make the last requirement the end state** ("the order is complete", "the list is sorted by lowest price").
93
+ The run ends when every requirement has been reached and the last one is in effect.
94
+ 3. **Pass every string as a value.** Search terms, names, codes: Jev cannot write them. Several values for one form
95
+ are typed in the same round.
96
+ 4. **Outcome-only goals are fine for one-part tasks** (open an article, log in). For anything with filters, a cart
97
+ or several screens, they stop early.
98
+ 5. **Split long flows at points you want to check yourself**, especially before payments or deletes. Keep `--max`
99
+ near the number of steps you expect.
100
+
101
+ ## Statuses
102
+
103
+ | status | meaning | what the caller should do |
104
+ |---|---|---|
105
+ | `done` | the goal (or every requirement) is in effect on the page | move on |
106
+ | `likely_done` | probably reached, but not every part is visible now; with `--require`, `requirements` lists the scores | look at `requirements` or the page. After a flow whose earlier states disappear (an emptied cart), this is the expected result |
107
+ | `needs_confirmation` | the next click looks hard to undo (order, payment, delete, send) | rerun with `--allow-irreversible` only if that step is intended |
108
+ | `needs_login` | the page asks for sign-in and no credentials were given | log in in ego (or hand off to a human), then rerun |
109
+ | `blocked` | captcha, access denied, error page | hand off to a human |
110
+ | `ambiguous` | Jev could not pick a target with confidence | act on one of the returned `candidates` yourself |
111
+ | `stuck` | the same action repeated without progress, or Jev found nothing to do while the goal is not in effect | inspect the page and take over |
112
+ | `error` | the page shows an error after the last action | read `page_text` |
113
+ | `max_actions` | the action budget ran out | raise `--max` or split the goal |
114
+
115
+ ## How it works
116
+
117
+ Each round:
118
+
119
+ 1. Wait until the DOM has been quiet for 400 ms (pages open at `domcontentloaded`, so slow third-party resources do
120
+ not block).
121
+ 2. List the interactive elements of the top document with their label, state (checked, value, expanded, sorted,
122
+ selected) and whether they only open a popup or help text, and tag them with `data-jev-i`.
123
+ 3. Ask Jev one request with several questions: is the goal done, is the page blocked, is an error shown, is this a
124
+ login wall, would the next step be hard to undo, which tool, which element, which value. With `--require`, the
125
+ requirement scores ride in the same request. Pages with more than 240 elements go through two stages: pick a
126
+ group of 30 consecutive elements, then the element inside it.
127
+ 4. Turn the answers into one action (`click`, `type`, `press_enter`, `press_key`, `select`, `hover`, `drag`,
128
+ `upload`, `scroll`, `wait`) and run it through the ego Page API. If the action types a value and more values
129
+ have empty fields on the page, one more request maps them to fields and all are typed in that round.
130
+ 5. Stop on done, on a guard (login, irreversible, error, repetition) or on the action budget.
131
+
132
+ Before returning `done` without requirements, ego-jev asks a stricter question: judging from the page, is every part
133
+ of the goal in effect, either shown (a selected filter, an entered value) or evidenced (a confirmation, the page after
134
+ submitting)? A goal that orders or submits is not in effect while the form is merely filled.
135
+
136
+ Clicks on checkboxes and radios land on the left edge of their label, because labels often hold links in the middle.
137
+ A blocked click is retried after scrolling the target to the centre and once more after Escape; if it stays blocked,
138
+ Jev is told what covers it (`dialog "..." covers the target; close or dismiss it first`).
139
+
140
+ The API key reaches the ego runtime through a one-shot FIFO (mode 600). It is not written to disk and is removed
141
+ from the runtime's environment.
142
+
143
+ ## When to use it
144
+
145
+ Against an agent (Claude Code) driving ego itself, wall clock including the agent's own thinking
146
+ ([benchmarks](benchmarks/README.md), first version, procedure phrasing):
147
+
148
+ | Task | Agent drives ego | Agent + ego-jev |
149
+ |---|---|---|
150
+ | Danawa: two filters + sort on a 900-element page, read top 5 | 31.6 s | **22.8 s** |
151
+ | SauceDemo: log in, add 2 items, check out, finish | 19.3 s | **16.7 s** |
152
+ | httpbin form: 7 fields + submit | **10.2 s** | 15.8 s |
153
+ | Wikipedia: search, open article, read one value | **7.7 s** | 15.9 s |
154
+
155
+ The httpbin form now takes 7.9 s on its own with batch filling (it was 10.7 s without).
156
+
157
+ - **Use ego-jev** for flows of five or more steps, pages with hundreds of elements or awkward markup, and when you
158
+ want to keep page snapshots out of your agent's context.
159
+ - **Drive ego directly** for one or two actions on a page with clean selectors. Every ego-jev call pays 2 to 3 s of
160
+ start-up plus 0.7 to 1.5 s per Jev request.
161
+ - **Mix them.** Let ego-jev do the clicking, then read and extract with your own `ego-browser nodejs` script in the
162
+ same TaskSpace.
163
+
164
+ ## Limits
165
+
166
+ - Only the top document is read; elements inside iframes are not listed.
167
+ - Settling watches DOM mutations only, not network requests.
168
+ - New tabs are not followed automatically.
169
+ - Controls whose selected state is not in the DOM (no `checked`, `aria-pressed`, `aria-selected` or a
170
+ `selected`/`active` class) cannot be scored as requirements reliably.
171
+ - The irreversible-step guard is a probability with a 0.6 threshold. On sites with real payments, deletes or
172
+ messages, keep `--max` small and split the goal into steps you can check.
173
+ - A requirement counts as reached at a score of 0.5 and stays reached. If a later action undoes an earlier filter,
174
+ the run ends as `likely_done` and lists it; it does not go back to fix it.
175
+
176
+ ## Credits
177
+
178
+ The decision loop, page script, page model, content pruning and Jev client are derived from
179
+ [jev-browser](https://github.com/Ying-Kai-Liao/jev-browser) 0.1.2 by Ying-Kai Liao (MIT). ego-jev replaces its
180
+ Playwright and MCP layers with ego lite and adds requirements, batch filling, the done check, blocker-aware clicks
181
+ and state-preserving page fitting.
182
+
183
+ ego-jev is not affiliated with Citro Labs (ego lite) or TypeSafe (Jev).
184
+
185
+ ## License
186
+
187
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env node
2
+ // ego-jev: let Jev pick the next action inside an ego lite TaskSpace. The caller (you, or an LLM agent) supplies
3
+ // the goal and the values to type; Jev only chooses what to click and where each value goes.
4
+ //
5
+ // ego-jev do "<goal>" [key=value ...] [--url U] [--space N] [--page p1] [--max 10] [--require R ...] [--allow-irreversible] [--finish] [--name NAME]
6
+ // ego-jev check "<yes/no question>" [--space N] [--page p1]
7
+ // ego-jev read "<question>" [--space N] [--page p1]
8
+ //
9
+ // The last line printed is `EGO_JEV_RESULT {json}`. Pass its `space` to --space to continue in the same TaskSpace.
10
+ //
11
+ // The ego runtime does not inherit environment variables, so the API key is handed over through a one-shot FIFO
12
+ // (mode 600) that the runtime reads once. The key is never written to disk.
13
+ import { spawn, spawnSync } from "node:child_process";
14
+ import { createWriteStream, existsSync, realpathSync, rmSync } from "node:fs";
15
+ import { tmpdir, homedir } from "node:os";
16
+ import { join, dirname, delimiter } from "node:path";
17
+ import { randomBytes } from "node:crypto";
18
+ import { fileURLToPath, pathToFileURL } from "node:url";
19
+ import { createInterface } from "node:readline";
20
+
21
+ const ROOT = join(dirname(realpathSync(fileURLToPath(import.meta.url))), "..");
22
+
23
+ const USAGE = `usage: ego-jev do|check|read "<text>" [key=value ...] [--url U] [--space N] [--page p1] [--max N] [--require R ...] [--allow-irreversible] [--finish] [--name NAME]
24
+
25
+ do work toward a goal; key=value pairs are the values Jev may type or select
26
+ check yes/no question about the current page, answered as a probability
27
+ read the page text relevant to a question (Jev ranks blocks, code keeps the best)
28
+
29
+ --url U open U first (otherwise continue on the current page)
30
+ --space N reuse TaskSpace N (default: create one named --name or "ego-jev")
31
+ --page p1 page label inside the TaskSpace (default p1)
32
+ --max N action budget for do (default 10)
33
+ --require R a page state do has to reach; repeat for several, in the order they should be reached.
34
+ Each round works on the first one not reached yet
35
+ --allow-irreversible let do click through steps that look hard to undo (orders, payments, deletes)
36
+ --no-batch-fill type one value per round instead of filling every matching empty field at once
37
+ --no-merge-require score requirements in a separate request every round instead of with the action
38
+ --finish close the TaskSpace afterwards
39
+
40
+ environment: TYPESAFE_API_KEY (required), EGO_BROWSER (path to ego-browser, optional)`;
41
+
42
+ const fail = msg => { console.error(msg); process.exit(2); };
43
+ const argv = process.argv.slice(2);
44
+ if (!argv.length || ["-h", "--help", "help"].includes(argv[0])) { console.log(USAGE); process.exit(0); }
45
+ const cmd = argv.shift();
46
+ if (!["do", "check", "read"].includes(cmd)) fail(USAGE);
47
+ const A = { cmd, values: {} };
48
+ for (let k = 0; k < argv.length; k++) {
49
+ const a = argv[k];
50
+ if (a === "--url") A.url = argv[++k];
51
+ else if (a === "--space") A.space = argv[++k];
52
+ else if (a === "--page") A.page = argv[++k];
53
+ else if (a === "--max") A.max = Number(argv[++k]);
54
+ else if (a === "--name") A.name = argv[++k];
55
+ else if (a === "--allow-irreversible") A.allowIrreversible = true;
56
+ else if (a === "--finish") A.finish = true;
57
+ else if (a === "--require") (A.require ??= []).push(argv[++k]);
58
+ else if (a === "--no-batch-fill") A.batchFill = false;
59
+ else if (a === "--no-merge-require") A.mergeRequire = false;
60
+ else if (a.startsWith("--")) fail(USAGE);
61
+ else if (A.goal == null) A.goal = a;
62
+ else { const i = a.indexOf("="); if (i < 1) fail(USAGE); A.values[a.slice(0, i)] = a.slice(i + 1); }
63
+ }
64
+ if (!A.goal) fail(USAGE);
65
+
66
+ const key = process.env.TYPESAFE_API_KEY;
67
+ if (!key) fail("TYPESAFE_API_KEY is not set. Get a key from TypeSafe and export it, or wrap the call with your secret manager.");
68
+
69
+ const which = name => (process.env.PATH ?? "").split(delimiter).map(d => join(d, name)).find(p => existsSync(p));
70
+ const egoBrowser = [process.env.EGO_BROWSER, which("ego-browser"), join(homedir(), ".local/bin/ego-browser")].find(p => p && existsSync(p));
71
+ if (!egoBrowser) fail("ego-browser was not found. Install ego lite (https://github.com/citrolabs/ego-lite) and finish its onboarding.");
72
+
73
+ A.keyFifo = join(tmpdir(), `ego-jev-${randomBytes(6).toString("hex")}.fifo`);
74
+ if (spawnSync("mkfifo", ["-m", "600", A.keyFifo]).status !== 0) fail("could not create a FIFO for the API key");
75
+ const cleanup = () => rmSync(A.keyFifo, { force: true });
76
+
77
+ const script = [
78
+ `globalThis.EGO_JEV_ARGS = ${JSON.stringify(A)};`,
79
+ `globalThis.EGO_JEV_EGO = { taskSpace };`,
80
+ `await import(${JSON.stringify(pathToFileURL(join(ROOT, "src/run.mjs")).href)});`,
81
+ ].join("\n");
82
+
83
+ const env = { ...process.env }; delete env.TYPESAFE_API_KEY;
84
+ const child = spawn(egoBrowser, ["nodejs"], { stdio: ["pipe", "pipe", "pipe"], env });
85
+ child.stdin.end(script);
86
+ // The ego runtime writes console.log to stderr when its stdout is not a terminal. Route ego-jev's own lines (progress
87
+ // and the result) to stdout and everything else (runtime errors) to stderr, so `ego-jev ... | tail -1` works.
88
+ const OURS = /^(EGO_JEV_RESULT |\s+r\d+:|\s+!|\s+(verify|confirm)=|\s+also typed )/;
89
+ for (const s of [child.stdout, child.stderr]) {
90
+ createInterface({ input: s }).on("line", l => (OURS.test(l) ? process.stdout : process.stderr).write(l + "\n"));
91
+ }
92
+ // Opening a FIFO for writing waits until the runtime opens it for reading.
93
+ const w = createWriteStream(A.keyFifo);
94
+ w.on("error", () => {});
95
+ w.end(key);
96
+ child.on("close", code => { cleanup(); process.exitCode = code ?? 1; });
97
+ for (const sig of ["SIGINT", "SIGTERM"]) process.on(sig, () => { child.kill(sig); cleanup(); process.exit(130); });
package/package.json CHANGED
@@ -1,6 +1,17 @@
1
1
  {
2
2
  "name": "ego-jev",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Let Jev (TypeSafe System One) drive multi-step flows inside ego lite, your real browser. The agent supplies goals, values and target page states; Jev picks every click.",
5
+ "keywords": ["ego-lite", "jev", "typesafe", "browser-automation", "agent", "claude-code"],
6
+ "license": "MIT",
7
+ "author": "Kim Jisub",
8
+ "repository": { "type": "git", "url": "git+https://github.com/kimjisub/ego-jev.git" },
9
+ "homepage": "https://github.com/kimjisub/ego-jev#readme",
10
+ "bugs": { "url": "https://github.com/kimjisub/ego-jev/issues" },
11
+ "type": "module",
12
+ "bin": { "ego-jev": "bin/ego-jev.mjs" },
13
+ "exports": { ".": "./src/ego-jev.mjs" },
14
+ "files": ["bin", "src", "README.md", "CHANGELOG.md", "LICENSE"],
15
+ "engines": { "node": ">=20" },
16
+ "os": ["darwin"]
17
+ }