@opennous/cli 0.12.2 → 0.12.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/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @opennous/cli changelog
2
2
 
3
+ ## 0.12.3
4
+
5
+ ### Fixed: a plain message when the server's certificate isn't trusted
6
+
7
+ Against a self-hosted install with Caddy's local certificate, the CLI used to crash with
8
+ a Node stack trace (`UNABLE_TO_GET_ISSUER_CERT_LOCALLY`). It now says to point
9
+ `NODE_EXTRA_CA_CERTS` at the root certificate and links the self-host guide (GET-1011).
10
+
11
+ On Windows, where `api.localhost` doesn't resolve until the hosts file names it, it says which
12
+ line to add (GET-1015).
13
+
3
14
  ## 0.10.1
4
15
 
5
16
  ### Changed — new default vault folder structure
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opennous/cli",
3
- "version": "0.12.2",
3
+ "version": "0.12.3",
4
4
  "description": "Nous CLI — set up Nous, connect it to your coding agent, and read the Context API from your terminal",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,6 +13,7 @@
13
13
  ],
14
14
  "scripts": {
15
15
  "dev": "node src/index.js",
16
+ "test": "node ../../scripts/run-tests.mjs",
16
17
  "start": "node src/index.js"
17
18
  },
18
19
  "keywords": [
@@ -0,0 +1,46 @@
1
+ // A self-hosted Nous on a laptop serves https://api.localhost with Caddy's own local CA, which
2
+ // Node does not trust. fetch then rejects with a bare "fetch failed" whose cause carries one of
3
+ // these codes. Name the fix in one line instead of printing a stack trace (GET-1011).
4
+ const UNTRUSTED_CERT_CODES = new Set([
5
+ "UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
6
+ "UNABLE_TO_GET_ISSUER_CERT",
7
+ "SELF_SIGNED_CERT_IN_CHAIN",
8
+ "DEPTH_ZERO_SELF_SIGNED_CERT",
9
+ "UNABLE_TO_VERIFY_LEAF_SIGNATURE",
10
+ ]);
11
+
12
+ export const CERT_DOCS_URL =
13
+ "https://github.com/NousC/opennous/blob/main/docs/self-host-bundled.md#trying-it-on-a-laptop";
14
+
15
+ /** The cert code on an error or anywhere down its `cause` chain, else null. */
16
+ export function untrustedCertCode(err) {
17
+ for (let e = err, depth = 0; e && depth < 5; e = e.cause, depth++) {
18
+ if (typeof e.code === "string" && UNTRUSTED_CERT_CODES.has(e.code)) return e.code;
19
+ }
20
+ return null;
21
+ }
22
+
23
+ /** The *.localhost name on a "not found" error down the cause chain, else null. */
24
+ function unresolvedLocalhost(err) {
25
+ for (let e = err, depth = 0; e && depth < 5; e = e.cause, depth++) {
26
+ if (e.code === "ENOTFOUND" && /\.localhost$/i.test(e.hostname || "")) return e.hostname;
27
+ }
28
+ return null;
29
+ }
30
+
31
+ /** One line naming the fix for a laptop self-host the CLI can't reach (untrusted certificate, or a
32
+ * *.localhost name Windows doesn't resolve), or null when the error is something else. */
33
+ export function certHint(err) {
34
+ // Windows resolves only "localhost" itself; the subdomains need the hosts file (GET-1015).
35
+ const host = unresolvedLocalhost(err);
36
+ if (host) {
37
+ return `Couldn't connect: ${host} doesn't resolve on this machine (Windows only resolves "localhost" itself). ` +
38
+ `Add "127.0.0.1 api.localhost mcp.localhost" to your hosts file and run it again. ` +
39
+ `How: ${CERT_DOCS_URL}`;
40
+ }
41
+ const code = untrustedCertCode(err);
42
+ if (!code) return null;
43
+ return `Couldn't connect: this server's TLS certificate isn't trusted by Node (${code}). ` +
44
+ `Set NODE_EXTRA_CA_CERTS to your server's CA certificate (for a local self-host, Caddy's root.crt) and run it again. ` +
45
+ `How: ${CERT_DOCS_URL}`;
46
+ }
package/src/index.js CHANGED
@@ -12,6 +12,16 @@ import { homedir, hostname } from "os";
12
12
  import { join, resolve, dirname, relative, sep } from "path";
13
13
  import { createInterface } from "readline";
14
14
  import { spawn, spawnSync } from "child_process";
15
+ import { certHint } from "./certHint.js";
16
+
17
+ // An untrusted certificate (a local self-host behind Caddy's own CA) surfaces as a rejected
18
+ // fetch somewhere in an async action. Print the one-line fix instead of a stack trace; anything
19
+ // else still fails the way it always did.
20
+ process.on("unhandledRejection", (err) => {
21
+ const hint = certHint(err);
22
+ if (hint) { console.error(hint); process.exit(1); }
23
+ throw err;
24
+ });
15
25
 
16
26
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
17
27
 
@@ -32,7 +42,7 @@ async function fetchWhoami(apiUrl, key) {
32
42
  return await r.json();
33
43
  } catch { return null; }
34
44
  }
35
- // "Bennet Glinder · WindSeeker AI · admin · AE"
45
+ // "Theo Bramwell · Fernpoint · admin · AE"
36
46
  function identityLine(who) {
37
47
  const y = who?.you || {};
38
48
  return [
@@ -114,11 +124,17 @@ function apiClient() {
114
124
  if (v !== undefined && v !== null) url.searchParams.set(k, String(v));
115
125
  }
116
126
  }
117
- const res = await fetch(url.toString(), {
118
- method,
119
- headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
120
- body: body ? JSON.stringify(body) : undefined,
121
- });
127
+ let res;
128
+ try {
129
+ res = await fetch(url.toString(), {
130
+ method,
131
+ headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
132
+ body: body ? JSON.stringify(body) : undefined,
133
+ });
134
+ } catch (e) {
135
+ console.error(certHint(e) || `Couldn't reach ${apiUrl}: ${e.message}`);
136
+ process.exit(1);
137
+ }
122
138
  if (!res.ok) {
123
139
  const err = await res.json().catch(() => ({ message: res.statusText }));
124
140
  console.error(`Error: ${err.error || err.message}`);
@@ -245,7 +261,7 @@ async function browserLogin(url, opts = {}) {
245
261
  }
246
262
  start = await r.json();
247
263
  } catch (e) {
248
- console.error(`Couldn't start: ${e.message}`);
264
+ console.error(certHint(e) || `Couldn't start: ${e.message}`);
249
265
  process.exit(1);
250
266
  }
251
267