@openluxeco/cli 0.5.0 → 0.5.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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/src/api.js +33 -6
  3. package/src/cli.js +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openluxeco/cli",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Official OpenLuxe command-line client — drive the OpenLuxe v1 API from your terminal or an AI agent.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/api.js CHANGED
@@ -8,6 +8,28 @@ export class ApiError extends Error {
8
8
  }
9
9
  }
10
10
 
11
+ /**
12
+ * Node's fetch buries the real failure (TLS, DNS, refused) in `error.cause` and
13
+ * reports only "fetch failed" — surface the cause plus an actionable hint.
14
+ */
15
+ function networkErrorMessage(e, url) {
16
+ const cause = e.cause || {};
17
+ const code = cause.code || '';
18
+ const causeMsg = cause.message || '';
19
+ let origin = '';
20
+ try { origin = ' (' + new URL(url).origin + ')'; } catch { /* keep plain */ }
21
+
22
+ let msg = `Network error: ${causeMsg || e.message}${origin}`;
23
+ if (/CERT|certificate|SSL|TLS/i.test(code + ' ' + causeMsg)) {
24
+ msg += '\n Untrusted/self-signed certificate — for a local dev server, retry with OPENLUXE_INSECURE=1';
25
+ } else if (code === 'ENOTFOUND' || code === 'EAI_AGAIN') {
26
+ msg += '\n Host not found — check the API base (OPENLUXE_API_URL, or `openluxe auth status` for the stored base)';
27
+ } else if (code === 'ECONNREFUSED') {
28
+ msg += '\n Connection refused — is the server running at that base URL?';
29
+ }
30
+ return msg;
31
+ }
32
+
11
33
  /**
12
34
  * Perform an authenticated v1 API request. `path` is everything after
13
35
  * /api/v1 (e.g. "/contacts"). `query` is an object, `body` is JSON-able.
@@ -36,7 +58,7 @@ export async function request(method, path, { query, body, token, base } = {}) {
36
58
  try {
37
59
  res = await fetch(url, init);
38
60
  } catch (e) {
39
- throw new ApiError(0, `Network error: ${e.message}`);
61
+ throw new ApiError(0, networkErrorMessage(e, url));
40
62
  }
41
63
 
42
64
  const text = await res.text();
@@ -55,11 +77,16 @@ export async function request(method, path, { query, body, token, base } = {}) {
55
77
  /** Unauthenticated POST used only by the device-auth handshake. */
56
78
  export async function postPublic(base, path, body) {
57
79
  const url = base.replace(/\/$/, '') + '/api/v1' + path;
58
- const res = await fetch(url, {
59
- method: 'POST',
60
- headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
61
- body: JSON.stringify(body || {}),
62
- });
80
+ let res;
81
+ try {
82
+ res = await fetch(url, {
83
+ method: 'POST',
84
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
85
+ body: JSON.stringify(body || {}),
86
+ });
87
+ } catch (e) {
88
+ throw new ApiError(0, networkErrorMessage(e, url));
89
+ }
63
90
  const text = await res.text();
64
91
  let parsed;
65
92
  try { parsed = text ? JSON.parse(text) : null; } catch { parsed = text; }
package/src/cli.js CHANGED
@@ -187,7 +187,7 @@ async function callApi(method, path, { positionals = [], flags = {}, body }) {
187
187
  printInsufficientCredits(e.body || {});
188
188
  process.exit(1);
189
189
  }
190
- console.error(C.red(`✗ ${e.status} ${e.message}`));
190
+ console.error(C.red('✗ ' + (e.status ? e.status + ' ' : '') + e.message));
191
191
  if (e.body && typeof e.body === 'object') console.error(C.dim(JSON.stringify(e.body, null, 2)));
192
192
  if (e.status === 401) console.error(C.dim(' Run: openluxe auth login'));
193
193
  process.exit(1);