haltija 1.5.2 → 1.5.4
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 +32 -0
- package/apps/desktop/package.json +1 -1
- package/apps/desktop/resources/component.js +6 -2
- package/bin/tosijs-dev.mjs +27 -11
- package/bin/version.mjs +1 -1
- package/dist/component.d.ts +1 -1
- package/dist/component.esm.js +6 -2
- package/dist/component.js +6 -2
- package/dist/hj.js +2 -2
- package/dist/index.js +38 -10
- package/dist/server.js +38 -10
- package/dist/version.d.ts +1 -1
- package/docs/CI-INTEGRATION.md +25 -2
- package/llms.txt +11 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.5.4
|
|
4
|
+
|
|
5
|
+
The `hj tabs open` client-less-tab trap now explains itself ([#5](https://github.com/tonioloewald/haltija/issues/5)).
|
|
6
|
+
|
|
7
|
+
Outside the desktop app, `hj tabs open <url>` has no tab API, so it falls back to `window.open()` —
|
|
8
|
+
and that new tab has **no haltija widget** unless its page injects one. So the server never hears
|
|
9
|
+
from it, it doesn't appear in `hj tabs`, and commands can't reach it (they go to the focused widget
|
|
10
|
+
tab). It presents as "the tab opened fine but every command goes somewhere else" — indistinguishable
|
|
11
|
+
from a routing bug.
|
|
12
|
+
|
|
13
|
+
- The fallback response now carries a `reason`, promoted to a top-level `warning` that `hj` prints
|
|
14
|
+
on stderr — at the one moment the client-less tab is created.
|
|
15
|
+
- The `/tabs/open` schema and `SKILL.md` now state plainly that only widget-injected tabs are
|
|
16
|
+
controllable and appear in `hj tabs`, so a non-responding tab is the first thing to check.
|
|
17
|
+
|
|
18
|
+
## 1.5.3
|
|
19
|
+
|
|
20
|
+
Discoverability fix for the two CI browser engines ([#6](https://github.com/tonioloewald/haltija/issues/6)).
|
|
21
|
+
|
|
22
|
+
Both `--headless` and `--ci` said "for CI" with no hint that they drive **different** engines:
|
|
23
|
+
`--headless` is Playwright Chromium (and needs the `playwright` package), while `--ci` / `--app` /
|
|
24
|
+
`--private --app` drive Electron and need no Playwright. An agent picked `--headless`, hit
|
|
25
|
+
"Playwright not installed", and wrongly concluded haltija's CI mode is just a Playwright wrapper.
|
|
26
|
+
|
|
27
|
+
- `hj --help` now names the engine per mode and adds a "Choosing a CI engine" block; `--private` is
|
|
28
|
+
clarified as an *isolation* modifier that pairs with either engine (not "pair with `--headless`").
|
|
29
|
+
- The "Playwright not installed" error points at the Electron path (`--ci` / `--private --app`) as
|
|
30
|
+
the no-Playwright alternative.
|
|
31
|
+
- `llms.txt`, `docs/CI-INTEGRATION.md`, and `SKILL.md` get an honest "which engine?" framing:
|
|
32
|
+
it's Electron vs Playwright (neither is bundled), and the real reason to choose Playwright is
|
|
33
|
+
multi-engine coverage (Firefox/WebKit), not the words "for CI".
|
|
34
|
+
|
|
3
35
|
## 1.5.2
|
|
4
36
|
|
|
5
37
|
Two follow-ups from the 1.5.0 review, both about the multi-tab experience on a shared server.
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
// src/version.ts
|
|
49
|
-
var VERSION = "1.5.
|
|
49
|
+
var VERSION = "1.5.4";
|
|
50
50
|
|
|
51
51
|
// src/text-selector.ts
|
|
52
52
|
var TEXT_PSEUDO_RE = /:(?:text-is|has-text|text)\(/;
|
|
@@ -5637,7 +5637,11 @@ ${elementSummary}${moreText}`;
|
|
|
5637
5637
|
});
|
|
5638
5638
|
} else {
|
|
5639
5639
|
window.open(payload2.url, "_blank");
|
|
5640
|
-
this.respond(msg2.id, true, {
|
|
5640
|
+
this.respond(msg2.id, true, {
|
|
5641
|
+
opened: true,
|
|
5642
|
+
fallback: true,
|
|
5643
|
+
reason: "Opened a plain browser tab via window.open() (this is not the Haltija desktop app). " + "That tab has NO Haltija widget unless its page injects one, so it will NOT appear in " + "`hj tabs` and hj commands cannot reach it — untargeted commands go to the focused " + "widget tab instead. To control the new tab: inject the widget on that page (e.g. " + "HALTIJA_DEV=1 / haltijaDev:true in that project), or use the Haltija desktop app, " + "which auto-injects into every tab."
|
|
5644
|
+
});
|
|
5641
5645
|
}
|
|
5642
5646
|
} else if (action2 === "close") {
|
|
5643
5647
|
if (haltija?.closeTab) {
|
package/bin/tosijs-dev.mjs
CHANGED
|
@@ -37,12 +37,12 @@ ${bold('haltija')} - Browser control for AI agents
|
|
|
37
37
|
Usage:
|
|
38
38
|
haltija [options]
|
|
39
39
|
|
|
40
|
-
Modes:
|
|
40
|
+
Modes: ${dim('(the browser ENGINE differs — see "Choosing a CI engine" below)')}
|
|
41
41
|
${dim('(default)')} Launch desktop app if electron available, otherwise server
|
|
42
|
-
--app Explicitly launch desktop app (Electron)
|
|
43
|
-
--server Server only
|
|
44
|
-
--headless
|
|
45
|
-
--ci CI mode: Electron app + wait
|
|
42
|
+
--app Explicitly launch desktop app (Electron — Chromium)
|
|
43
|
+
--server Server only; bring your own browser (inject the widget). For bookmarklets/CI-lite
|
|
44
|
+
--headless Headless Chromium via ${bold('Playwright')} (needs the 'playwright' package) — for CI
|
|
45
|
+
--ci CI mode: ${bold('Electron')} app + wait-ready + sandbox off. No Playwright. Recommended for CI
|
|
46
46
|
|
|
47
47
|
Options:
|
|
48
48
|
--http HTTP only on port 8700 (default protocol)
|
|
@@ -62,7 +62,9 @@ Options:
|
|
|
62
62
|
--private Isolated automation instance: own server on an EPHEMERAL port (never
|
|
63
63
|
8700), own browser, torn down with the run. Never sees/adopts/navigates
|
|
64
64
|
the shared interactive browser. Reports its address on stdout
|
|
65
|
-
(HALTIJA_PRIVATE_READY {json}) and to --port-file.
|
|
65
|
+
(HALTIJA_PRIVATE_READY {json}) and to --port-file. This is an ISOLATION
|
|
66
|
+
modifier, not an engine: pair with --app (Electron, no Playwright) or
|
|
67
|
+
--headless (Playwright Chromium).
|
|
66
68
|
--port-file <p> Write the (private) server's bound address as JSON to <p> when ready.
|
|
67
69
|
--setup-mcp Configure Claude Desktop MCP integration
|
|
68
70
|
--setup-mcp-check Check MCP configuration status
|
|
@@ -106,13 +108,23 @@ Subcommands:
|
|
|
106
108
|
|
|
107
109
|
Use 'hj' as a short alias: hj tree, hj click @42, etc.
|
|
108
110
|
|
|
111
|
+
Choosing a CI engine ${dim('(both need one external browser, but a DIFFERENT one):')}
|
|
112
|
+
${bold('Electron')} (Chromium, same engine the desktop app uses) — no Playwright:
|
|
113
|
+
haltija --ci # recommended: Electron + wait-ready + sandbox off
|
|
114
|
+
haltija --private --app # + isolation (own ephemeral port, adopts nothing)
|
|
115
|
+
${dim('Electron is fetched via npx if not already installed.')}
|
|
116
|
+
${bold('Playwright')} Chromium — needs the 'playwright' package:
|
|
117
|
+
npm i playwright && npx playwright install chromium
|
|
118
|
+
haltija --headless # single-engine; or --private --headless for isolation
|
|
119
|
+
${dim("Pick this for MULTI-ENGINE coverage (Firefox/WebKit) — not just because it says \"CI\".")}
|
|
120
|
+
|
|
109
121
|
Examples:
|
|
110
122
|
haltija # Desktop app (or server fallback)
|
|
111
123
|
haltija --app # Desktop app explicitly
|
|
112
|
-
haltija --server # Server only
|
|
124
|
+
haltija --server # Server only (bring your own browser)
|
|
113
125
|
haltija --server --https # HTTPS server only
|
|
114
|
-
haltija --
|
|
115
|
-
haltija --
|
|
126
|
+
haltija --ci # CI: Electron (no Playwright) + wait + sandbox off
|
|
127
|
+
haltija --headless # CI: Playwright Chromium (needs playwright)
|
|
116
128
|
haltija --setup-mcp # Configure Claude Desktop integration
|
|
117
129
|
`)
|
|
118
130
|
process.exit(0)
|
|
@@ -729,8 +741,12 @@ const startHeadlessBrowser = async (port) => {
|
|
|
729
741
|
|
|
730
742
|
} catch (err) {
|
|
731
743
|
if (err.code === 'ERR_MODULE_NOT_FOUND') {
|
|
732
|
-
console.error('[tosijs-dev] Playwright not installed.
|
|
733
|
-
console.error('[tosijs-dev]
|
|
744
|
+
console.error('[tosijs-dev] --headless uses Playwright Chromium, which is not installed.')
|
|
745
|
+
console.error('[tosijs-dev] To use Playwright: npm install playwright && npx playwright install chromium')
|
|
746
|
+
console.error('[tosijs-dev] Or skip Playwright entirely and use the ELECTRON engine instead:')
|
|
747
|
+
console.error('[tosijs-dev] haltija --ci (Electron, waits for ready, sandbox off)')
|
|
748
|
+
console.error('[tosijs-dev] haltija --private --app (Electron + isolated ephemeral instance)')
|
|
749
|
+
console.error('[tosijs-dev] Reach for --headless (Playwright) when you need Firefox/WebKit coverage.')
|
|
734
750
|
} else {
|
|
735
751
|
console.error('[tosijs-dev] Failed to start headless browser:', err.message)
|
|
736
752
|
}
|
package/bin/version.mjs
CHANGED
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.5.
|
|
23
|
+
export declare const VERSION = "1.5.4";
|
|
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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/version.ts
|
|
2
|
-
var VERSION = "1.5.
|
|
2
|
+
var VERSION = "1.5.4";
|
|
3
3
|
|
|
4
4
|
// src/text-selector.ts
|
|
5
5
|
var TEXT_PSEUDO_RE = /:(?:text-is|has-text|text)\(/;
|
|
@@ -5590,7 +5590,11 @@ ${elementSummary}${moreText}`;
|
|
|
5590
5590
|
});
|
|
5591
5591
|
} else {
|
|
5592
5592
|
window.open(payload2.url, "_blank");
|
|
5593
|
-
this.respond(msg2.id, true, {
|
|
5593
|
+
this.respond(msg2.id, true, {
|
|
5594
|
+
opened: true,
|
|
5595
|
+
fallback: true,
|
|
5596
|
+
reason: "Opened a plain browser tab via window.open() (this is not the Haltija desktop app). " + "That tab has NO Haltija widget unless its page injects one, so it will NOT appear in " + "`hj tabs` and hj commands cannot reach it — untargeted commands go to the focused " + "widget tab instead. To control the new tab: inject the widget on that page (e.g. " + "HALTIJA_DEV=1 / haltijaDev:true in that project), or use the Haltija desktop app, " + "which auto-injects into every tab."
|
|
5597
|
+
});
|
|
5594
5598
|
}
|
|
5595
5599
|
} else if (action2 === "close") {
|
|
5596
5600
|
if (haltija?.closeTab) {
|
package/dist/component.js
CHANGED
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
// src/version.ts
|
|
49
|
-
var VERSION = "1.5.
|
|
49
|
+
var VERSION = "1.5.4";
|
|
50
50
|
|
|
51
51
|
// src/text-selector.ts
|
|
52
52
|
var TEXT_PSEUDO_RE = /:(?:text-is|has-text|text)\(/;
|
|
@@ -5637,7 +5637,11 @@ ${elementSummary}${moreText}`;
|
|
|
5637
5637
|
});
|
|
5638
5638
|
} else {
|
|
5639
5639
|
window.open(payload2.url, "_blank");
|
|
5640
|
-
this.respond(msg2.id, true, {
|
|
5640
|
+
this.respond(msg2.id, true, {
|
|
5641
|
+
opened: true,
|
|
5642
|
+
fallback: true,
|
|
5643
|
+
reason: "Opened a plain browser tab via window.open() (this is not the Haltija desktop app). " + "That tab has NO Haltija widget unless its page injects one, so it will NOT appear in " + "`hj tabs` and hj commands cannot reach it — untargeted commands go to the focused " + "widget tab instead. To control the new tab: inject the widget on that page (e.g. " + "HALTIJA_DEV=1 / haltijaDev:true in that project), or use the Haltija desktop app, " + "which auto-injects into every tab."
|
|
5644
|
+
});
|
|
5641
5645
|
}
|
|
5642
5646
|
} else if (action2 === "close") {
|
|
5643
5647
|
if (haltija?.closeTab) {
|
package/dist/hj.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
// haltija-cli:do-not-edit v1.5.
|
|
2
|
+
// haltija-cli:do-not-edit v1.5.4
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
5
5
|
|
|
@@ -756,7 +756,7 @@ function substituteGeneratedVars(text, seed) {
|
|
|
756
756
|
}
|
|
757
757
|
|
|
758
758
|
// bin/version.mjs
|
|
759
|
-
var HJ_VERSION = "1.5.
|
|
759
|
+
var HJ_VERSION = "1.5.4";
|
|
760
760
|
|
|
761
761
|
// bin/semver.mjs
|
|
762
762
|
function parseVersion(v) {
|
package/dist/index.js
CHANGED
|
@@ -674,7 +674,7 @@ var injectorCode = `
|
|
|
674
674
|
`;
|
|
675
675
|
|
|
676
676
|
// src/version.ts
|
|
677
|
-
var VERSION = "1.5.
|
|
677
|
+
var VERSION = "1.5.4";
|
|
678
678
|
|
|
679
679
|
// src/embedded-assets.ts
|
|
680
680
|
var APP_MD = `# Haltija App
|
|
@@ -2176,9 +2176,15 @@ Use window IDs in other endpoints (e.g., /click, /tree) to target specific tabs.
|
|
|
2176
2176
|
|
|
2177
2177
|
**Open a new tab**
|
|
2178
2178
|
|
|
2179
|
-
|
|
2179
|
+
Opens a new tab with optional URL. If url is omitted, opens a blank tab.
|
|
2180
2180
|
|
|
2181
|
-
|
|
2181
|
+
**In the Haltija desktop app** the new tab gets the widget auto-injected, so it's immediately
|
|
2182
|
+
controllable. **Anywhere else** (widget injected into a normal browser via bookmarklet/dev-server)
|
|
2183
|
+
there is no tab API, so this falls back to \`window.open()\` \u2014 the new tab is a plain browser tab
|
|
2184
|
+
with NO widget unless its own page injects one. In that case the response has \`fallback: true\`
|
|
2185
|
+
plus a \`reason\`, and a top-level \`warning\`: the tab will NOT appear in \`hj tabs\` and hj commands
|
|
2186
|
+
cannot reach it (they go to the focused widget tab). To control it, inject the widget on that page
|
|
2187
|
+
(e.g. HALTIJA_DEV / haltijaDev) or use the desktop app.
|
|
2182
2188
|
|
|
2183
2189
|
**Parameters:**
|
|
2184
2190
|
|
|
@@ -3024,8 +3030,17 @@ Two first-party ways to run (plus embedding, below):
|
|
|
3024
3030
|
\`hj tree\` inside a project reaches that project's server with no flags. Falling
|
|
3025
3031
|
back to the shared default port 8700 warns on stderr. Use \`hj where\` to see which
|
|
3026
3032
|
port a shell targets and WHY; override with \`--port\` or \`--name\`.
|
|
3027
|
-
- **CI:**
|
|
3028
|
-
|
|
3033
|
+
- **CI:** two engines, and the choice matters \u2014 both need one external browser, but
|
|
3034
|
+
a *different* one:
|
|
3035
|
+
- \`haltija --ci\` (or \`--private --app\` for an isolated instance) drives **Electron**
|
|
3036
|
+
(Chromium \u2014 the same engine the Haltija desktop app uses). Electron is fetched via
|
|
3037
|
+
\`npx\` if not already present; **no \`playwright\` needed.** This is the default CI path.
|
|
3038
|
+
- \`haltija --headless\` drives **Playwright Chromium**, which requires the \`playwright\`
|
|
3039
|
+
package (\`npm i playwright && npx playwright install chromium\`). Reach for it when you
|
|
3040
|
+
want multi-engine coverage (Firefox/WebKit) or a lighter single-engine run \u2014 not just
|
|
3041
|
+
because it says "for CI". \`--private\` is an *isolation* modifier (ephemeral port), and
|
|
3042
|
+
pairs with either engine: \`--private --app\` (Electron) or \`--private --headless\`
|
|
3043
|
+
(Playwright). See the CI guide below.
|
|
3029
3044
|
|
|
3030
3045
|
## Embed in your app
|
|
3031
3046
|
|
|
@@ -3101,7 +3116,7 @@ var COMPONENT_JS = `(() => {
|
|
|
3101
3116
|
});
|
|
3102
3117
|
|
|
3103
3118
|
// src/version.ts
|
|
3104
|
-
var VERSION = "1.5.
|
|
3119
|
+
var VERSION = "1.5.4";
|
|
3105
3120
|
|
|
3106
3121
|
// src/text-selector.ts
|
|
3107
3122
|
var TEXT_PSEUDO_RE = /:(?:text-is|has-text|text)\\(/;
|
|
@@ -8692,7 +8707,11 @@ var COMPONENT_JS = `(() => {
|
|
|
8692
8707
|
});
|
|
8693
8708
|
} else {
|
|
8694
8709
|
window.open(payload2.url, "_blank");
|
|
8695
|
-
this.respond(msg2.id, true, {
|
|
8710
|
+
this.respond(msg2.id, true, {
|
|
8711
|
+
opened: true,
|
|
8712
|
+
fallback: true,
|
|
8713
|
+
reason: "Opened a plain browser tab via window.open() (this is not the Haltija desktop app). " + "That tab has NO Haltija widget unless its page injects one, so it will NOT appear in " + "\`hj tabs\` and hj commands cannot reach it \u2014 untargeted commands go to the focused " + "widget tab instead. To control the new tab: inject the widget on that page (e.g. " + "HALTIJA_DEV=1 / haltijaDev:true in that project), or use the Haltija desktop app, " + "which auto-injects into every tab."
|
|
8714
|
+
});
|
|
8696
8715
|
}
|
|
8697
8716
|
} else if (action2 === "close") {
|
|
8698
8717
|
if (haltija?.closeTab) {
|
|
@@ -12818,9 +12837,15 @@ var tabsOpen = endpoint({
|
|
|
12818
12837
|
path: "/tabs/open",
|
|
12819
12838
|
method: "POST",
|
|
12820
12839
|
summary: "Open a new tab",
|
|
12821
|
-
description: `
|
|
12822
|
-
|
|
12823
|
-
|
|
12840
|
+
description: `Opens a new tab with optional URL. If url is omitted, opens a blank tab.
|
|
12841
|
+
|
|
12842
|
+
**In the Haltija desktop app** the new tab gets the widget auto-injected, so it's immediately
|
|
12843
|
+
controllable. **Anywhere else** (widget injected into a normal browser via bookmarklet/dev-server)
|
|
12844
|
+
there is no tab API, so this falls back to \`window.open()\` \u2014 the new tab is a plain browser tab
|
|
12845
|
+
with NO widget unless its own page injects one. In that case the response has \`fallback: true\`
|
|
12846
|
+
plus a \`reason\`, and a top-level \`warning\`: the tab will NOT appear in \`hj tabs\` and hj commands
|
|
12847
|
+
cannot reach it (they go to the focused widget tab). To control it, inject the widget on that page
|
|
12848
|
+
(e.g. HALTIJA_DEV / haltijaDev) or use the desktop app.`,
|
|
12824
12849
|
category: "windows",
|
|
12825
12850
|
input: L.object({
|
|
12826
12851
|
url: L.string.describe("URL to open").optional
|
|
@@ -14247,6 +14272,9 @@ registerHandler(screenshot, async (body, ctx) => {
|
|
|
14247
14272
|
});
|
|
14248
14273
|
registerHandler(tabsOpen, async (body, ctx) => {
|
|
14249
14274
|
const response = await ctx.requestFromBrowser("tabs", "open", { url: body.url });
|
|
14275
|
+
if (response?.data?.fallback && response.data.reason && !response.warning) {
|
|
14276
|
+
response.warning = response.data.reason;
|
|
14277
|
+
}
|
|
14250
14278
|
return Response.json(response, { headers: ctx.headers });
|
|
14251
14279
|
});
|
|
14252
14280
|
registerHandler(tabsClose, async (body, ctx) => {
|
package/dist/server.js
CHANGED
|
@@ -674,7 +674,7 @@ var injectorCode = `
|
|
|
674
674
|
`;
|
|
675
675
|
|
|
676
676
|
// src/version.ts
|
|
677
|
-
var VERSION = "1.5.
|
|
677
|
+
var VERSION = "1.5.4";
|
|
678
678
|
|
|
679
679
|
// src/embedded-assets.ts
|
|
680
680
|
var APP_MD = `# Haltija App
|
|
@@ -2176,9 +2176,15 @@ Use window IDs in other endpoints (e.g., /click, /tree) to target specific tabs.
|
|
|
2176
2176
|
|
|
2177
2177
|
**Open a new tab**
|
|
2178
2178
|
|
|
2179
|
-
|
|
2179
|
+
Opens a new tab with optional URL. If url is omitted, opens a blank tab.
|
|
2180
2180
|
|
|
2181
|
-
|
|
2181
|
+
**In the Haltija desktop app** the new tab gets the widget auto-injected, so it's immediately
|
|
2182
|
+
controllable. **Anywhere else** (widget injected into a normal browser via bookmarklet/dev-server)
|
|
2183
|
+
there is no tab API, so this falls back to \`window.open()\` \u2014 the new tab is a plain browser tab
|
|
2184
|
+
with NO widget unless its own page injects one. In that case the response has \`fallback: true\`
|
|
2185
|
+
plus a \`reason\`, and a top-level \`warning\`: the tab will NOT appear in \`hj tabs\` and hj commands
|
|
2186
|
+
cannot reach it (they go to the focused widget tab). To control it, inject the widget on that page
|
|
2187
|
+
(e.g. HALTIJA_DEV / haltijaDev) or use the desktop app.
|
|
2182
2188
|
|
|
2183
2189
|
**Parameters:**
|
|
2184
2190
|
|
|
@@ -3024,8 +3030,17 @@ Two first-party ways to run (plus embedding, below):
|
|
|
3024
3030
|
\`hj tree\` inside a project reaches that project's server with no flags. Falling
|
|
3025
3031
|
back to the shared default port 8700 warns on stderr. Use \`hj where\` to see which
|
|
3026
3032
|
port a shell targets and WHY; override with \`--port\` or \`--name\`.
|
|
3027
|
-
- **CI:**
|
|
3028
|
-
|
|
3033
|
+
- **CI:** two engines, and the choice matters \u2014 both need one external browser, but
|
|
3034
|
+
a *different* one:
|
|
3035
|
+
- \`haltija --ci\` (or \`--private --app\` for an isolated instance) drives **Electron**
|
|
3036
|
+
(Chromium \u2014 the same engine the Haltija desktop app uses). Electron is fetched via
|
|
3037
|
+
\`npx\` if not already present; **no \`playwright\` needed.** This is the default CI path.
|
|
3038
|
+
- \`haltija --headless\` drives **Playwright Chromium**, which requires the \`playwright\`
|
|
3039
|
+
package (\`npm i playwright && npx playwright install chromium\`). Reach for it when you
|
|
3040
|
+
want multi-engine coverage (Firefox/WebKit) or a lighter single-engine run \u2014 not just
|
|
3041
|
+
because it says "for CI". \`--private\` is an *isolation* modifier (ephemeral port), and
|
|
3042
|
+
pairs with either engine: \`--private --app\` (Electron) or \`--private --headless\`
|
|
3043
|
+
(Playwright). See the CI guide below.
|
|
3029
3044
|
|
|
3030
3045
|
## Embed in your app
|
|
3031
3046
|
|
|
@@ -3101,7 +3116,7 @@ var COMPONENT_JS = `(() => {
|
|
|
3101
3116
|
});
|
|
3102
3117
|
|
|
3103
3118
|
// src/version.ts
|
|
3104
|
-
var VERSION = "1.5.
|
|
3119
|
+
var VERSION = "1.5.4";
|
|
3105
3120
|
|
|
3106
3121
|
// src/text-selector.ts
|
|
3107
3122
|
var TEXT_PSEUDO_RE = /:(?:text-is|has-text|text)\\(/;
|
|
@@ -8692,7 +8707,11 @@ var COMPONENT_JS = `(() => {
|
|
|
8692
8707
|
});
|
|
8693
8708
|
} else {
|
|
8694
8709
|
window.open(payload2.url, "_blank");
|
|
8695
|
-
this.respond(msg2.id, true, {
|
|
8710
|
+
this.respond(msg2.id, true, {
|
|
8711
|
+
opened: true,
|
|
8712
|
+
fallback: true,
|
|
8713
|
+
reason: "Opened a plain browser tab via window.open() (this is not the Haltija desktop app). " + "That tab has NO Haltija widget unless its page injects one, so it will NOT appear in " + "\`hj tabs\` and hj commands cannot reach it \u2014 untargeted commands go to the focused " + "widget tab instead. To control the new tab: inject the widget on that page (e.g. " + "HALTIJA_DEV=1 / haltijaDev:true in that project), or use the Haltija desktop app, " + "which auto-injects into every tab."
|
|
8714
|
+
});
|
|
8696
8715
|
}
|
|
8697
8716
|
} else if (action2 === "close") {
|
|
8698
8717
|
if (haltija?.closeTab) {
|
|
@@ -12818,9 +12837,15 @@ var tabsOpen = endpoint({
|
|
|
12818
12837
|
path: "/tabs/open",
|
|
12819
12838
|
method: "POST",
|
|
12820
12839
|
summary: "Open a new tab",
|
|
12821
|
-
description: `
|
|
12822
|
-
|
|
12823
|
-
|
|
12840
|
+
description: `Opens a new tab with optional URL. If url is omitted, opens a blank tab.
|
|
12841
|
+
|
|
12842
|
+
**In the Haltija desktop app** the new tab gets the widget auto-injected, so it's immediately
|
|
12843
|
+
controllable. **Anywhere else** (widget injected into a normal browser via bookmarklet/dev-server)
|
|
12844
|
+
there is no tab API, so this falls back to \`window.open()\` \u2014 the new tab is a plain browser tab
|
|
12845
|
+
with NO widget unless its own page injects one. In that case the response has \`fallback: true\`
|
|
12846
|
+
plus a \`reason\`, and a top-level \`warning\`: the tab will NOT appear in \`hj tabs\` and hj commands
|
|
12847
|
+
cannot reach it (they go to the focused widget tab). To control it, inject the widget on that page
|
|
12848
|
+
(e.g. HALTIJA_DEV / haltijaDev) or use the desktop app.`,
|
|
12824
12849
|
category: "windows",
|
|
12825
12850
|
input: L.object({
|
|
12826
12851
|
url: L.string.describe("URL to open").optional
|
|
@@ -14247,6 +14272,9 @@ registerHandler(screenshot, async (body, ctx) => {
|
|
|
14247
14272
|
});
|
|
14248
14273
|
registerHandler(tabsOpen, async (body, ctx) => {
|
|
14249
14274
|
const response = await ctx.requestFromBrowser("tabs", "open", { url: body.url });
|
|
14275
|
+
if (response?.data?.fallback && response.data.reason && !response.warning) {
|
|
14276
|
+
response.warning = response.data.reason;
|
|
14277
|
+
}
|
|
14250
14278
|
return Response.json(response, { headers: ctx.headers });
|
|
14251
14279
|
});
|
|
14252
14280
|
registerHandler(tabsClose, async (body, ctx) => {
|
package/dist/version.d.ts
CHANGED
package/docs/CI-INTEGRATION.md
CHANGED
|
@@ -95,6 +95,24 @@ run: |
|
|
|
95
95
|
|
|
96
96
|
## Launch Options
|
|
97
97
|
|
|
98
|
+
### Which engine? (read this first)
|
|
99
|
+
|
|
100
|
+
Haltija can drive one of two browser engines in CI, and they need **different** external browsers —
|
|
101
|
+
picking by the words "for CI" alone leads people to the wrong one:
|
|
102
|
+
|
|
103
|
+
| You want | Use | Engine | External dependency |
|
|
104
|
+
| --- | --- | --- | --- |
|
|
105
|
+
| The default CI path | `--ci` | **Electron** (Chromium) | Electron (auto-fetched via `npx`) — **no Playwright** |
|
|
106
|
+
| Isolated CI instance, own ephemeral port | `--private --app` | **Electron** (Chromium) | Electron (as above) |
|
|
107
|
+
| Multi-engine coverage (Firefox/WebKit) or a lighter single-engine run | `--headless` | **Playwright** Chromium | the `playwright` package (`npm i playwright && npx playwright install`) |
|
|
108
|
+
|
|
109
|
+
Neither engine is bundled in the npm package, so *something* is downloaded either way — the choice
|
|
110
|
+
is **Electron vs. Playwright**, not "deps vs. no deps". The one genuine reason to reach for
|
|
111
|
+
`--headless`/Playwright is engines Electron can't give you (Firefox, WebKit). For a straight
|
|
112
|
+
Chromium CI lane, `--ci` (Electron) needs no separate `playwright` install. `--private` is an
|
|
113
|
+
*isolation* modifier — it pairs with either engine (`--private --app` = Electron, `--private
|
|
114
|
+
--headless` = Playwright).
|
|
115
|
+
|
|
98
116
|
### Option 1: CI mode (recommended)
|
|
99
117
|
|
|
100
118
|
```bash
|
|
@@ -115,13 +133,18 @@ xvfb-run --auto-servernum bunx haltija@latest --app &
|
|
|
115
133
|
|
|
116
134
|
Same as `--ci` but doesn't wait for ready state. You'll need to poll `/status` yourself.
|
|
117
135
|
|
|
118
|
-
### Option 3: Playwright headless (
|
|
136
|
+
### Option 3: Playwright headless (multi-engine)
|
|
119
137
|
|
|
120
138
|
```bash
|
|
139
|
+
npm install playwright && npx playwright install chromium # required — not bundled
|
|
121
140
|
bunx haltija@latest --headless &
|
|
122
141
|
```
|
|
123
142
|
|
|
124
|
-
Uses Playwright's Chromium instead of Electron
|
|
143
|
+
Uses **Playwright's** Chromium instead of Electron, so it needs the `playwright` package installed
|
|
144
|
+
(the command above; without it, `--headless` exits with a "Playwright not installed" error that
|
|
145
|
+
points you back to `--ci`). Choose this when you want a lighter single-engine run or, the real
|
|
146
|
+
reason, **multi-engine coverage** — Playwright can also drive Firefox and WebKit, which Electron
|
|
147
|
+
cannot. For a plain Chromium lane with no extra install, prefer Option 1 (`--ci`).
|
|
125
148
|
|
|
126
149
|
### Option 4: Server-only mode (lighter weight)
|
|
127
150
|
|
package/llms.txt
CHANGED
|
@@ -40,8 +40,17 @@ Two first-party ways to run (plus embedding, below):
|
|
|
40
40
|
`hj tree` inside a project reaches that project's server with no flags. Falling
|
|
41
41
|
back to the shared default port 8700 warns on stderr. Use `hj where` to see which
|
|
42
42
|
port a shell targets and WHY; override with `--port` or `--name`.
|
|
43
|
-
- **CI:**
|
|
44
|
-
|
|
43
|
+
- **CI:** two engines, and the choice matters — both need one external browser, but
|
|
44
|
+
a *different* one:
|
|
45
|
+
- `haltija --ci` (or `--private --app` for an isolated instance) drives **Electron**
|
|
46
|
+
(Chromium — the same engine the Haltija desktop app uses). Electron is fetched via
|
|
47
|
+
`npx` if not already present; **no `playwright` needed.** This is the default CI path.
|
|
48
|
+
- `haltija --headless` drives **Playwright Chromium**, which requires the `playwright`
|
|
49
|
+
package (`npm i playwright && npx playwright install chromium`). Reach for it when you
|
|
50
|
+
want multi-engine coverage (Firefox/WebKit) or a lighter single-engine run — not just
|
|
51
|
+
because it says "for CI". `--private` is an *isolation* modifier (ephemeral port), and
|
|
52
|
+
pairs with either engine: `--private --app` (Electron) or `--private --headless`
|
|
53
|
+
(Playwright). See the CI guide below.
|
|
45
54
|
|
|
46
55
|
## Embed in your app
|
|
47
56
|
|