@insforge/cli 0.1.57 → 0.1.58

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
@@ -539,6 +539,9 @@ To sign in, open this URL in your browser:
539
539
 
540
540
  // src/lib/credentials.ts
541
541
  import * as clack3 from "@clack/prompts";
542
+ function isPatLogin(creds) {
543
+ return creds?.refresh_token?.startsWith("uak_") ?? false;
544
+ }
542
545
  async function requireAuth(apiUrl, allowOssBypass = true) {
543
546
  const projConfig = getProjectConfig();
544
547
  if (allowOssBypass && projConfig?.project_id === FAKE_PROJECT_ID) {
@@ -556,6 +559,10 @@ async function requireAuth(apiUrl, allowOssBypass = true) {
556
559
  }
557
560
  const creds = getCredentials();
558
561
  if (creds && creds.access_token) return creds;
562
+ if (isPatLogin(creds)) {
563
+ await refreshAccessToken(apiUrl);
564
+ return getCredentials();
565
+ }
559
566
  clack3.log.info("You need to log in to continue.");
560
567
  for (; ; ) {
561
568
  try {
@@ -573,10 +580,43 @@ async function requireAuth(apiUrl, allowOssBypass = true) {
573
580
  }
574
581
  async function refreshAccessToken(apiUrl) {
575
582
  const creds = getCredentials();
576
- if (!creds?.refresh_token) {
577
- throw new AuthError("Refresh token not found. Run `npx @insforge/cli login` again.");
583
+ if (!creds) {
584
+ throw new AuthError("Not logged in. Run `npx @insforge/cli login` first.");
578
585
  }
579
586
  const platformUrl = getPlatformApiUrl(apiUrl);
587
+ if (isPatLogin(creds)) {
588
+ let res;
589
+ try {
590
+ res = await fetch(`${platformUrl}/auth/v1/exchange-api-key`, {
591
+ method: "POST",
592
+ headers: { "Content-Type": "application/json" },
593
+ body: JSON.stringify({ apiKey: creds.refresh_token })
594
+ });
595
+ } catch {
596
+ throw new AuthError(
597
+ `Unable to reach auth server at ${platformUrl}. Check your network connection.`
598
+ );
599
+ }
600
+ if (!res.ok) {
601
+ if (res.status === 401 || res.status === 403 || res.status === 404) {
602
+ throw new AuthError(
603
+ "API key is invalid or revoked. Run `npx @insforge/cli login --user-api-key <new-key>` again."
604
+ );
605
+ }
606
+ throw new AuthError(
607
+ `Auth server returned HTTP ${res.status} while refreshing session. Please retry shortly.`
608
+ );
609
+ }
610
+ const data = await res.json().catch(() => ({}));
611
+ if (typeof data.token !== "string" || data.token.length === 0) {
612
+ throw new AuthError("Exchange endpoint returned an invalid response (missing token).");
613
+ }
614
+ saveCredentials({ ...creds, access_token: data.token });
615
+ return data.token;
616
+ }
617
+ if (!creds.refresh_token) {
618
+ throw new AuthError("Refresh token not found. Run `npx @insforge/cli login` again.");
619
+ }
580
620
  const config = getGlobalConfig();
581
621
  const clientId = config.oauth_client_id ?? DEFAULT_CLIENT_ID;
582
622
  try {
@@ -774,10 +814,12 @@ async function createProject(orgId, name, region, apiUrl) {
774
814
 
775
815
  // src/commands/login.ts
776
816
  function registerLoginCommand(program2) {
777
- program2.command("login").description("Authenticate with InsForge platform").option("--email", "Login with email and password instead of browser").option("--client-id <id>", "OAuth client ID (defaults to insforge-cli)").action(async (opts, cmd) => {
817
+ program2.command("login").description("Authenticate with InsForge platform").option("--email", "Login with email and password instead of browser").option("--client-id <id>", "OAuth client ID (defaults to insforge-cli)").option("--user-api-key <key>", "Authenticate with a uak_ personal access token").action(async (opts, cmd) => {
778
818
  const { json, apiUrl } = getRootOpts(cmd);
779
819
  try {
780
- if (opts.email) {
820
+ if (opts.userApiKey) {
821
+ await loginWithUserApiKey(opts.userApiKey, json, apiUrl);
822
+ } else if (opts.email) {
781
823
  await loginWithEmail(json, apiUrl);
782
824
  } else {
783
825
  await loginWithOAuth(json, apiUrl);
@@ -846,6 +888,78 @@ async function loginWithOAuth(json, apiUrl) {
846
888
  console.log(JSON.stringify({ success: true, user: creds.user }));
847
889
  }
848
890
  }
891
+ async function loginWithUserApiKey(key, json, apiUrl) {
892
+ if (!json) {
893
+ clack4.intro("InsForge CLI");
894
+ }
895
+ if (!key.startsWith("uak_")) {
896
+ throw new CLIError('Invalid API key \u2014 must start with "uak_".');
897
+ }
898
+ const s = !json ? clack4.spinner() : null;
899
+ s?.start("Verifying API key...");
900
+ let jwt;
901
+ let user;
902
+ try {
903
+ const exchanged = await exchangePatForJwt(key, apiUrl);
904
+ jwt = exchanged.token;
905
+ user = exchanged.user;
906
+ } catch (err) {
907
+ s?.stop("API key verification failed");
908
+ throw err instanceof CLIError ? err : new CLIError(err instanceof Error ? err.message : String(err));
909
+ }
910
+ saveCredentials({
911
+ access_token: jwt,
912
+ refresh_token: key,
913
+ user
914
+ });
915
+ if (!json) {
916
+ s?.stop(`Authenticated as ${user.email}`);
917
+ clack4.outro("Done");
918
+ } else {
919
+ console.log(JSON.stringify({ success: true, user }));
920
+ }
921
+ }
922
+ async function exchangePatForJwt(apiKey, apiUrl) {
923
+ const baseUrl = getPlatformApiUrl(apiUrl);
924
+ const fullUrl = `${baseUrl}/auth/v1/exchange-api-key`;
925
+ let res;
926
+ try {
927
+ res = await fetch(fullUrl, {
928
+ method: "POST",
929
+ headers: { "Content-Type": "application/json" },
930
+ body: JSON.stringify({ apiKey })
931
+ });
932
+ } catch (err) {
933
+ throw new CLIError(formatFetchError(err, fullUrl));
934
+ }
935
+ if (!res.ok) {
936
+ const body = await res.json().catch(() => ({}));
937
+ const msg = body.message ?? body.error ?? `HTTP ${res.status}`;
938
+ throw new CLIError(`API key is invalid or revoked: ${msg}`);
939
+ }
940
+ const data = await res.json().catch(() => ({}));
941
+ if (typeof data.token !== "string" || data.token.length === 0) {
942
+ throw new CLIError("Exchange endpoint returned an invalid response (missing token).");
943
+ }
944
+ const jwt = data.token;
945
+ let profileRes;
946
+ try {
947
+ profileRes = await fetch(`${baseUrl}/auth/v1/profile`, {
948
+ headers: { Authorization: `Bearer ${jwt}` }
949
+ });
950
+ } catch (err) {
951
+ throw new CLIError(formatFetchError(err, `${baseUrl}/auth/v1/profile`));
952
+ }
953
+ if (!profileRes.ok) {
954
+ throw new CLIError(`Exchange succeeded but could not fetch profile: HTTP ${profileRes.status}`);
955
+ }
956
+ const profile = await profileRes.json().catch(() => null);
957
+ const user = profile && typeof profile === "object" && "user" in profile ? profile.user : profile ?? void 0;
958
+ if (!user) {
959
+ throw new CLIError("Exchange succeeded but profile response was empty");
960
+ }
961
+ return { token: jwt, user };
962
+ }
849
963
 
850
964
  // src/lib/output.ts
851
965
  import Table from "cli-table3";
@@ -1208,10 +1322,11 @@ async function ossFetch(path5, options = {}) {
1208
1322
  message += `
1209
1323
  ${err.nextActions}`;
1210
1324
  }
1211
- if (res.status === 404 && path5.startsWith("/api/compute")) {
1325
+ const isRouteLevel404 = !err.error || err.error === "NOT_FOUND";
1326
+ if (res.status === 404 && isRouteLevel404 && path5.startsWith("/api/compute")) {
1212
1327
  message = "Compute services are not available on this backend.\nSelf-hosted: upgrade your InsForge instance. Cloud: contact your InsForge admin to enable compute.";
1213
1328
  }
1214
- if (res.status === 404 && path5 === "/api/database/migrations") {
1329
+ if (res.status === 404 && isRouteLevel404 && path5 === "/api/database/migrations") {
1215
1330
  message = "Database migrations are not available on this backend.\nSelf-hosted: upgrade your InsForge instance. Cloud: contact your InsForge admin about database migration support.";
1216
1331
  }
1217
1332
  throw new CLIError(message);
@@ -5602,7 +5717,7 @@ function registerDiagnoseCommands(diagnoseCmd2) {
5602
5717
  const s = !json ? clack10.spinner() : null;
5603
5718
  s?.start("Collecting diagnostic data...");
5604
5719
  const data2 = await collectDiagnosticData(projectId, ossMode, apiUrl);
5605
- const cliVersion = "0.1.57";
5720
+ const cliVersion = "0.1.58";
5606
5721
  s?.stop("Data collected");
5607
5722
  if (!json) {
5608
5723
  console.log(`