@insforge/cli 0.1.44 → 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 +132 -45
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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 ??
|
|
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
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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 ??
|
|
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
|
|
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
|
-
|
|
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:
|
|
822
|
+
timeout: SKILL_INSTALL_TIMEOUT_MS
|
|
745
823
|
});
|
|
746
824
|
if (!json) clack5.log.success("InsForge agent skills installed.");
|
|
747
|
-
} catch {
|
|
748
|
-
if (!json)
|
|
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:
|
|
835
|
+
timeout: SKILL_INSTALL_TIMEOUT_MS
|
|
755
836
|
});
|
|
756
837
|
if (!json) clack5.log.success("find-skills installed.");
|
|
757
|
-
} catch {
|
|
758
|
-
if (!json)
|
|
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
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
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({
|
|
@@ -1215,7 +1301,7 @@ async function copyDir(src, dest) {
|
|
|
1215
1301
|
}
|
|
1216
1302
|
}
|
|
1217
1303
|
function registerCreateCommand(program2) {
|
|
1218
|
-
program2.command("create").description("Create a new InsForge project").option("--name <name>", "Project name").option("--org-id <id>", "Organization ID").option("--region <region>", "Deployment region (us-east, us-west, eu-central, ap-southeast)").option("--template <template>", "Template to use: react, nextjs, chatbot, crm, e-commerce, or empty").action(async (opts, cmd) => {
|
|
1304
|
+
program2.command("create").description("Create a new InsForge project").option("--name <name>", "Project name").option("--org-id <id>", "Organization ID").option("--region <region>", "Deployment region (us-east, us-west, eu-central, ap-southeast)").option("--template <template>", "Template to use: react, nextjs, chatbot, crm, e-commerce, todo, or empty").action(async (opts, cmd) => {
|
|
1219
1305
|
const { json, apiUrl } = getRootOpts(cmd);
|
|
1220
1306
|
try {
|
|
1221
1307
|
await requireAuth(apiUrl, false);
|
|
@@ -1266,7 +1352,7 @@ function registerCreateCommand(program2) {
|
|
|
1266
1352
|
if (projectName.length < 2 || projectName === "." || projectName === "..") {
|
|
1267
1353
|
throw new CLIError("Project name must be at least 2 safe characters (letters, numbers, hyphens).");
|
|
1268
1354
|
}
|
|
1269
|
-
const validTemplates = ["react", "nextjs", "chatbot", "crm", "e-commerce", "empty"];
|
|
1355
|
+
const validTemplates = ["react", "nextjs", "chatbot", "crm", "e-commerce", "todo", "empty"];
|
|
1270
1356
|
let template = opts.template;
|
|
1271
1357
|
if (template && !validTemplates.includes(template)) {
|
|
1272
1358
|
throw new CLIError(`Invalid template "${template}". Valid options: ${validTemplates.join(", ")}`);
|
|
@@ -1296,7 +1382,8 @@ function registerCreateCommand(program2) {
|
|
|
1296
1382
|
{ value: "nextjs", label: "Web app template with Next.js" },
|
|
1297
1383
|
{ value: "chatbot", label: "AI Chatbot with Next.js" },
|
|
1298
1384
|
{ value: "crm", label: "CRM with Next.js" },
|
|
1299
|
-
{ value: "e-commerce", label: "E-Commerce store with Next.js" }
|
|
1385
|
+
{ value: "e-commerce", label: "E-Commerce store with Next.js" },
|
|
1386
|
+
{ value: "todo", label: "Todo app with Next.js" }
|
|
1300
1387
|
]
|
|
1301
1388
|
});
|
|
1302
1389
|
if (clack7.isCancel(selected)) process.exit(0);
|
|
@@ -1359,7 +1446,7 @@ function registerCreateCommand(program2) {
|
|
|
1359
1446
|
saveProjectConfig(projectConfig);
|
|
1360
1447
|
projectLinked = true;
|
|
1361
1448
|
s?.stop(`Project "${project.name}" created and linked`);
|
|
1362
|
-
const githubTemplates = ["chatbot", "crm", "e-commerce", "nextjs", "react"];
|
|
1449
|
+
const githubTemplates = ["chatbot", "crm", "e-commerce", "nextjs", "react", "todo"];
|
|
1363
1450
|
if (githubTemplates.includes(template)) {
|
|
1364
1451
|
await downloadGitHubTemplate(template, projectConfig, json);
|
|
1365
1452
|
} else if (hasTemplate) {
|
|
@@ -1618,7 +1705,7 @@ function buildOssHost2(appkey, region) {
|
|
|
1618
1705
|
return `https://${appkey}.${region}.insforge.app`;
|
|
1619
1706
|
}
|
|
1620
1707
|
function registerProjectLinkCommand(program2) {
|
|
1621
|
-
program2.command("link").description("Link current directory to an InsForge project").option("--project-id <id>", "Project ID to link").option("--org-id <id>", "Organization ID").option("--template <template>", "Download a template after linking: react, nextjs, chatbot, crm, e-commerce").option("--api-base-url <url>", "API Base URL for direct linking (OSS/Self-hosted)").option("--api-key <key>", "API Key for direct linking (OSS/Self-hosted)").action(async (opts, cmd) => {
|
|
1708
|
+
program2.command("link").description("Link current directory to an InsForge project").option("--project-id <id>", "Project ID to link").option("--org-id <id>", "Organization ID").option("--template <template>", "Download a template after linking: react, nextjs, chatbot, crm, e-commerce, todo").option("--api-base-url <url>", "API Base URL for direct linking (OSS/Self-hosted)").option("--api-key <key>", "API Key for direct linking (OSS/Self-hosted)").action(async (opts, cmd) => {
|
|
1622
1709
|
const { json, apiUrl } = getRootOpts(cmd);
|
|
1623
1710
|
try {
|
|
1624
1711
|
if (opts.apiBaseUrl || opts.apiKey) {
|
|
@@ -1649,7 +1736,7 @@ function registerProjectLinkCommand(program2) {
|
|
|
1649
1736
|
}
|
|
1650
1737
|
trackCommand("link", "oss-org", { direct: true });
|
|
1651
1738
|
await installSkills(json);
|
|
1652
|
-
await reportCliUsage("cli.link_direct", true, 6);
|
|
1739
|
+
await reportCliUsage("cli.link_direct", true, 6, projectConfig2);
|
|
1653
1740
|
try {
|
|
1654
1741
|
const urlMatch = opts.apiBaseUrl.match(/^https?:\/\/([^.]+)\.[^.]+\.insforge\.app/);
|
|
1655
1742
|
if (urlMatch) {
|
|
@@ -1747,14 +1834,14 @@ function registerProjectLinkCommand(program2) {
|
|
|
1747
1834
|
outputSuccess(`Linked to project "${project.name}" (${project.appkey}.${project.region})`);
|
|
1748
1835
|
}
|
|
1749
1836
|
await installSkills(json);
|
|
1750
|
-
await reportCliUsage("cli.link", true, 6);
|
|
1837
|
+
await reportCliUsage("cli.link", true, 6, projectConfig);
|
|
1751
1838
|
try {
|
|
1752
1839
|
await reportAgentConnected({ project_id: project.id }, apiUrl);
|
|
1753
1840
|
} catch {
|
|
1754
1841
|
}
|
|
1755
1842
|
const template = opts.template;
|
|
1756
1843
|
if (template) {
|
|
1757
|
-
const validTemplates = ["react", "nextjs", "chatbot", "crm", "e-commerce"];
|
|
1844
|
+
const validTemplates = ["react", "nextjs", "chatbot", "crm", "e-commerce", "todo"];
|
|
1758
1845
|
if (!validTemplates.includes(template)) {
|
|
1759
1846
|
throw new CLIError(`Invalid template "${template}". Valid options: ${validTemplates.join(", ")}`);
|
|
1760
1847
|
}
|
|
@@ -1785,7 +1872,7 @@ function registerProjectLinkCommand(program2) {
|
|
|
1785
1872
|
process.chdir(templateDir);
|
|
1786
1873
|
saveProjectConfig(projectConfig);
|
|
1787
1874
|
captureEvent(orgId ?? project.organization_id, "template_selected", { template, source: "link" });
|
|
1788
|
-
const githubTemplates = ["chatbot", "crm", "e-commerce", "nextjs", "react"];
|
|
1875
|
+
const githubTemplates = ["chatbot", "crm", "e-commerce", "nextjs", "react", "todo"];
|
|
1789
1876
|
if (githubTemplates.includes(template)) {
|
|
1790
1877
|
await downloadGitHubTemplate(template, projectConfig, json);
|
|
1791
1878
|
} else {
|