@sunilaleti/hx 1.1.0 → 1.1.1

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
@@ -28,6 +28,20 @@ the project follows [semantic versioning](https://semver.org/).
28
28
 
29
29
  ---
30
30
 
31
+ ## [1.1.1] - 2026-10-31
32
+
33
+ ### Fixed
34
+
35
+ - HTTPS failed on macOS for every host - OpenRouter, ChatGPT, Tavily, npm - with "unable to get
36
+ local issuer certificate". HX verified against the OS trust store alone, and on macOS Node reads
37
+ only the certificates trusted by hand there, not the system roots. HX now trusts Node's own
38
+ roots and the OS store together, so a corporate CA in the keychain still works.
39
+ - A certificate HX will not accept is reported with what to do about it - add the CA to the
40
+ system trust store or set `HX_CA_BUNDLE` - instead of Node's advice to restart it with
41
+ `--use-system-ca`, and is not retried: another attempt meets the same certificate.
42
+
43
+ ---
44
+
31
45
  ## [1.1.0] - 2026-10-31
32
46
 
33
47
  ### Added
@@ -1123,7 +1137,8 @@ First release, published to PyPI as [`hx-cli`](https://pypi.org/project/hx-cli/)
1123
1137
  - `/configure` and `hx auth` for the OpenRouter key, `hx upgrade` for
1124
1138
  self-update, and `install.sh` bootstrapping uv with a pinned Python.
1125
1139
 
1126
- [Unreleased]: https://github.com/aletisunil/hx/compare/v1.1.0...HEAD
1140
+ [Unreleased]: https://github.com/aletisunil/hx/compare/v1.1.1...HEAD
1141
+ [1.1.1]: https://github.com/aletisunil/hx/compare/v1.1.0...v1.1.1
1127
1142
  [1.1.0]: https://github.com/aletisunil/hx/compare/v1.0.0...v1.1.0
1128
1143
  [1.0.0]: https://github.com/aletisunil/hx/compare/v0.3.0-beta.1...v1.0.0
1129
1144
  [0.3.0-beta.1]: https://github.com/aletisunil/hx/compare/v0.2.20...v0.3.0-beta.1
package/README.md CHANGED
@@ -293,9 +293,9 @@ number.
293
293
  Credits are *not* in the status bar's cost, which prices tokens: `/cost`
294
294
  under-reports a session that searched.
295
295
 
296
- **Behind a TLS-inspecting proxy.** HX verifies certificates against the
297
- operating system's trust store, so a corporate CA the machine already trusts
298
- needs no setup. When the CA lives only in a file, name it:
296
+ **Behind a TLS-inspecting proxy.** HX verifies certificates against Node's own
297
+ roots and the operating system's trust store together, so a corporate CA the
298
+ machine already trusts needs no setup. When the CA lives only in a file, name it:
299
299
 
300
300
  ```sh
301
301
  export HX_CA_BUNDLE=/path/to/corp-ca.pem # SSL_CERT_FILE and REQUESTS_CA_BUNDLE also work
package/dist/hx.js CHANGED
@@ -534,10 +534,8 @@ function tlsAgentOptions() {
534
534
  const bundle = caBundle();
535
535
  let ca;
536
536
  if (bundle !== null) ca = certificates(readText(bundle));
537
- else {
538
- ca = tls.getCACertificates("system");
539
- if (ca.length === 0) ca = tls.getCACertificates("default");
540
- }
537
+ else
538
+ ca = [.../* @__PURE__ */ new Set([...tls.getCACertificates("default"), ...tls.getCACertificates("system")])];
541
539
  tlsOptions = { ca, ALPNProtocols: ["http/1.1"] };
542
540
  }
543
541
  return tlsOptions;
@@ -582,12 +580,10 @@ function withTlsHint(message2) {
582
580
  return `${rstrip(rstrip(message2), ".")}. ${hint2}`;
583
581
  }
584
582
  function describe(error) {
583
+ const certificate = certificateFailure(error);
584
+ if (certificate !== null) return certificate;
585
585
  for (const err of chain(error)) {
586
586
  const code2 = err.code ?? "";
587
- if (VERIFY_CODES.has(code2)) {
588
- const reason = flatten(err.message || "certificate verify failed");
589
- return `TLS certificate verification failed (${reason}). ${tlsHint(reason)}`;
590
- }
591
587
  if (code2.startsWith("ERR_SSL") || code2.startsWith("ERR_TLS")) {
592
588
  return `TLS handshake failed: ${flatten(err.message)}.`;
593
589
  }
@@ -599,6 +595,19 @@ function describe(error) {
599
595
  const failure2 = error;
600
596
  return withTlsHint(`${failure2?.name ?? "Error"}: ${flatten(String(failure2?.message ?? error))}`);
601
597
  }
598
+ function certificateFailure(error) {
599
+ for (const err of chain(error)) {
600
+ if (!VERIFY_CODES.has(err.code ?? "")) continue;
601
+ const reason = flatten(
602
+ (err.message || "certificate verify failed").replace(
603
+ /;\s*if the root CA[\s\S]*$/,
604
+ ""
605
+ )
606
+ );
607
+ return `TLS certificate verification failed (${reason}). ${tlsHint(reason)}`;
608
+ }
609
+ return null;
610
+ }
602
611
  function chain(error) {
603
612
  const seen = [];
604
613
  let current = error;
@@ -1725,11 +1734,17 @@ function streamEnd(stopReason, fields = {}) {
1725
1734
  planWindows: fields.planWindows ?? []
1726
1735
  };
1727
1736
  }
1737
+ function unreached(error) {
1738
+ const certificate = certificateFailure(error);
1739
+ if (certificate !== null) return new ProviderError(certificate);
1740
+ return new ProviderError(error.message, { retryable: true });
1741
+ }
1728
1742
  var delta, ProviderError;
1729
1743
  var init_base = __esm({
1730
1744
  "src/providers/base.ts"() {
1731
1745
  "use strict";
1732
1746
  init_usage();
1747
+ init_net();
1733
1748
  delta = (fields) => ({
1734
1749
  kind: "delta",
1735
1750
  ...fields
@@ -3288,7 +3303,7 @@ var init_openrouter = __esm({
3288
3303
  });
3289
3304
  } catch (error) {
3290
3305
  if (signal.aborted) throw error;
3291
- throw new ProviderError(error.message, { retryable: true });
3306
+ throw unreached(error);
3292
3307
  }
3293
3308
  try {
3294
3309
  if (response.status >= 400) {
@@ -7190,7 +7205,7 @@ async function unaryCall(baseUrl, path, body, operation, timeout = 600, signal)
7190
7205
  [status3, payload] = await post(`${baseUrl}${path}`, body, UNARY_HEADERS, timeout, signal);
7191
7206
  } catch (error) {
7192
7207
  if (signal?.aborted) throw error;
7193
- throw new ProviderError(error.message, { retryable: true });
7208
+ throw unreached(error);
7194
7209
  }
7195
7210
  if (status3 >= 400) throw httpError(operation, status3, payload);
7196
7211
  return payload;
@@ -7486,7 +7501,7 @@ var init_devin2 = __esm({
7486
7501
  });
7487
7502
  } catch (error) {
7488
7503
  if (signal.aborted) throw error;
7489
- throw new ProviderError(error.message, { retryable: true });
7504
+ throw unreached(error);
7490
7505
  }
7491
7506
  try {
7492
7507
  if (response.status >= 400) {
@@ -67733,7 +67748,7 @@ init_net();
67733
67748
  init_py();
67734
67749
 
67735
67750
  // src/version.ts
67736
- var VERSION = "1.1.0";
67751
+ var VERSION = "1.1.1";
67737
67752
  var PACKAGE = "@sunilaleti/hx";
67738
67753
 
67739
67754
  // src/providers/codex.ts
@@ -67805,7 +67820,7 @@ var CodexProvider = class {
67805
67820
  });
67806
67821
  } catch (error) {
67807
67822
  if (signal.aborted) throw error;
67808
- throw new ProviderError(error.message, { retryable: true });
67823
+ throw unreached(error);
67809
67824
  }
67810
67825
  const windows = planWindows(response.headers);
67811
67826
  try {