@insforge/cli 0.1.45 → 0.1.46

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/dist/index.js CHANGED
@@ -106,6 +106,39 @@ var ProjectNotLinkedError = class extends CLIError {
106
106
  super("No project linked. Run `npx @insforge/cli link` first.", 3, "PROJECT_NOT_LINKED");
107
107
  }
108
108
  };
109
+ function formatFetchError(err, url) {
110
+ if (!(err instanceof Error)) return `Network request to ${url} failed: ${String(err)}`;
111
+ if (err.message !== "fetch failed") return err.message;
112
+ const cause = err.cause;
113
+ const code = cause?.code ?? cause?.errno;
114
+ const causeMsg = cause instanceof Error ? cause.message : typeof cause === "string" ? cause : void 0;
115
+ let host = url;
116
+ try {
117
+ host = new URL(url).host;
118
+ } catch {
119
+ }
120
+ switch (code) {
121
+ case "ENOTFOUND":
122
+ case "EAI_AGAIN":
123
+ return `Cannot resolve ${host} (DNS lookup failed: ${code}). Check your internet connection or DNS settings.`;
124
+ case "ECONNREFUSED":
125
+ return `Connection to ${host} refused. The server may be down or blocked by a firewall.`;
126
+ case "ETIMEDOUT":
127
+ case "UND_ERR_CONNECT_TIMEOUT":
128
+ return `Connection to ${host} timed out. Check your network, VPN, or proxy settings.`;
129
+ case "ECONNRESET":
130
+ case "UND_ERR_SOCKET":
131
+ return `Connection to ${host} was reset. A proxy, VPN, or firewall may be interfering.`;
132
+ case "CERT_HAS_EXPIRED":
133
+ case "UNABLE_TO_VERIFY_LEAF_SIGNATURE":
134
+ case "SELF_SIGNED_CERT_IN_CHAIN":
135
+ case "DEPTH_ZERO_SELF_SIGNED_CERT":
136
+ return `TLS certificate error contacting ${host} (${code}). Your network may be intercepting HTTPS (corporate proxy / VPN).`;
137
+ }
138
+ if (code) return `Network error contacting ${host}: ${code}${causeMsg ? ` \u2014 ${causeMsg}` : ""}`;
139
+ if (causeMsg) return `Network error contacting ${host}: ${causeMsg}`;
140
+ return `Network error contacting ${host}.`;
141
+ }
109
142
  function getDeploymentError(metadata) {
110
143
  if (!metadata || typeof metadata.error !== "object" || !metadata.error) return null;
111
144
  return metadata.error.errorMessage ?? null;
@@ -167,36 +200,48 @@ function buildAuthorizeUrl(params) {
167
200
  return url.toString();
168
201
  }
169
202
  async function exchangeCodeForTokens(params) {
170
- const res = await fetch(`${params.platformUrl}/api/oauth/v1/token`, {
171
- method: "POST",
172
- headers: { "Content-Type": "application/json" },
173
- body: JSON.stringify({
174
- grant_type: "authorization_code",
175
- code: params.code,
176
- redirect_uri: params.redirectUri,
177
- client_id: params.clientId,
178
- code_verifier: params.codeVerifier
179
- })
180
- });
203
+ const tokenUrl = `${params.platformUrl}/api/oauth/v1/token`;
204
+ let res;
205
+ try {
206
+ res = await fetch(tokenUrl, {
207
+ method: "POST",
208
+ headers: { "Content-Type": "application/json" },
209
+ body: JSON.stringify({
210
+ grant_type: "authorization_code",
211
+ code: params.code,
212
+ redirect_uri: params.redirectUri,
213
+ client_id: params.clientId,
214
+ code_verifier: params.codeVerifier
215
+ })
216
+ });
217
+ } catch (err) {
218
+ throw new Error(`Token exchange failed \u2014 ${formatFetchError(err, tokenUrl)}`, { cause: err });
219
+ }
181
220
  if (!res.ok) {
182
221
  const err = await res.json().catch(() => ({}));
183
- throw new Error(err.error_description ?? err.error ?? "Token exchange failed");
222
+ throw new Error(err.error_description ?? err.error ?? `Token exchange failed (HTTP ${res.status})`);
184
223
  }
185
224
  return await res.json();
186
225
  }
187
226
  async function refreshOAuthToken(params) {
188
- const res = await fetch(`${params.platformUrl}/api/oauth/v1/token`, {
189
- method: "POST",
190
- headers: { "Content-Type": "application/json" },
191
- body: JSON.stringify({
192
- grant_type: "refresh_token",
193
- refresh_token: params.refreshToken,
194
- client_id: params.clientId
195
- })
196
- });
227
+ const tokenUrl = `${params.platformUrl}/api/oauth/v1/token`;
228
+ let res;
229
+ try {
230
+ res = await fetch(tokenUrl, {
231
+ method: "POST",
232
+ headers: { "Content-Type": "application/json" },
233
+ body: JSON.stringify({
234
+ grant_type: "refresh_token",
235
+ refresh_token: params.refreshToken,
236
+ client_id: params.clientId
237
+ })
238
+ });
239
+ } catch (err) {
240
+ throw new Error(`Token refresh failed \u2014 ${formatFetchError(err, tokenUrl)}`, { cause: err });
241
+ }
197
242
  if (!res.ok) {
198
243
  const err = await res.json().catch(() => ({}));
199
- throw new Error(err.error_description ?? err.error ?? "Token refresh failed");
244
+ throw new Error(err.error_description ?? err.error ?? `Token refresh failed (HTTP ${res.status})`);
200
245
  }
201
246
  return await res.json();
202
247
  }
@@ -394,11 +439,22 @@ async function platformFetch(path5, options = {}, apiUrl) {
394
439
  Authorization: `Bearer ${token}`,
395
440
  ...options.headers ?? {}
396
441
  };
397
- const res = await fetch(`${baseUrl}${path5}`, { ...options, headers });
442
+ const fullUrl = `${baseUrl}${path5}`;
443
+ let res;
444
+ try {
445
+ res = await fetch(fullUrl, { ...options, headers });
446
+ } catch (err) {
447
+ throw new CLIError(formatFetchError(err, fullUrl));
448
+ }
398
449
  if (res.status === 401) {
399
450
  const newToken = await refreshAccessToken(apiUrl);
400
451
  headers.Authorization = `Bearer ${newToken}`;
401
- const retryRes = await fetch(`${baseUrl}${path5}`, { ...options, headers });
452
+ let retryRes;
453
+ try {
454
+ retryRes = await fetch(fullUrl, { ...options, headers });
455
+ } catch (err) {
456
+ throw new CLIError(formatFetchError(err, fullUrl));
457
+ }
402
458
  if (!retryRes.ok) {
403
459
  const err = await retryRes.json().catch(() => ({}));
404
460
  throw new CLIError(err.error ?? `Request failed: ${retryRes.status}`, retryRes.status === 403 ? 5 : 1);
@@ -709,6 +765,28 @@ import { join as join2 } from "path";
709
765
  import { promisify } from "util";
710
766
  import * as clack5 from "@clack/prompts";
711
767
  var execAsync = promisify(exec);
768
+ var SKILL_INSTALL_TIMEOUT_MS = 6e4;
769
+ function describeExecError(err) {
770
+ const e = err;
771
+ if (e.killed && (e.signal === "SIGTERM" || e.signal === "SIGKILL")) {
772
+ return `timed out after ${SKILL_INSTALL_TIMEOUT_MS / 1e3}s \u2014 the npm registry may be slow or blocked by your network`;
773
+ }
774
+ if (e.code === "ENOENT") {
775
+ return "`npx` is not on your PATH \u2014 install Node.js 18+ and reopen your shell";
776
+ }
777
+ const stderr = (typeof e.stderr === "string" ? e.stderr : e.stderr?.toString()) ?? "";
778
+ if (/ENOTFOUND|EAI_AGAIN|getaddrinfo/i.test(stderr)) return "cannot reach the npm registry (DNS lookup failed) \u2014 check your internet connection";
779
+ if (/ECONNREFUSED/i.test(stderr)) return "connection to the npm registry was refused \u2014 a proxy or firewall is likely blocking it";
780
+ if (/ETIMEDOUT|ESOCKETTIMEDOUT|network timeout/i.test(stderr)) return "the npm registry timed out \u2014 check your VPN, proxy, or corporate network";
781
+ if (/CERT_HAS_EXPIRED|UNABLE_TO_VERIFY_LEAF_SIGNATURE|SELF_SIGNED_CERT/i.test(stderr)) return "TLS error reaching the npm registry \u2014 a corporate proxy may be intercepting HTTPS";
782
+ if (/\bE404\b|404 Not Found/i.test(stderr)) return "npm returned 404 \u2014 the `skills` package or a dependency could not be found (check your npm registry config)";
783
+ if (/EACCES|permission denied/i.test(stderr)) return "permission denied writing files \u2014 run from a directory you own, without sudo";
784
+ if (/ENOSPC|no space left/i.test(stderr)) return "no disk space left to install the package";
785
+ if (/\b401\b|EAUTH|authentication/i.test(stderr)) return "npm authentication failed \u2014 check ~/.npmrc";
786
+ if (typeof e.code === "number") return `npx exited with code ${e.code}`;
787
+ if (typeof e.code === "string") return e.code;
788
+ return e.message ?? "unknown error";
789
+ }
712
790
  var GITIGNORE_ENTRIES = [
713
791
  ".insforge",
714
792
  ".agent",
@@ -741,33 +819,41 @@ async function installSkills(json) {
741
819
  if (!json) clack5.log.info("Installing InsForge agent skills...");
742
820
  await execAsync("npx skills add insforge/agent-skills -y -a antigravity -a augment -a claude-code -a cline -a codex -a cursor -a gemini-cli -a github-copilot -a kilo -a qoder -a qwen-code -a roo -a trae -a windsurf", {
743
821
  cwd: process.cwd(),
744
- timeout: 6e4
822
+ timeout: SKILL_INSTALL_TIMEOUT_MS
745
823
  });
746
824
  if (!json) clack5.log.success("InsForge agent skills installed.");
747
- } catch {
748
- if (!json) clack5.log.warn("Failed to install agent skills. You can run manually: npx skills add insforge/agent-skills");
825
+ } catch (err) {
826
+ if (!json) {
827
+ clack5.log.warn(`Could not install agent skills: ${describeExecError(err)}`);
828
+ clack5.log.info("Run `npx skills add insforge/agent-skills` once resolved to see the full output.");
829
+ }
749
830
  }
750
831
  try {
751
832
  if (!json) clack5.log.info("Installing find-skills...");
752
833
  await execAsync("npx skills add https://github.com/vercel-labs/skills --skill find-skills -y", {
753
834
  cwd: process.cwd(),
754
- timeout: 6e4
835
+ timeout: SKILL_INSTALL_TIMEOUT_MS
755
836
  });
756
837
  if (!json) clack5.log.success("find-skills installed.");
757
- } catch {
758
- if (!json) clack5.log.warn("Failed to install find-skills. You can run manually: npx skills add https://github.com/vercel-labs/skills --skill find-skills");
838
+ } catch (err) {
839
+ if (!json) {
840
+ clack5.log.warn(`Could not install find-skills: ${describeExecError(err)}`);
841
+ clack5.log.info("Run `npx skills add https://github.com/vercel-labs/skills --skill find-skills` once resolved.");
842
+ }
759
843
  }
760
844
  try {
761
845
  updateGitignore();
762
846
  } catch {
763
847
  }
764
848
  }
765
- async function reportCliUsage(toolName, success, maxRetries = 1) {
766
- let config;
767
- try {
768
- config = getProjectConfig();
769
- } catch {
770
- return;
849
+ async function reportCliUsage(toolName, success, maxRetries = 1, explicitConfig) {
850
+ let config = explicitConfig;
851
+ if (!config) {
852
+ try {
853
+ config = getProjectConfig();
854
+ } catch {
855
+ return;
856
+ }
771
857
  }
772
858
  if (!config) return;
773
859
  const payload = JSON.stringify({
@@ -1650,7 +1736,7 @@ function registerProjectLinkCommand(program2) {
1650
1736
  }
1651
1737
  trackCommand("link", "oss-org", { direct: true });
1652
1738
  await installSkills(json);
1653
- await reportCliUsage("cli.link_direct", true, 6);
1739
+ await reportCliUsage("cli.link_direct", true, 6, projectConfig2);
1654
1740
  try {
1655
1741
  const urlMatch = opts.apiBaseUrl.match(/^https?:\/\/([^.]+)\.[^.]+\.insforge\.app/);
1656
1742
  if (urlMatch) {
@@ -1748,7 +1834,7 @@ function registerProjectLinkCommand(program2) {
1748
1834
  outputSuccess(`Linked to project "${project.name}" (${project.appkey}.${project.region})`);
1749
1835
  }
1750
1836
  await installSkills(json);
1751
- await reportCliUsage("cli.link", true, 6);
1837
+ await reportCliUsage("cli.link", true, 6, projectConfig);
1752
1838
  try {
1753
1839
  await reportAgentConnected({ project_id: project.id }, apiUrl);
1754
1840
  } catch {