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 CHANGED
@@ -61,18 +61,24 @@ Full API: `hj docs` — or `hj api` for complete reference
61
61
 
62
62
  ---
63
63
 
64
- ## Why Not Playwright / Puppeteer?
65
-
66
- | | Haltija | Playwright MCP |
67
- |---|---------|----------------|
68
- | **Browser** | Your real browser | Separate headless instance |
69
- | **State** | Already logged in, cookies, extensions | Clean slate every time |
70
- | **Setup** | `bunx haltija` | Install Playwright, configure MCP |
71
- | **Protocol** | Simple REST/curl | Complex CDP |
72
- | **Feedback** | "Button hidden by modal" | `TimeoutError: element not found` |
73
- | **Visibility** | Watch it happen live | Background process |
74
-
75
- Haltija connects to the browser you're already using. The one with the bug, the active session, and the weird cookie state. No reproduction script required.
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
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "haltija-desktop",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "private": true,
5
5
  "description": "Haltija Desktop - God Mode Browser for AI Agents",
6
6
  "homepage": "https://github.com/tonioloewald/haltija",
@@ -46,7 +46,7 @@
46
46
  });
47
47
 
48
48
  // src/version.ts
49
- var VERSION = "1.3.0";
49
+ var VERSION = "1.3.2";
50
50
 
51
51
  // src/text-selector.ts
52
52
  var TEXT_PSEUDO_RE = /:(?:text-is|has-text|text)\(/;
@@ -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) {
@@ -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.0";
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;
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var VERSION = "1.3.0";
2
+ var VERSION = "1.3.2";
3
3
 
4
4
  // src/text-selector.ts
5
5
  var TEXT_PSEUDO_RE = /:(?:text-is|has-text|text)\(/;
package/dist/component.js CHANGED
@@ -46,7 +46,7 @@
46
46
  });
47
47
 
48
48
  // src/version.ts
49
- var VERSION = "1.3.0";
49
+ var VERSION = "1.3.2";
50
50
 
51
51
  // src/text-selector.ts
52
52
  var TEXT_PSEUDO_RE = /:(?:text-is|has-text|text)\(/;
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.0";
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.0";
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.0";
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.0";
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
@@ -8,4 +8,4 @@
8
8
  * ⚠️ AUTO-GENERATED FROM package.json - DO NOT EDIT THIS FILE
9
9
  * ⚠️ To change the version, update package.json and run: bun run build
10
10
  */
11
- export declare const VERSION = "1.3.0";
11
+ export declare const VERSION = "1.3.2";
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 to 10/10](ROADMAP-TO-10.md)** - Where we're going
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
 
@@ -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."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "haltija",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "Browser control for AI agents - query DOM, click, type, run JS, watch mutations",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",