haltija 1.3.0 → 1.3.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 +18 -12
- package/apps/desktop/package.json +1 -1
- package/apps/desktop/resources/component.js +1 -1
- package/bin/cli-subcommand.mjs +16 -2
- package/bin/hj.mjs +11 -2
- package/dist/component.d.ts +1 -1
- package/dist/component.esm.js +1 -1
- package/dist/component.js +1 -1
- package/dist/hj.js +11 -4
- package/dist/index.js +2 -2
- package/dist/server.js +2 -2
- package/dist/version.d.ts +1 -1
- package/docs/README.md +1 -1
- package/docs/agent-prompt.md +5 -1
- package/docs/recipes.md +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,18 +61,24 @@ Full API: `hj docs` — or `hj api` for complete reference
|
|
|
61
61
|
|
|
62
62
|
---
|
|
63
63
|
|
|
64
|
-
##
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
|
69
|
-
|
|
70
|
-
|
|
|
71
|
-
|
|
|
72
|
-
|
|
|
73
|
-
|
|
|
74
|
-
|
|
75
|
-
|
|
64
|
+
## Haltija vs. Playwright
|
|
65
|
+
|
|
66
|
+
Playwright is a mature, cross-browser automation framework with deep tooling (trace viewer, codegen, test runner, fixtures) and an official MCP that hands agents a structured accessibility snapshot. Reach for it when you want cross-browser coverage or a full test framework. Haltija aims at something narrower: letting an agent drive **a browser you're already running**, over plain HTTP — or spawn its own when you'd rather it did.
|
|
67
|
+
|
|
68
|
+
| | Haltija | Playwright (+ MCP) |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| Browser | Attach to your real, already-open browser — *or* spawn its own | Usually its own managed context; can connect to an existing browser over CDP |
|
|
71
|
+
| Cross-browser | ⚠️ Chromium only | ✅ Chromium, Firefox, WebKit |
|
|
72
|
+
| Session / auth | Uses your live logged-in session as-is | Fresh context by default; reusable via `storageState` / persistent context |
|
|
73
|
+
| How an agent drives it | Plain HTTP/REST — curl, any language, no client library | Playwright client library, or the MCP tools |
|
|
74
|
+
| Page model | DOM tree with stable ref IDs + `eval` | Accessibility-tree snapshot with ref IDs (MCP), or the full API in code |
|
|
75
|
+
| Interaction | Per-character, framework-triggering synthetic events you can steer live over HTTP | Trusted CDP-level input |
|
|
76
|
+
| Feedback when a step can't proceed | **Fails fast** — reports what's blocking it (missing / hidden / covered) right away | **Auto-waits to a timeout** (tens of seconds) — smooths over races, but a genuinely broken step burns the full timeout before failing |
|
|
77
|
+
| Test generation | record→replay → JSON tests | codegen → Playwright code |
|
|
78
|
+
| Debugging | Live widget, console capture, semantic events, click diff | Trace viewer — time-travel step snapshots |
|
|
79
|
+
| Embed in your app | ✅ Ship the widget inside your own product | Not designed for that |
|
|
80
|
+
|
|
81
|
+
**The short version:** they're complementary, and this project uses both — Playwright for cross-browser smoke checks, Haltija for the fast end-to-end pass. Reach for Haltija when you want to point an agent at the browser (and logged-in session) you already have, or embed agent-control into your own app — over nothing more than HTTP.
|
|
76
82
|
|
|
77
83
|
---
|
|
78
84
|
|
package/bin/cli-subcommand.mjs
CHANGED
|
@@ -584,7 +584,7 @@ async function launchElectronApp() {
|
|
|
584
584
|
return false
|
|
585
585
|
}
|
|
586
586
|
|
|
587
|
-
async function ensureBrowserConnected(port) {
|
|
587
|
+
async function ensureBrowserConnected(port, { explicitTarget = false } = {}) {
|
|
588
588
|
let status
|
|
589
589
|
try {
|
|
590
590
|
const resp = await fetch(`http://localhost:${port}/status`, {
|
|
@@ -600,6 +600,19 @@ async function ensureBrowserConnected(port) {
|
|
|
600
600
|
// produce two app instances side by side — skip the launch.
|
|
601
601
|
if (status?.desktopApp) return true
|
|
602
602
|
|
|
603
|
+
// Private / project-owned server (explicitly targeted via --port / --name /
|
|
604
|
+
// HALTIJA_PORT). Launching the standalone Haltija.app here is wrong — it runs
|
|
605
|
+
// its own server on 8700 and would never connect to this port — so guide the
|
|
606
|
+
// user to attach a browser to *this* server instead of spawning Electron.
|
|
607
|
+
if (explicitTarget) {
|
|
608
|
+
process.stderr.write(
|
|
609
|
+
`\x1b[2mNo browser connected to the haltija server on port ${port}. ` +
|
|
610
|
+
`Open your app/page with the widget injected (script tag or bookmarklet), ` +
|
|
611
|
+
`or run \`hj --no-launch\` to skip this check.\x1b[0m\n`
|
|
612
|
+
)
|
|
613
|
+
return false
|
|
614
|
+
}
|
|
615
|
+
|
|
603
616
|
// Respect "user explicitly quit" — don't auto-relaunch on every agent
|
|
604
617
|
// call. Cleared when the user starts Haltija manually.
|
|
605
618
|
try {
|
|
@@ -748,6 +761,7 @@ export async function runSubcommand(subcommand, subArgs, port = '8700', options
|
|
|
748
761
|
const baseUrl = `http://localhost:${port}`
|
|
749
762
|
const jsonOutput = subArgs.includes('--json')
|
|
750
763
|
const noLaunch = options.noLaunch || false
|
|
764
|
+
const explicitTarget = options.explicitTarget || false
|
|
751
765
|
// Remove --json and extract --window before processing
|
|
752
766
|
let filteredArgs = subArgs.filter(a => a !== '--json')
|
|
753
767
|
let targetWindowId = undefined
|
|
@@ -791,7 +805,7 @@ export async function runSubcommand(subcommand, subArgs, port = '8700', options
|
|
|
791
805
|
|
|
792
806
|
// Auto-launch browser if no windows connected (skip for info commands and --no-launch)
|
|
793
807
|
if (!noLaunch && !INFO_COMMANDS.has(subcommand)) {
|
|
794
|
-
await ensureBrowserConnected(port)
|
|
808
|
+
await ensureBrowserConnected(port, { explicitTarget })
|
|
795
809
|
}
|
|
796
810
|
|
|
797
811
|
// Special handling for 'send' command - route to appropriate endpoint
|
package/bin/hj.mjs
CHANGED
|
@@ -196,6 +196,15 @@ if (noLaunchIdx !== -1) {
|
|
|
196
196
|
args.splice(noLaunchIdx, 1)
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
// Did the shell explicitly target a private instance (--port / --name /
|
|
200
|
+
// HALTIJA_PORT / HALTIJA_NAME / DEV_CHANNEL_PORT)? If so, this is a
|
|
201
|
+
// project-owned server with a bring-your-own browser — auto-launching the
|
|
202
|
+
// standalone Haltija.app is never right (it runs its own server on 8700 and
|
|
203
|
+
// can't connect to this port), so we suppress the Electron launch and print
|
|
204
|
+
// an actionable hint instead. Only the bare, unconfigured 8700 default keeps
|
|
205
|
+
// the zero-config desktop auto-launch.
|
|
206
|
+
const explicitTarget = portSource !== '8700 (default)'
|
|
207
|
+
|
|
199
208
|
// --- Space-to-hyphen sub-command resolution ---
|
|
200
209
|
// "hj test run foo.json" → "hj test-run foo.json"
|
|
201
210
|
// "hj events watch" → "hj events-watch"
|
|
@@ -247,7 +256,7 @@ if (!isSubcommand(subcommand)) {
|
|
|
247
256
|
|
|
248
257
|
// Auto-execute if there's exactly one fuzzy match
|
|
249
258
|
if (suggestion) {
|
|
250
|
-
runSubcommand(suggestion, subArgs, port, { noLaunch })
|
|
259
|
+
runSubcommand(suggestion, subArgs, port, { noLaunch, explicitTarget })
|
|
251
260
|
} else {
|
|
252
261
|
console.error(`Unknown command: '${subcommand}'`)
|
|
253
262
|
console.error(`\nExamples: hj tree, hj navigate <url>, hj click @42`)
|
|
@@ -255,7 +264,7 @@ if (!isSubcommand(subcommand)) {
|
|
|
255
264
|
process.exit(1)
|
|
256
265
|
}
|
|
257
266
|
} else {
|
|
258
|
-
runSubcommand(subcommand, subArgs, port, { noLaunch })
|
|
267
|
+
runSubcommand(subcommand, subArgs, port, { noLaunch, explicitTarget })
|
|
259
268
|
}
|
|
260
269
|
|
|
261
270
|
function filterHelp(topic) {
|
package/dist/component.d.ts
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* - Option+Tab toggles visibility (but active state always shows briefly)
|
|
21
21
|
* - Localhost only by default
|
|
22
22
|
*/
|
|
23
|
-
export declare const VERSION = "1.3.
|
|
23
|
+
export declare const VERSION = "1.3.2";
|
|
24
24
|
export declare class DevChannel extends HTMLElement {
|
|
25
25
|
static get tagName(): string;
|
|
26
26
|
static elementCreator(): () => DevChannel;
|
package/dist/component.esm.js
CHANGED
package/dist/component.js
CHANGED
package/dist/hj.js
CHANGED
|
@@ -1345,7 +1345,7 @@ async function launchElectronApp() {
|
|
|
1345
1345
|
}
|
|
1346
1346
|
return false;
|
|
1347
1347
|
}
|
|
1348
|
-
async function ensureBrowserConnected(port) {
|
|
1348
|
+
async function ensureBrowserConnected(port, { explicitTarget = false } = {}) {
|
|
1349
1349
|
let status;
|
|
1350
1350
|
try {
|
|
1351
1351
|
const resp = await fetch(`http://localhost:${port}/status`, {
|
|
@@ -1359,6 +1359,11 @@ async function ensureBrowserConnected(port) {
|
|
|
1359
1359
|
}
|
|
1360
1360
|
if (status?.desktopApp)
|
|
1361
1361
|
return true;
|
|
1362
|
+
if (explicitTarget) {
|
|
1363
|
+
process.stderr.write(`\x1B[2mNo browser connected to the haltija server on port ${port}. Open your app/page with the widget injected (script tag or bookmarklet), or run \`hj --no-launch\` to skip this check.\x1B[0m
|
|
1364
|
+
`);
|
|
1365
|
+
return false;
|
|
1366
|
+
}
|
|
1362
1367
|
try {
|
|
1363
1368
|
const quitMarker = join(homedir(), ".haltija", "last-quit");
|
|
1364
1369
|
if (existsSync(quitMarker)) {
|
|
@@ -1483,6 +1488,7 @@ async function runSubcommand(subcommand, subArgs, port = "8700", options = {}) {
|
|
|
1483
1488
|
const baseUrl = `http://localhost:${port}`;
|
|
1484
1489
|
const jsonOutput = subArgs.includes("--json");
|
|
1485
1490
|
const noLaunch = options.noLaunch || false;
|
|
1491
|
+
const explicitTarget = options.explicitTarget || false;
|
|
1486
1492
|
let filteredArgs = subArgs.filter((a) => a !== "--json");
|
|
1487
1493
|
let targetWindowId = undefined;
|
|
1488
1494
|
const windowIdx = filteredArgs.indexOf("--window");
|
|
@@ -1516,7 +1522,7 @@ async function runSubcommand(subcommand, subArgs, port = "8700", options = {}) {
|
|
|
1516
1522
|
}
|
|
1517
1523
|
}
|
|
1518
1524
|
if (!noLaunch && !INFO_COMMANDS.has(subcommand)) {
|
|
1519
|
-
await ensureBrowserConnected(port);
|
|
1525
|
+
await ensureBrowserConnected(port, { explicitTarget });
|
|
1520
1526
|
}
|
|
1521
1527
|
if (subcommand === "send") {
|
|
1522
1528
|
const firstArg = filteredArgs[0]?.toLocaleLowerCase();
|
|
@@ -2018,6 +2024,7 @@ if (noLaunchIdx !== -1) {
|
|
|
2018
2024
|
noLaunch = true;
|
|
2019
2025
|
args.splice(noLaunchIdx, 1);
|
|
2020
2026
|
}
|
|
2027
|
+
var explicitTarget = portSource !== "8700 (default)";
|
|
2021
2028
|
if (args.length >= 2 && isSubcommand(`${args[0]}-${args[1]}`)) {
|
|
2022
2029
|
args.splice(0, 2, `${args[0]}-${args[1]}`);
|
|
2023
2030
|
}
|
|
@@ -2052,7 +2059,7 @@ if (!isSubcommand(subcommand)) {
|
|
|
2052
2059
|
process.exit(0);
|
|
2053
2060
|
}
|
|
2054
2061
|
if (suggestion) {
|
|
2055
|
-
runSubcommand(suggestion, subArgs, port, { noLaunch });
|
|
2062
|
+
runSubcommand(suggestion, subArgs, port, { noLaunch, explicitTarget });
|
|
2056
2063
|
} else {
|
|
2057
2064
|
console.error(`Unknown command: '${subcommand}'`);
|
|
2058
2065
|
console.error(`
|
|
@@ -2061,7 +2068,7 @@ Examples: hj tree, hj navigate <url>, hj click @42`);
|
|
|
2061
2068
|
process.exit(1);
|
|
2062
2069
|
}
|
|
2063
2070
|
} else {
|
|
2064
|
-
runSubcommand(subcommand, subArgs, port, { noLaunch });
|
|
2071
|
+
runSubcommand(subcommand, subArgs, port, { noLaunch, explicitTarget });
|
|
2065
2072
|
}
|
|
2066
2073
|
function filterHelp(topic) {
|
|
2067
2074
|
const bold2 = (s) => `\x1B[1m${s}\x1B[0m`;
|
package/dist/index.js
CHANGED
|
@@ -674,7 +674,7 @@ var injectorCode = `
|
|
|
674
674
|
`;
|
|
675
675
|
|
|
676
676
|
// src/version.ts
|
|
677
|
-
var VERSION = "1.3.
|
|
677
|
+
var VERSION = "1.3.2";
|
|
678
678
|
|
|
679
679
|
// src/embedded-assets.ts
|
|
680
680
|
var APP_MD = `# Haltija App
|
|
@@ -3019,7 +3019,7 @@ var COMPONENT_JS = `(() => {
|
|
|
3019
3019
|
});
|
|
3020
3020
|
|
|
3021
3021
|
// src/version.ts
|
|
3022
|
-
var VERSION = "1.3.
|
|
3022
|
+
var VERSION = "1.3.2";
|
|
3023
3023
|
|
|
3024
3024
|
// src/text-selector.ts
|
|
3025
3025
|
var TEXT_PSEUDO_RE = /:(?:text-is|has-text|text)\\(/;
|
package/dist/server.js
CHANGED
|
@@ -674,7 +674,7 @@ var injectorCode = `
|
|
|
674
674
|
`;
|
|
675
675
|
|
|
676
676
|
// src/version.ts
|
|
677
|
-
var VERSION = "1.3.
|
|
677
|
+
var VERSION = "1.3.2";
|
|
678
678
|
|
|
679
679
|
// src/embedded-assets.ts
|
|
680
680
|
var APP_MD = `# Haltija App
|
|
@@ -3019,7 +3019,7 @@ var COMPONENT_JS = `(() => {
|
|
|
3019
3019
|
});
|
|
3020
3020
|
|
|
3021
3021
|
// src/version.ts
|
|
3022
|
-
var VERSION = "1.3.
|
|
3022
|
+
var VERSION = "1.3.2";
|
|
3023
3023
|
|
|
3024
3024
|
// src/text-selector.ts
|
|
3025
3025
|
var TEXT_PSEUDO_RE = /:(?:text-is|has-text|text)\\(/;
|
package/dist/version.d.ts
CHANGED
package/docs/README.md
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
## Planning
|
|
35
35
|
|
|
36
36
|
- **[Executive Summary](EXECUTIVE-SUMMARY.md)** - What Haltija is, who it's for
|
|
37
|
-
- **[Roadmap
|
|
37
|
+
- **[Roadmap](ROADMAP.md)** - Where we're going
|
|
38
38
|
- **[Development Roadmap](../ROADMAP.md)** - Completed and planned phases
|
|
39
39
|
- **[TODO](../TODO.md)** - Outstanding issues and ideas
|
|
40
40
|
|
package/docs/agent-prompt.md
CHANGED
|
@@ -81,7 +81,7 @@ To fill this form:
|
|
|
81
81
|
hj tree -d 5 Deeper tree
|
|
82
82
|
hj tree form Subtree rooted at selector
|
|
83
83
|
hj console Recent console logs/errors
|
|
84
|
-
hj screenshot Capture page as image
|
|
84
|
+
hj screenshot Capture page as image (--format webp for smaller)
|
|
85
85
|
hj location Current URL and title
|
|
86
86
|
hj events Recent semantic events
|
|
87
87
|
|
|
@@ -113,6 +113,10 @@ To fill this form:
|
|
|
113
113
|
hj windows List connected tabs
|
|
114
114
|
hj click 5 --window abc Target specific tab
|
|
115
115
|
|
|
116
|
+
### Server
|
|
117
|
+
hj status Is the server up? which tab is focused?
|
|
118
|
+
hj where Which server this shell targets (add --json)
|
|
119
|
+
|
|
116
120
|
## Tips
|
|
117
121
|
|
|
118
122
|
- Start with `hj tree` — look for [interactive] to find actionable elements
|
package/docs/recipes.md
CHANGED
|
@@ -128,8 +128,8 @@ curl localhost:8700/events
|
|
|
128
128
|
# Highlight what might be wrong
|
|
129
129
|
curl -X POST localhost:8700/highlight -d '{"selector":".error-message","label":"This error"}'
|
|
130
130
|
|
|
131
|
-
# Screenshot for the ticket
|
|
132
|
-
curl -X POST localhost:8700/screenshot -d '{"maxWidth":1200}'
|
|
131
|
+
# Screenshot for the ticket (webp keeps it small)
|
|
132
|
+
curl -X POST localhost:8700/screenshot -d '{"maxWidth":1200,"format":"webp"}'
|
|
133
133
|
```
|
|
134
134
|
|
|
135
135
|
**Or just tell the agent**: "Customer says Settings page is broken. Check it out."
|