@opennous/cli 0.12.1 → 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 +11 -0
- package/package.json +2 -1
- package/src/certHint.js +46 -0
- package/src/index.js +26 -10
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.
|
|
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": [
|
package/src/certHint.js
ADDED
|
@@ -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
|
-
// "
|
|
45
|
+
// "Theo Bramwell · Fernpoint · admin · AE"
|
|
36
46
|
function identityLine(who) {
|
|
37
47
|
const y = who?.you || {};
|
|
38
48
|
return [
|
|
@@ -45,7 +55,7 @@ function identityLine(who) {
|
|
|
45
55
|
// The one thing left after signing in: install the plugin in the agent.
|
|
46
56
|
function printNextSteps() {
|
|
47
57
|
console.log(` ${dim("Next — install the plugin in Claude Code:")}`);
|
|
48
|
-
console.log(` /plugin marketplace add NousC/
|
|
58
|
+
console.log(` /plugin marketplace add NousC/revenue-plugin`);
|
|
49
59
|
console.log(` /plugin install opennous@opennous\n`);
|
|
50
60
|
}
|
|
51
61
|
|
|
@@ -114,11 +124,17 @@ function apiClient() {
|
|
|
114
124
|
if (v !== undefined && v !== null) url.searchParams.set(k, String(v));
|
|
115
125
|
}
|
|
116
126
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
|
|
@@ -361,7 +377,7 @@ function writeJsonMcp(path, apiUrl) {
|
|
|
361
377
|
* 2. Write ./.mcp.json in the current project (Claude Code and most clients read it).
|
|
362
378
|
* We never fail the flow on this — a printed fallback is better than an abort.
|
|
363
379
|
*/
|
|
364
|
-
const PLUGIN_MARKETPLACE = "NousC/
|
|
380
|
+
const PLUGIN_MARKETPLACE = "NousC/revenue-plugin";
|
|
365
381
|
const PLUGIN_ID = "opennous@opennous";
|
|
366
382
|
|
|
367
383
|
// Claude Code → install the OpenNous PLUGIN (skills + routing hooks + the trimmed MCP, in one).
|
|
@@ -441,7 +457,7 @@ program
|
|
|
441
457
|
console.log(dim(" (Already have it open? Start a new session so the skills load.)"));
|
|
442
458
|
} else {
|
|
443
459
|
console.log("\nDetected Claude Code. Install the OpenNous plugin (skills + hooks + tools):");
|
|
444
|
-
console.log(" /plugin marketplace add NousC/
|
|
460
|
+
console.log(" /plugin marketplace add NousC/revenue-plugin");
|
|
445
461
|
console.log(" /plugin install opennous@opennous");
|
|
446
462
|
console.log(dim(" Then open Claude Code — OpenNous starts onboarding on its own."));
|
|
447
463
|
}
|