haltija 1.12.2 → 1.12.5

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 CHANGED
@@ -1,5 +1,140 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.12.5
4
+
5
+ **Installing this delivers 1.12.3 and 1.12.4 as well** — both were tagged but never published, so
6
+ npm went straight from 1.12.2 to here. Their entries below still describe what they contained.
7
+
8
+ ### Embedding on an HTTPS dev server no longer fails silently ([#33](https://github.com/tonioloewald/haltija/issues/33))
9
+
10
+ An `https://` page cannot load an `http://` script: the browser blocks it as **mixed content** and
11
+ reports nothing that names the cause. You get no widget, `hj where` says `0 tabs`, and the natural
12
+ conclusion is that your own setup is wrong. The reporter worked it out from `--help` and source.
13
+
14
+ This is not an edge case — the tosijs-ui doc-system dev server is HTTPS by default (`bun run tls`),
15
+ so the default project setup hits it on the first attempt.
16
+
17
+ A static `src` cannot branch on the page's scheme, so the canonical embed snippet is now a
18
+ three-line loader that picks the matching transport, with the plain tag kept below as the equivalent
19
+ for HTTP pages. Serving an HTTPS page also needs `bunx haltija --server --both` and accepting the
20
+ self-signed cert once at `https://localhost:8701`.
21
+
22
+ Same root cause as [#32](https://github.com/tonioloewald/haltija/issues/32) from the other end:
23
+ there, a shared HTTP-only server silently denied another project's HTTPS pages; here, an HTTPS page
24
+ silently cannot reach an HTTP server. **Opening both transports by default would remove the class
25
+ rather than documenting it**, which is why that remains the top of the queue rather than something
26
+ more prose can fix.
27
+
28
+ ## 1.12.4
29
+
30
+ Backlog work done while releases were paused. **Includes everything in 1.12.3**, which was tagged
31
+ but never published — installing 1.12.4 gets both.
32
+
33
+ ### Three bugs found by closing a coverage hole
34
+
35
+ **Ten of seventeen step actions had no executable coverage in any lane** — including the
36
+ deprecated-alias branch and the `select-text` dispatch that shipped in 1.12.3. Nothing was red; the
37
+ lanes simply never ran those paths, and the failure mode there is a *silent success*. There is now a
38
+ blocking suite that asserts an **observable effect** per action (a checkbox actually checked, a
39
+ keydown that actually arrived), and it found:
40
+
41
+ - **`hj drag` does nothing to a native `<input type=range>` and reported success.** Measured: a 60px
42
+ drag leaves a native range at 0 while moving a custom div thumb 0 → 60px. Browsers drive native
43
+ controls from *trusted* input only; a custom implementation listens for `mousemove` on `document`,
44
+ which is why MUI sliders, resize handles and reorder lists are unaffected. `/drag` now returns a
45
+ warning naming the cause and the way round it.
46
+ - **`hj test validate` accepted an `assert` step with no `assertion` object** — the shape our own
47
+ `CLAUDE.md` documented. Such a step never checks anything: a guard that cannot fail.
48
+ - **`tests/haltija.test.ts` reported 11 passes for work it never did.** With no server it early-returned
49
+ from each test body, and bun records that as PASSED. Now 0 pass / 11 skip.
50
+
51
+ ### The MCP server has been shipping 43 of 63 endpoints since January
52
+
53
+ `apps/mcp/build/endpoints.json` — what the committed MCP server imports at runtime — had drifted
54
+ seven months behind. Anyone on the MCP path had no `/map`, `/find`, `/wait`, `/key`, `/call`,
55
+ `/form`, `/fetch`, `/select`, `/recording`, `/test/suite`, or the `/network`, `/video` and `/dialog`
56
+ families. Twenty endpoints.
57
+
58
+ `docs-drift` could not catch it, and that is the lesson: the gate asserts a build leaves the tree
59
+ clean, and **nothing in the build wrote that file**. A drift gate only covers what the build
60
+ produces. Now it writes it, with a test asserting the runtime copy matches the generated one.
61
+
62
+ Also fixed: `hj --setup-mcp` pointed at `node_modules/tosijs-dev/…` in two of three lookups, a
63
+ package name that changed long ago. (`apps/mcp` is still not in the npm `files` list — that is a
64
+ packaging decision, deliberately left rather than guessed at.)
65
+
66
+ ### `hj where` says which transports are open — and why one is not ([#32](https://github.com/tonioloewald/haltija/issues/32))
67
+
68
+ ```
69
+ transports: http 8700 ✓ https 8701 ✗ (no certs in <path>)
70
+ ```
71
+
72
+ The channel is shared across projects, so **one project's transport choice is paid for by another**:
73
+ an instance that came up HTTP-only leaves every `https` page with nothing to import, because mixed
74
+ content blocks the fallback. The reporter's doc site had no haltija at all while `hj where` said
75
+ "haltija 1.12.2, 1 tab" and everything looked healthy. A half-open channel and a full one were
76
+ indistinguishable from outside; now the reason is named (`not requested`, `no certs at <path>`, or
77
+ `port N is held`). A `--private` instance reports transports but not the cross-project warning — it
78
+ is isolated by construction and denies nobody.
79
+
80
+ Opening both transports by default (the other half of that issue) needs a release to exercise and is
81
+ queued rather than rushed.
82
+
83
+ ### Also
84
+
85
+ - Four stale copies folded in, including a **fourth** hand-maintained step list in `api-schema.ts`
86
+ that had drifted to 8 of 17 and propagated to `API.md` and the MCP definitions.
87
+ - `CLAUDE.md`'s test-JSON example used the flat `assert` shape the runner ignores.
88
+
89
+ ## 1.12.3
90
+
91
+ Documentation-drift machinery, a measured rename, and the six blockers a nine-lens review raised
92
+ against all of it. **Known open:** [#26](https://github.com/tonioloewald/haltija/issues/26) (a tab
93
+ becoming permanently undrivable — observed, not reproducible on any version) and
94
+ [#16](https://github.com/tonioloewald/haltija/issues/16) (native tosiAgent bridge, on hold).
95
+
96
+ **Recorded suites now use `select-text`, which servers before 1.12.3 reject.** If you record on an
97
+ updated machine and run against a pinned older haltija, that step fails with `unknown step action`.
98
+ The paved CI path (`bunx haltija@latest`) is unaffected.
99
+
100
+
101
+ ### `select` is now `select-text` — and `select` is being freed
102
+
103
+ Measured, not argued. Shown the bare step vocabulary with no descriptions, **3/3 agents reached for
104
+ `select` to choose an option from a dropdown, and 3/3 read it cold as "choose an `<option>`".
105
+ Nobody read it as text selection** — which is what it does. A name that unanimously means something
106
+ else to competent readers is not a documentation problem.
107
+
108
+ `select-text` says what it does. **`select` still works** as a deprecated alias, so existing suites
109
+ keep running; steps using it now carry a `warning` naming the replacement, rendered in the test
110
+ report rather than only present in `--json`.
111
+
112
+ The recorder also stopped emitting two illegal steps it had emitted since long before this release:
113
+ `set` (not a step action at all — recorded suites died with "Unsupported step action: set") and
114
+ `select` for **dropdowns**, which resolved to `select-text` and dispatched a text-selection event at
115
+ the `<select>`, passing while choosing nothing. Both now record an explicit `eval` that works, and a
116
+ test asserts every action the recorder can emit is legal and non-deprecated.
117
+
118
+ The point of the deprecation is the reuse: once the alias can be dropped, `select` is free to mean
119
+ what everyone already expects — pick an option from a `<select>` — which haltija cannot do at all
120
+ today. A test enforces that the two meanings can never overlap (`no alias may shadow a live
121
+ action`), because a word meaning two things depending on vintage is exactly the silent-wrong-action
122
+ trap the rename exists to remove.
123
+
124
+ **Correction, and it matters more than the rename.** An earlier draft of these notes acquitted
125
+ `check`, `verify` and `tabs-focus` as "measured and fine". That was wrong: the harness spawned each
126
+ agent **in this repo**, so every "first impression" had our own `CLAUDE.md` — which documents the
127
+ vocabulary under test — in context. Re-run from a neutral directory, `check` is cold-read as
128
+ *"asserts that some condition is true"* by **3/3** (the exact confusion it was cleared of), and
129
+ `verify` slips to 2/3. What survives is narrower and honest: with the full vocabulary in view agents
130
+ still pick `assert` for assertions and `check` for checkboxes, so `check` is safe **in context** and
131
+ misleading **in isolation**. `tabs-focus` holds at 3/3.
132
+
133
+ The `select` verdict is unaffected — contamination ran in its favour and it still lost 3/3, and the
134
+ clean re-run agrees. The probe now runs each sample in an empty temp directory, and its vocabularies
135
+ are derived from the registries rather than hand-copied (the old copy still listed `select`, stale
136
+ as of the commit it motivated). Harness: `tools/naming-probe.mjs`, run with bun.
137
+
3
138
  ## 1.12.2
4
139
 
5
140
  Two fixes, both from an agent driving real apps. Each is a case where haltija reported success — or
@@ -40,12 +40,12 @@ var __export = (target, all) => {
40
40
  // src/artifacts.ts
41
41
  var exports_artifacts = {};
42
42
  __export(exports_artifacts, {
43
- saveDataUrl: () => saveDataUrl,
44
- pruneKind: () => pruneKind,
45
- pruneArtifacts: () => pruneArtifacts,
46
- parseDataUrl: () => parseDataUrl,
43
+ RETENTION: () => RETENTION,
47
44
  artifactDir: () => artifactDir,
48
- RETENTION: () => RETENTION
45
+ parseDataUrl: () => parseDataUrl,
46
+ pruneArtifacts: () => pruneArtifacts,
47
+ pruneKind: () => pruneKind,
48
+ saveDataUrl: () => saveDataUrl
49
49
  });
50
50
  module.exports = __toCommonJS(exports_artifacts);
51
51
  var import_promises = require("fs/promises");
@@ -40,12 +40,12 @@ var __export = (target, all) => {
40
40
  // src/desktop-isolation.ts
41
41
  var exports_desktop_isolation = {};
42
42
  __export(exports_desktop_isolation, {
43
- resolveServerUrl: () => resolveServerUrl,
44
- resolvePublicUrl: () => resolvePublicUrl,
45
- resolveInternalPort: () => resolveInternalPort,
46
- isPrivateInstance: () => isPrivateInstance,
43
+ SHARED_INTERNAL_PORT: () => SHARED_INTERNAL_PORT,
47
44
  SHARED_PUBLIC_URL: () => SHARED_PUBLIC_URL,
48
- SHARED_INTERNAL_PORT: () => SHARED_INTERNAL_PORT
45
+ isPrivateInstance: () => isPrivateInstance,
46
+ resolveInternalPort: () => resolveInternalPort,
47
+ resolvePublicUrl: () => resolvePublicUrl,
48
+ resolveServerUrl: () => resolveServerUrl
49
49
  });
50
50
  module.exports = __toCommonJS(exports_desktop_isolation);
51
51
  var SHARED_PUBLIC_URL = "http://localhost:8700";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "haltija-desktop",
3
- "version": "1.12.2",
3
+ "version": "1.12.5",
4
4
  "private": true,
5
5
  "description": "Haltija Desktop - God Mode Browser for AI Agents",
6
6
  "homepage": "https://github.com/tonioloewald/haltija",
@@ -26,10 +26,10 @@ function resolveServerUrl(inputs) {
26
26
  return injected || persisted || SHARED_PUBLIC_URL;
27
27
  }
28
28
  export {
29
- resolveServerUrl,
30
- resolvePublicUrl,
31
- resolveInternalPort,
32
- isPrivateInstance,
29
+ SHARED_INTERNAL_PORT,
33
30
  SHARED_PUBLIC_URL,
34
- SHARED_INTERNAL_PORT
31
+ isPrivateInstance,
32
+ resolveInternalPort,
33
+ resolvePublicUrl,
34
+ resolveServerUrl
35
35
  };
@@ -40,13 +40,13 @@
40
40
  // src/component.ts
41
41
  var exports_component = {};
42
42
  __export(exports_component, {
43
- inject: () => inject,
43
+ DevChannel: () => DevChannel,
44
44
  VERSION: () => VERSION2,
45
- DevChannel: () => DevChannel
45
+ inject: () => inject
46
46
  });
47
47
 
48
48
  // src/version.ts
49
- var VERSION = "1.12.2";
49
+ var VERSION = "1.12.5";
50
50
 
51
51
  // src/text-selector.ts
52
52
  var TEXT_PSEUDO_RE = /:(?:text-is|has-text|text)\(/;
@@ -4095,25 +4095,21 @@
4095
4095
  let inputDesc = "";
4096
4096
  if (isCleared) {
4097
4097
  inputDesc = `Clear ${inputLabel}`;
4098
- } else if (inputType === "range") {
4099
- inputAction = "set";
4100
- inputDesc = `Set ${inputLabel} to ${inputValue}`;
4101
- } else if (inputType === "select-one" || inputType === "select-multiple") {
4102
- inputAction = "select";
4103
- inputDesc = `Select "${inputValue}" in ${inputLabel}`;
4098
+ } else if (inputType === "range" || inputType === "select-one" || inputType === "select-multiple" || inputType === "date" || inputType === "time" || inputType === "color") {
4099
+ inputAction = "eval";
4100
+ inputDesc = `Set ${inputLabel} to "${inputValue}"`;
4104
4101
  } else if (inputType === "checkbox" || inputType === "radio") {
4105
4102
  inputAction = "check";
4106
4103
  inputDesc = `Check ${inputLabel}`;
4107
- } else if (inputType === "date" || inputType === "time" || inputType === "color") {
4108
- inputAction = "set";
4109
- inputDesc = `Set ${inputLabel} to ${inputValue}`;
4110
4104
  } else {
4111
4105
  inputDesc = `Type "${inputValue}" in ${inputLabel}`;
4112
4106
  }
4113
4107
  steps.push(withFallback({
4114
4108
  action: inputAction,
4115
4109
  selector,
4116
- ...inputAction === "type" || inputAction === "set" ? { text: inputValue } : { value: inputValue },
4110
+ ...inputAction === "eval" ? {
4111
+ code: `const el = document.querySelector(${JSON.stringify(selector)});` + ` el.value = ${JSON.stringify(inputValue)};` + ` el.dispatchEvent(new Event('input', { bubbles: true }));` + ` el.dispatchEvent(new Event('change', { bubbles: true }));`
4112
+ } : inputAction === "type" ? { text: inputValue } : { value: inputValue },
4117
4113
  description: inputDesc,
4118
4114
  ...delay && delay > 50 ? { delay } : {}
4119
4115
  }, event.target));
@@ -4146,7 +4142,7 @@
4146
4142
  case "interaction:select":
4147
4143
  const selectedText = this.cleanDescription(event.payload?.text || "", 50);
4148
4144
  steps.push({
4149
- action: "select",
4145
+ action: "select-text",
4150
4146
  selector,
4151
4147
  text: event.payload?.text || "",
4152
4148
  description: `Select text "${selectedText}"`,
@@ -120,9 +120,9 @@ function cliNameForEndpoint(path) {
120
120
  return isKnownCommand(name) ? name : null;
121
121
  }
122
122
  export {
123
- isKnownCommand,
124
- cliNameForEndpoint,
125
- ROUTED_COMMANDS,
123
+ LOCAL_COMMANDS,
126
124
  LOCAL_COMMAND_HELP,
127
- LOCAL_COMMANDS
125
+ ROUTED_COMMANDS,
126
+ cliNameForEndpoint,
127
+ isKnownCommand
128
128
  };
@@ -1065,6 +1065,40 @@ function exitOnTestFailure(json, subcommand) {
1065
1065
  }
1066
1066
  }
1067
1067
 
1068
+ /**
1069
+ * One endpoint's section out of API.md, or the whole document when no name is given.
1070
+ *
1071
+ * Matches the `### \`METHOD /path\`` headings the generator emits, on the path's last segment, so
1072
+ * `hj api screenshot` and `hj api map` both work. An unknown name returns the full document with a
1073
+ * note on stderr rather than nothing — a lookup that silently prints emptiness is worse than one
1074
+ * that over-delivers, and the caller has already paid for the fetch.
1075
+ */
1076
+ export function filterApiSection(markdown, args) {
1077
+ const wanted = (args || []).find((a) => !a.startsWith('-'))
1078
+ if (!wanted) return markdown
1079
+ const lines = markdown.split('\n')
1080
+ const needle = wanted.replace(/^\/+/, '').toLowerCase()
1081
+ let start = -1
1082
+ for (let i = 0; i < lines.length; i++) {
1083
+ const m = /^### `[A-Z]+ (\/\S*)`/.exec(lines[i])
1084
+ if (!m) continue
1085
+ const seg = m[1].replace(/^\//, '').toLowerCase()
1086
+ if (seg === needle || seg.split('/').pop() === needle) { start = i; break }
1087
+ }
1088
+ if (start === -1) {
1089
+ process.stderr.write(
1090
+ dim(`[hj] no API section named "${wanted}" — printing the full reference. ` +
1091
+ `Section headings look like \`### \`POST /screenshot\`\`.`) + '\n',
1092
+ )
1093
+ return markdown
1094
+ }
1095
+ let end = lines.length
1096
+ for (let i = start + 1; i < lines.length; i++) {
1097
+ if (/^#{2,3} /.test(lines[i])) { end = i; break }
1098
+ }
1099
+ return lines.slice(start, end).join('\n').trimEnd()
1100
+ }
1101
+
1068
1102
  export async function runSubcommand(subcommand, subArgs, port = '8700', options = {}) {
1069
1103
  const baseUrl = `http://localhost:${port}`
1070
1104
  const jsonOutput = subArgs.includes('--json')
@@ -1184,7 +1218,7 @@ export async function runSubcommand(subcommand, subArgs, port = '8700', options
1184
1218
  if (isGet) {
1185
1219
  const url = new URL(path, baseUrl)
1186
1220
  url.searchParams.set('window', targetWindowId)
1187
- return doRequest(url.toString(), 'GET', undefined, { subcommand, jsonOutput })
1221
+ return doRequest(url.toString(), 'GET', undefined, { subcommand, jsonOutput, args: filteredArgs })
1188
1222
  } else {
1189
1223
  if (!body) body = {}
1190
1224
  body.window = targetWindowId
@@ -1192,11 +1226,11 @@ export async function runSubcommand(subcommand, subArgs, port = '8700', options
1192
1226
  }
1193
1227
 
1194
1228
  const url = `${baseUrl}${path}`
1195
- return doRequest(url, isGet ? 'GET' : 'POST', body, { subcommand, jsonOutput })
1229
+ return doRequest(url, isGet ? 'GET' : 'POST', body, { subcommand, jsonOutput, args: filteredArgs })
1196
1230
  }
1197
1231
 
1198
1232
  async function doRequest(url, method, body, context = {}) {
1199
- const { subcommand, jsonOutput } = context
1233
+ const { subcommand, jsonOutput, args: cmdArgs } = context
1200
1234
  try {
1201
1235
  const headers = {}
1202
1236
  if (process.env.HALTIJA_TOKEN) headers['X-Haltija-Token'] = process.env.HALTIJA_TOKEN
@@ -1369,7 +1403,14 @@ async function doRequest(url, method, body, context = {}) {
1369
1403
  }
1370
1404
  } else {
1371
1405
  const text = await resp.text()
1372
- console.log(text)
1406
+ // `hj api <endpoint>` is a LOOKUP, not a dump.
1407
+ //
1408
+ // SKILL.md defers detail to `hj api screenshot` / `hj api map` to keep the always-loaded
1409
+ // prompt small. That only pays off if the pointer works: positionals were dropped for GET
1410
+ // subcommands, so every form returned the whole 64KB file — ~3KB saved from the prompt and
1411
+ // ~16k tokens spent the first time anyone followed the advice. Filtering client-side needs no
1412
+ // server change and no new endpoint.
1413
+ console.log(subcommand === 'api' ? filterApiSection(text, cmdArgs) : text)
1373
1414
  }
1374
1415
 
1375
1416
  // Show hint for this command (if available and successful).
@@ -46,6 +46,14 @@ export function formatTestResult(result) {
46
46
 
47
47
  lines.push(` ${step.index + 1} ${[stepStatus, desc, dur, err].filter(Boolean).join(' ')}`)
48
48
 
49
+ // A step can PASS and still have something to say — currently a deprecated action spelling.
50
+ // Every loop here filtered on failure, so the notice reached nobody: a suite using an old
51
+ // name ran clean and its author found out when the alias was deleted. The whole point of
52
+ // deprecating rather than breaking is that the warning arrives while there is time to act.
53
+ if (step.warning) {
54
+ lines.push(` ! ${step.warning}`)
55
+ }
56
+
49
57
  // Failure context detail
50
58
  if (!step.passed && step.context) {
51
59
  const detail = formatFailureContext(step.context)
package/bin/hj.mjs CHANGED
@@ -196,6 +196,33 @@ async function runWhere(port, portSource, jsonOutput) {
196
196
  )
197
197
  return
198
198
  }
199
+ // TRANSPORTS, present and absent (issue #32). The channel is shared, so an instance that came up
200
+ // HTTP-only silently denies HTTPS pages in OTHER projects — and nothing said so. A half-open
201
+ // channel used to look identical to a full one from here.
202
+ const t = serverInfo.transports
203
+ if (t) {
204
+ const parts = []
205
+ if (t.http) parts.push(t.http.listening ? green(`http ${t.http.port} ✓`) : dim(`http ${t.http.port} ✗`))
206
+ if (t.https) {
207
+ // No port when there is nothing listening and none was assigned — `https 0` is noise.
208
+ const label = t.https.port ? `https ${t.https.port}` : 'https'
209
+ parts.push(
210
+ t.https.listening
211
+ ? green(`${label} ✓`)
212
+ : `${yellow(`${label} ✗`)} ${dim(`(${t.https.reason})`)}`,
213
+ )
214
+ }
215
+ console.log(`${bold('transports:')} ${parts.join(' ')}`)
216
+ // Only for a SHARED channel. A private instance denies nobody else — saying otherwise would be
217
+ // the alarming answer rather than the true one.
218
+ if (t.https && !t.https.listening && !serverInfo.isPrivate) {
219
+ console.log(
220
+ dim(' an HTTPS page cannot import an HTTP channel (mixed content), so any page served over ' +
221
+ 'https has no haltija here — including pages belonging to other projects sharing this channel.'),
222
+ )
223
+ }
224
+ }
225
+
199
226
  const desc = [
200
227
  `haltija ${serverInfo.serverVersion}`,
201
228
  instanceName ? `name=${instanceName}` : null,
package/bin/mcp-setup.mjs CHANGED
@@ -62,7 +62,7 @@ function findMcpServerPath() {
62
62
  try {
63
63
  const result = spawnSync('npm', ['root', '-g'], { encoding: 'utf8' })
64
64
  if (result.status === 0) {
65
- const globalPath = join(result.stdout.trim(), 'tosijs-dev/apps/mcp/build/index.js')
65
+ const globalPath = join(result.stdout.trim(), 'haltija/apps/mcp/build/index.js')
66
66
  if (existsSync(globalPath)) {
67
67
  return globalPath
68
68
  }
@@ -70,7 +70,7 @@ function findMcpServerPath() {
70
70
  } catch {}
71
71
 
72
72
  // Check node_modules in current directory
73
- const nodeModulesPath = join(process.cwd(), 'node_modules/tosijs-dev/apps/mcp/build/index.js')
73
+ const nodeModulesPath = join(process.cwd(), 'node_modules/haltija/apps/mcp/build/index.js')
74
74
  if (existsSync(nodeModulesPath)) {
75
75
  return nodeModulesPath
76
76
  }
@@ -24,6 +24,6 @@ function pidIsAlive(pid) {
24
24
  }
25
25
  }
26
26
  export {
27
- stalePrivateEntries,
28
- pidIsAlive
27
+ pidIsAlive,
28
+ stalePrivateEntries
29
29
  };
@@ -86,8 +86,8 @@ function routeByDeclaredOrigin(declared, tabs, focusedWindowId) {
86
86
  };
87
87
  }
88
88
  export {
89
- routeByDeclaredOrigin,
90
- normalizeOrigin,
89
+ ORIGINS_FILE,
91
90
  findProjectOrigins,
92
- ORIGINS_FILE
91
+ normalizeOrigin,
92
+ routeByDeclaredOrigin
93
93
  };
package/bin/semver.mjs CHANGED
@@ -64,8 +64,8 @@ function differsBeyondPatch(a, b) {
64
64
  return pa.major !== pb.major || pa.minor !== pb.minor;
65
65
  }
66
66
  export {
67
- parseVersion,
68
- isOlderThan,
67
+ compareVersions,
69
68
  differsBeyondPatch,
70
- compareVersions
69
+ isOlderThan,
70
+ parseVersion
71
71
  };
@@ -38,9 +38,9 @@ function isAmbiguousTarget(portSourceKind, resolvedPort, liveInstances) {
38
38
  return { ambiguous: portSourceKind === "default" && others.length > 0, others };
39
39
  }
40
40
  export {
41
- sortRows,
42
- labelFor,
43
- isAmbiguousTarget,
41
+ collectCandidates,
44
42
  describeServer,
45
- collectCandidates
43
+ isAmbiguousTarget,
44
+ labelFor,
45
+ sortRows
46
46
  };
package/bin/version.mjs CHANGED
@@ -3,4 +3,4 @@
3
3
  * ⚠️ To change the version, update package.json and run: bun run build
4
4
  */
5
5
 
6
- export const HJ_VERSION = '1.12.2'
6
+ export const HJ_VERSION = '1.12.5'
@@ -30,10 +30,10 @@ function summarizeWindow(w) {
30
30
  };
31
31
  }
32
32
  export {
33
- visibilityKnown,
34
- summarizeWindow,
35
- isVisibleTab,
36
- isVisible,
33
+ isDrivable,
37
34
  isTopLevelTab,
38
- isDrivable
35
+ isVisible,
36
+ isVisibleTab,
37
+ summarizeWindow,
38
+ visibilityKnown
39
39
  };