haltija 1.3.1 → 1.3.3
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/apps/desktop/package.json +1 -1
- package/apps/desktop/resources/component.js +17 -1
- package/bin/cli-subcommand.mjs +16 -2
- package/bin/hj.mjs +11 -2
- package/dist/api-schema.d.ts +6 -0
- package/dist/component.d.ts +2 -1
- package/dist/component.esm.js +17 -1
- package/dist/component.js +17 -1
- package/dist/hj.js +11 -4
- package/dist/index.js +31 -4
- package/dist/server.js +31 -4
- 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
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
// src/version.ts
|
|
49
|
-
var VERSION = "1.3.
|
|
49
|
+
var VERSION = "1.3.3";
|
|
50
50
|
|
|
51
51
|
// src/text-selector.ts
|
|
52
52
|
var TEXT_PSEUDO_RE = /:(?:text-is|has-text|text)\(/;
|
|
@@ -1761,6 +1761,7 @@
|
|
|
1761
1761
|
displayStream = null;
|
|
1762
1762
|
displayVideo = null;
|
|
1763
1763
|
isActive = true;
|
|
1764
|
+
visibilityHandler = null;
|
|
1764
1765
|
homeLeft = 0;
|
|
1765
1766
|
homeBottom = 16;
|
|
1766
1767
|
headless = false;
|
|
@@ -1952,6 +1953,16 @@
|
|
|
1952
1953
|
}
|
|
1953
1954
|
this.interceptConsole();
|
|
1954
1955
|
this.interceptDialogs();
|
|
1956
|
+
if (typeof document !== "undefined") {
|
|
1957
|
+
this.visibilityHandler = () => {
|
|
1958
|
+
if (document.visibilityState === "hidden") {
|
|
1959
|
+
this.deactivate();
|
|
1960
|
+
} else {
|
|
1961
|
+
this.activate();
|
|
1962
|
+
}
|
|
1963
|
+
};
|
|
1964
|
+
document.addEventListener("visibilitychange", this.visibilityHandler);
|
|
1965
|
+
}
|
|
1955
1966
|
this.connect();
|
|
1956
1967
|
}
|
|
1957
1968
|
disconnectedCallback() {
|
|
@@ -1962,6 +1973,10 @@
|
|
|
1962
1973
|
this.clearEventWatchers();
|
|
1963
1974
|
this.stopMutationWatch();
|
|
1964
1975
|
this.stopScreenCapture();
|
|
1976
|
+
if (this.visibilityHandler && typeof document !== "undefined") {
|
|
1977
|
+
document.removeEventListener("visibilitychange", this.visibilityHandler);
|
|
1978
|
+
this.visibilityHandler = null;
|
|
1979
|
+
}
|
|
1965
1980
|
}
|
|
1966
1981
|
attributeChangedCallback(name, _old, value) {
|
|
1967
1982
|
if (name === "server") {
|
|
@@ -5100,6 +5115,7 @@ ${elementSummary}${moreText}`;
|
|
|
5100
5115
|
this.state = "connected";
|
|
5101
5116
|
this.show();
|
|
5102
5117
|
const windowType = window.opener ? "popup" : window.parent !== window ? "iframe" : "tab";
|
|
5118
|
+
this.isActive = typeof document === "undefined" || document.visibilityState !== "hidden";
|
|
5103
5119
|
this.send("system", "connected", {
|
|
5104
5120
|
windowId: this.windowId,
|
|
5105
5121
|
browserId: this.browserId,
|
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/api-schema.d.ts
CHANGED
|
@@ -63,6 +63,8 @@ export declare const tree: EndpointDef<{
|
|
|
63
63
|
pierceFrames?: boolean | undefined;
|
|
64
64
|
compact?: boolean | undefined;
|
|
65
65
|
ancestors?: boolean | undefined;
|
|
66
|
+
includeBox?: boolean | undefined;
|
|
67
|
+
mode?: string | undefined;
|
|
66
68
|
window?: string | undefined;
|
|
67
69
|
}>;
|
|
68
70
|
export declare const query: EndpointDef<{
|
|
@@ -354,6 +356,8 @@ export declare const endpoints: {
|
|
|
354
356
|
pierceFrames?: boolean | undefined;
|
|
355
357
|
compact?: boolean | undefined;
|
|
356
358
|
ancestors?: boolean | undefined;
|
|
359
|
+
includeBox?: boolean | undefined;
|
|
360
|
+
mode?: string | undefined;
|
|
357
361
|
window?: string | undefined;
|
|
358
362
|
}>;
|
|
359
363
|
readonly query: EndpointDef<{
|
|
@@ -645,6 +649,8 @@ export declare const ALL_ENDPOINTS: (EndpointDef<{
|
|
|
645
649
|
pierceFrames?: boolean | undefined;
|
|
646
650
|
compact?: boolean | undefined;
|
|
647
651
|
ancestors?: boolean | undefined;
|
|
652
|
+
includeBox?: boolean | undefined;
|
|
653
|
+
mode?: string | undefined;
|
|
648
654
|
window?: string | undefined;
|
|
649
655
|
}> | EndpointDef<{
|
|
650
656
|
selector?: string | undefined;
|
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.3";
|
|
24
24
|
export declare class DevChannel extends HTMLElement {
|
|
25
25
|
static get tagName(): string;
|
|
26
26
|
static elementCreator(): () => DevChannel;
|
|
@@ -50,6 +50,7 @@ export declare class DevChannel extends HTMLElement {
|
|
|
50
50
|
private displayStream;
|
|
51
51
|
private displayVideo;
|
|
52
52
|
private isActive;
|
|
53
|
+
private visibilityHandler;
|
|
53
54
|
private homeLeft;
|
|
54
55
|
private homeBottom;
|
|
55
56
|
private headless;
|
package/dist/component.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/version.ts
|
|
2
|
-
var VERSION = "1.3.
|
|
2
|
+
var VERSION = "1.3.3";
|
|
3
3
|
|
|
4
4
|
// src/text-selector.ts
|
|
5
5
|
var TEXT_PSEUDO_RE = /:(?:text-is|has-text|text)\(/;
|
|
@@ -1714,6 +1714,7 @@ class DevChannel extends HTMLElement {
|
|
|
1714
1714
|
displayStream = null;
|
|
1715
1715
|
displayVideo = null;
|
|
1716
1716
|
isActive = true;
|
|
1717
|
+
visibilityHandler = null;
|
|
1717
1718
|
homeLeft = 0;
|
|
1718
1719
|
homeBottom = 16;
|
|
1719
1720
|
headless = false;
|
|
@@ -1905,6 +1906,16 @@ class DevChannel extends HTMLElement {
|
|
|
1905
1906
|
}
|
|
1906
1907
|
this.interceptConsole();
|
|
1907
1908
|
this.interceptDialogs();
|
|
1909
|
+
if (typeof document !== "undefined") {
|
|
1910
|
+
this.visibilityHandler = () => {
|
|
1911
|
+
if (document.visibilityState === "hidden") {
|
|
1912
|
+
this.deactivate();
|
|
1913
|
+
} else {
|
|
1914
|
+
this.activate();
|
|
1915
|
+
}
|
|
1916
|
+
};
|
|
1917
|
+
document.addEventListener("visibilitychange", this.visibilityHandler);
|
|
1918
|
+
}
|
|
1908
1919
|
this.connect();
|
|
1909
1920
|
}
|
|
1910
1921
|
disconnectedCallback() {
|
|
@@ -1915,6 +1926,10 @@ class DevChannel extends HTMLElement {
|
|
|
1915
1926
|
this.clearEventWatchers();
|
|
1916
1927
|
this.stopMutationWatch();
|
|
1917
1928
|
this.stopScreenCapture();
|
|
1929
|
+
if (this.visibilityHandler && typeof document !== "undefined") {
|
|
1930
|
+
document.removeEventListener("visibilitychange", this.visibilityHandler);
|
|
1931
|
+
this.visibilityHandler = null;
|
|
1932
|
+
}
|
|
1918
1933
|
}
|
|
1919
1934
|
attributeChangedCallback(name, _old, value) {
|
|
1920
1935
|
if (name === "server") {
|
|
@@ -5053,6 +5068,7 @@ ${elementSummary}${moreText}`;
|
|
|
5053
5068
|
this.state = "connected";
|
|
5054
5069
|
this.show();
|
|
5055
5070
|
const windowType = window.opener ? "popup" : window.parent !== window ? "iframe" : "tab";
|
|
5071
|
+
this.isActive = typeof document === "undefined" || document.visibilityState !== "hidden";
|
|
5056
5072
|
this.send("system", "connected", {
|
|
5057
5073
|
windowId: this.windowId,
|
|
5058
5074
|
browserId: this.browserId,
|
package/dist/component.js
CHANGED
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
// src/version.ts
|
|
49
|
-
var VERSION = "1.3.
|
|
49
|
+
var VERSION = "1.3.3";
|
|
50
50
|
|
|
51
51
|
// src/text-selector.ts
|
|
52
52
|
var TEXT_PSEUDO_RE = /:(?:text-is|has-text|text)\(/;
|
|
@@ -1761,6 +1761,7 @@
|
|
|
1761
1761
|
displayStream = null;
|
|
1762
1762
|
displayVideo = null;
|
|
1763
1763
|
isActive = true;
|
|
1764
|
+
visibilityHandler = null;
|
|
1764
1765
|
homeLeft = 0;
|
|
1765
1766
|
homeBottom = 16;
|
|
1766
1767
|
headless = false;
|
|
@@ -1952,6 +1953,16 @@
|
|
|
1952
1953
|
}
|
|
1953
1954
|
this.interceptConsole();
|
|
1954
1955
|
this.interceptDialogs();
|
|
1956
|
+
if (typeof document !== "undefined") {
|
|
1957
|
+
this.visibilityHandler = () => {
|
|
1958
|
+
if (document.visibilityState === "hidden") {
|
|
1959
|
+
this.deactivate();
|
|
1960
|
+
} else {
|
|
1961
|
+
this.activate();
|
|
1962
|
+
}
|
|
1963
|
+
};
|
|
1964
|
+
document.addEventListener("visibilitychange", this.visibilityHandler);
|
|
1965
|
+
}
|
|
1955
1966
|
this.connect();
|
|
1956
1967
|
}
|
|
1957
1968
|
disconnectedCallback() {
|
|
@@ -1962,6 +1973,10 @@
|
|
|
1962
1973
|
this.clearEventWatchers();
|
|
1963
1974
|
this.stopMutationWatch();
|
|
1964
1975
|
this.stopScreenCapture();
|
|
1976
|
+
if (this.visibilityHandler && typeof document !== "undefined") {
|
|
1977
|
+
document.removeEventListener("visibilitychange", this.visibilityHandler);
|
|
1978
|
+
this.visibilityHandler = null;
|
|
1979
|
+
}
|
|
1965
1980
|
}
|
|
1966
1981
|
attributeChangedCallback(name, _old, value) {
|
|
1967
1982
|
if (name === "server") {
|
|
@@ -5100,6 +5115,7 @@ ${elementSummary}${moreText}`;
|
|
|
5100
5115
|
this.state = "connected";
|
|
5101
5116
|
this.show();
|
|
5102
5117
|
const windowType = window.opener ? "popup" : window.parent !== window ? "iframe" : "tab";
|
|
5118
|
+
this.isActive = typeof document === "undefined" || document.visibilityState !== "hidden";
|
|
5103
5119
|
this.send("system", "connected", {
|
|
5104
5120
|
windowId: this.windowId,
|
|
5105
5121
|
browserId: this.browserId,
|
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.3";
|
|
678
678
|
|
|
679
679
|
// src/embedded-assets.ts
|
|
680
680
|
var APP_MD = `# Haltija App
|
|
@@ -1086,6 +1086,8 @@ Use ancestors:true to see parent elements when inspecting deep elements.
|
|
|
1086
1086
|
| \`pierceFrames\` | boolean,null | Pierce same-origin iframes (default true) |
|
|
1087
1087
|
| \`compact\` | boolean,null | Minimal output (default false) |
|
|
1088
1088
|
| \`ancestors\` | boolean,null | Include ancestor path from root (default false) |
|
|
1089
|
+
| \`includeBox\` | boolean,null | Include each node's bounding box { x, y, w, h, visible } (default false) |
|
|
1090
|
+
| \`mode\` | string,null | 'actionable' returns a page action summary (url, title, headings, buttons, links, inputs, selects) instead of the DOM tree |
|
|
1089
1091
|
| \`window\` | string,null | Target window ID |
|
|
1090
1092
|
|
|
1091
1093
|
**Examples:**
|
|
@@ -3019,7 +3021,7 @@ var COMPONENT_JS = `(() => {
|
|
|
3019
3021
|
});
|
|
3020
3022
|
|
|
3021
3023
|
// src/version.ts
|
|
3022
|
-
var VERSION = "1.3.
|
|
3024
|
+
var VERSION = "1.3.3";
|
|
3023
3025
|
|
|
3024
3026
|
// src/text-selector.ts
|
|
3025
3027
|
var TEXT_PSEUDO_RE = /:(?:text-is|has-text|text)\\(/;
|
|
@@ -4734,6 +4736,7 @@ var COMPONENT_JS = `(() => {
|
|
|
4734
4736
|
displayStream = null;
|
|
4735
4737
|
displayVideo = null;
|
|
4736
4738
|
isActive = true;
|
|
4739
|
+
visibilityHandler = null;
|
|
4737
4740
|
homeLeft = 0;
|
|
4738
4741
|
homeBottom = 16;
|
|
4739
4742
|
headless = false;
|
|
@@ -4925,6 +4928,16 @@ var COMPONENT_JS = `(() => {
|
|
|
4925
4928
|
}
|
|
4926
4929
|
this.interceptConsole();
|
|
4927
4930
|
this.interceptDialogs();
|
|
4931
|
+
if (typeof document !== "undefined") {
|
|
4932
|
+
this.visibilityHandler = () => {
|
|
4933
|
+
if (document.visibilityState === "hidden") {
|
|
4934
|
+
this.deactivate();
|
|
4935
|
+
} else {
|
|
4936
|
+
this.activate();
|
|
4937
|
+
}
|
|
4938
|
+
};
|
|
4939
|
+
document.addEventListener("visibilitychange", this.visibilityHandler);
|
|
4940
|
+
}
|
|
4928
4941
|
this.connect();
|
|
4929
4942
|
}
|
|
4930
4943
|
disconnectedCallback() {
|
|
@@ -4935,6 +4948,10 @@ var COMPONENT_JS = `(() => {
|
|
|
4935
4948
|
this.clearEventWatchers();
|
|
4936
4949
|
this.stopMutationWatch();
|
|
4937
4950
|
this.stopScreenCapture();
|
|
4951
|
+
if (this.visibilityHandler && typeof document !== "undefined") {
|
|
4952
|
+
document.removeEventListener("visibilitychange", this.visibilityHandler);
|
|
4953
|
+
this.visibilityHandler = null;
|
|
4954
|
+
}
|
|
4938
4955
|
}
|
|
4939
4956
|
attributeChangedCallback(name, _old, value) {
|
|
4940
4957
|
if (name === "server") {
|
|
@@ -8073,6 +8090,7 @@ var COMPONENT_JS = `(() => {
|
|
|
8073
8090
|
this.state = "connected";
|
|
8074
8091
|
this.show();
|
|
8075
8092
|
const windowType = window.opener ? "popup" : window.parent !== window ? "iframe" : "tab";
|
|
8093
|
+
this.isActive = typeof document === "undefined" || document.visibilityState !== "hidden";
|
|
8076
8094
|
this.send("system", "connected", {
|
|
8077
8095
|
windowId: this.windowId,
|
|
8078
8096
|
browserId: this.browserId,
|
|
@@ -11373,6 +11391,8 @@ Use ancestors:true to see parent elements when inspecting deep elements.`,
|
|
|
11373
11391
|
pierceFrames: L.boolean.describe("Pierce same-origin iframes (default true)").optional,
|
|
11374
11392
|
compact: L.boolean.describe("Minimal output (default false)").optional,
|
|
11375
11393
|
ancestors: L.boolean.describe("Include ancestor path from root (default false)").optional,
|
|
11394
|
+
includeBox: L.boolean.describe("Include each node's bounding box { x, y, w, h, visible } (default false)").optional,
|
|
11395
|
+
mode: L.string.describe("'actionable' returns a page action summary (url, title, headings, buttons, links, inputs, selects) instead of the DOM tree").optional,
|
|
11376
11396
|
window: L.string.describe("Target window ID").optional
|
|
11377
11397
|
}),
|
|
11378
11398
|
examples: [
|
|
@@ -14063,7 +14083,10 @@ registerHandler(tree, async (body, ctx) => {
|
|
|
14063
14083
|
pierceShadow: body.pierceShadow,
|
|
14064
14084
|
pierceFrames: body.pierceFrames,
|
|
14065
14085
|
visibleOnly: body.visibleOnly,
|
|
14066
|
-
|
|
14086
|
+
interactiveOnly: body.interactiveOnly,
|
|
14087
|
+
ancestors: body.ancestors,
|
|
14088
|
+
includeBox: body.includeBox,
|
|
14089
|
+
mode: body.mode
|
|
14067
14090
|
}, 5000, windowId);
|
|
14068
14091
|
return Response.json(response, { headers: ctx.headers });
|
|
14069
14092
|
});
|
|
@@ -18525,7 +18548,8 @@ var serverConfig = {
|
|
|
18525
18548
|
label: existingWindow?.label,
|
|
18526
18549
|
windowType: windowType || "tab"
|
|
18527
18550
|
});
|
|
18528
|
-
|
|
18551
|
+
const isVisibleTab = (windowType || "tab") === "tab" && active !== false;
|
|
18552
|
+
if (!focusedWindowId || focusedWindowId === windowId || isVisibleTab) {
|
|
18529
18553
|
focusedWindowId = windowId;
|
|
18530
18554
|
}
|
|
18531
18555
|
console.log(`${LOG_PREFIX} Window connected: ${windowId} (${url})`);
|
|
@@ -18569,6 +18593,9 @@ var serverConfig = {
|
|
|
18569
18593
|
if (win) {
|
|
18570
18594
|
win.active = active;
|
|
18571
18595
|
win.lastSeen = Date.now();
|
|
18596
|
+
if (active && win.windowType === "tab") {
|
|
18597
|
+
focusedWindowId = windowId;
|
|
18598
|
+
}
|
|
18572
18599
|
}
|
|
18573
18600
|
}
|
|
18574
18601
|
} catch {}
|
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.3";
|
|
678
678
|
|
|
679
679
|
// src/embedded-assets.ts
|
|
680
680
|
var APP_MD = `# Haltija App
|
|
@@ -1086,6 +1086,8 @@ Use ancestors:true to see parent elements when inspecting deep elements.
|
|
|
1086
1086
|
| \`pierceFrames\` | boolean,null | Pierce same-origin iframes (default true) |
|
|
1087
1087
|
| \`compact\` | boolean,null | Minimal output (default false) |
|
|
1088
1088
|
| \`ancestors\` | boolean,null | Include ancestor path from root (default false) |
|
|
1089
|
+
| \`includeBox\` | boolean,null | Include each node's bounding box { x, y, w, h, visible } (default false) |
|
|
1090
|
+
| \`mode\` | string,null | 'actionable' returns a page action summary (url, title, headings, buttons, links, inputs, selects) instead of the DOM tree |
|
|
1089
1091
|
| \`window\` | string,null | Target window ID |
|
|
1090
1092
|
|
|
1091
1093
|
**Examples:**
|
|
@@ -3019,7 +3021,7 @@ var COMPONENT_JS = `(() => {
|
|
|
3019
3021
|
});
|
|
3020
3022
|
|
|
3021
3023
|
// src/version.ts
|
|
3022
|
-
var VERSION = "1.3.
|
|
3024
|
+
var VERSION = "1.3.3";
|
|
3023
3025
|
|
|
3024
3026
|
// src/text-selector.ts
|
|
3025
3027
|
var TEXT_PSEUDO_RE = /:(?:text-is|has-text|text)\\(/;
|
|
@@ -4734,6 +4736,7 @@ var COMPONENT_JS = `(() => {
|
|
|
4734
4736
|
displayStream = null;
|
|
4735
4737
|
displayVideo = null;
|
|
4736
4738
|
isActive = true;
|
|
4739
|
+
visibilityHandler = null;
|
|
4737
4740
|
homeLeft = 0;
|
|
4738
4741
|
homeBottom = 16;
|
|
4739
4742
|
headless = false;
|
|
@@ -4925,6 +4928,16 @@ var COMPONENT_JS = `(() => {
|
|
|
4925
4928
|
}
|
|
4926
4929
|
this.interceptConsole();
|
|
4927
4930
|
this.interceptDialogs();
|
|
4931
|
+
if (typeof document !== "undefined") {
|
|
4932
|
+
this.visibilityHandler = () => {
|
|
4933
|
+
if (document.visibilityState === "hidden") {
|
|
4934
|
+
this.deactivate();
|
|
4935
|
+
} else {
|
|
4936
|
+
this.activate();
|
|
4937
|
+
}
|
|
4938
|
+
};
|
|
4939
|
+
document.addEventListener("visibilitychange", this.visibilityHandler);
|
|
4940
|
+
}
|
|
4928
4941
|
this.connect();
|
|
4929
4942
|
}
|
|
4930
4943
|
disconnectedCallback() {
|
|
@@ -4935,6 +4948,10 @@ var COMPONENT_JS = `(() => {
|
|
|
4935
4948
|
this.clearEventWatchers();
|
|
4936
4949
|
this.stopMutationWatch();
|
|
4937
4950
|
this.stopScreenCapture();
|
|
4951
|
+
if (this.visibilityHandler && typeof document !== "undefined") {
|
|
4952
|
+
document.removeEventListener("visibilitychange", this.visibilityHandler);
|
|
4953
|
+
this.visibilityHandler = null;
|
|
4954
|
+
}
|
|
4938
4955
|
}
|
|
4939
4956
|
attributeChangedCallback(name, _old, value) {
|
|
4940
4957
|
if (name === "server") {
|
|
@@ -8073,6 +8090,7 @@ var COMPONENT_JS = `(() => {
|
|
|
8073
8090
|
this.state = "connected";
|
|
8074
8091
|
this.show();
|
|
8075
8092
|
const windowType = window.opener ? "popup" : window.parent !== window ? "iframe" : "tab";
|
|
8093
|
+
this.isActive = typeof document === "undefined" || document.visibilityState !== "hidden";
|
|
8076
8094
|
this.send("system", "connected", {
|
|
8077
8095
|
windowId: this.windowId,
|
|
8078
8096
|
browserId: this.browserId,
|
|
@@ -11373,6 +11391,8 @@ Use ancestors:true to see parent elements when inspecting deep elements.`,
|
|
|
11373
11391
|
pierceFrames: L.boolean.describe("Pierce same-origin iframes (default true)").optional,
|
|
11374
11392
|
compact: L.boolean.describe("Minimal output (default false)").optional,
|
|
11375
11393
|
ancestors: L.boolean.describe("Include ancestor path from root (default false)").optional,
|
|
11394
|
+
includeBox: L.boolean.describe("Include each node's bounding box { x, y, w, h, visible } (default false)").optional,
|
|
11395
|
+
mode: L.string.describe("'actionable' returns a page action summary (url, title, headings, buttons, links, inputs, selects) instead of the DOM tree").optional,
|
|
11376
11396
|
window: L.string.describe("Target window ID").optional
|
|
11377
11397
|
}),
|
|
11378
11398
|
examples: [
|
|
@@ -14063,7 +14083,10 @@ registerHandler(tree, async (body, ctx) => {
|
|
|
14063
14083
|
pierceShadow: body.pierceShadow,
|
|
14064
14084
|
pierceFrames: body.pierceFrames,
|
|
14065
14085
|
visibleOnly: body.visibleOnly,
|
|
14066
|
-
|
|
14086
|
+
interactiveOnly: body.interactiveOnly,
|
|
14087
|
+
ancestors: body.ancestors,
|
|
14088
|
+
includeBox: body.includeBox,
|
|
14089
|
+
mode: body.mode
|
|
14067
14090
|
}, 5000, windowId);
|
|
14068
14091
|
return Response.json(response, { headers: ctx.headers });
|
|
14069
14092
|
});
|
|
@@ -18525,7 +18548,8 @@ var serverConfig = {
|
|
|
18525
18548
|
label: existingWindow?.label,
|
|
18526
18549
|
windowType: windowType || "tab"
|
|
18527
18550
|
});
|
|
18528
|
-
|
|
18551
|
+
const isVisibleTab = (windowType || "tab") === "tab" && active !== false;
|
|
18552
|
+
if (!focusedWindowId || focusedWindowId === windowId || isVisibleTab) {
|
|
18529
18553
|
focusedWindowId = windowId;
|
|
18530
18554
|
}
|
|
18531
18555
|
console.log(`${LOG_PREFIX} Window connected: ${windowId} (${url})`);
|
|
@@ -18569,6 +18593,9 @@ var serverConfig = {
|
|
|
18569
18593
|
if (win) {
|
|
18570
18594
|
win.active = active;
|
|
18571
18595
|
win.lastSeen = Date.now();
|
|
18596
|
+
if (active && win.windowType === "tab") {
|
|
18597
|
+
focusedWindowId = windowId;
|
|
18598
|
+
}
|
|
18572
18599
|
}
|
|
18573
18600
|
}
|
|
18574
18601
|
} catch {}
|
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."
|