@base44-preview/cli 0.0.15-pr.98.72adf3c → 0.0.15-pr.99.05b68cd

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 (2) hide show
  1. package/dist/cli/index.js +425 -1
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -4547,6 +4547,7 @@ const string$1 = (params) => {
4547
4547
  };
4548
4548
  const integer = /^-?\d+$/;
4549
4549
  const number$1 = /^-?\d+(?:\.\d+)?$/;
4550
+ const boolean$1 = /^(?:true|false)$/i;
4550
4551
  const lowercase = /^[^A-Z]*$/;
4551
4552
  const uppercase = /^[^a-z]*$/;
4552
4553
 
@@ -5325,6 +5326,24 @@ const $ZodNumberFormat = /* @__PURE__ */ $constructor("$ZodNumberFormat", (inst,
5325
5326
  $ZodCheckNumberFormat.init(inst, def);
5326
5327
  $ZodNumber.init(inst, def);
5327
5328
  });
5329
+ const $ZodBoolean = /* @__PURE__ */ $constructor("$ZodBoolean", (inst, def) => {
5330
+ $ZodType.init(inst, def);
5331
+ inst._zod.pattern = boolean$1;
5332
+ inst._zod.parse = (payload, _ctx) => {
5333
+ if (def.coerce) try {
5334
+ payload.value = Boolean(payload.value);
5335
+ } catch (_$2) {}
5336
+ const input = payload.value;
5337
+ if (typeof input === "boolean") return payload;
5338
+ payload.issues.push({
5339
+ expected: "boolean",
5340
+ code: "invalid_type",
5341
+ input,
5342
+ inst
5343
+ });
5344
+ return payload;
5345
+ };
5346
+ });
5328
5347
  const $ZodUnknown = /* @__PURE__ */ $constructor("$ZodUnknown", (inst, def) => {
5329
5348
  $ZodType.init(inst, def);
5330
5349
  inst._zod.parse = (payload) => payload;
@@ -6379,6 +6398,13 @@ function _int(Class, params) {
6379
6398
  });
6380
6399
  }
6381
6400
  /* @__NO_SIDE_EFFECTS__ */
6401
+ function _boolean(Class, params) {
6402
+ return new Class({
6403
+ type: "boolean",
6404
+ ...normalizeParams(params)
6405
+ });
6406
+ }
6407
+ /* @__NO_SIDE_EFFECTS__ */
6382
6408
  function _unknown(Class) {
6383
6409
  return new Class({ type: "unknown" });
6384
6410
  }
@@ -6949,6 +6975,9 @@ const numberProcessor = (schema, ctx, _json, _params) => {
6949
6975
  }
6950
6976
  if (typeof multipleOf === "number") json.multipleOf = multipleOf;
6951
6977
  };
6978
+ const booleanProcessor = (_schema, _ctx, json, _params) => {
6979
+ json.type = "boolean";
6980
+ };
6952
6981
  const neverProcessor = (_schema, _ctx, json, _params) => {
6953
6982
  json.not = {};
6954
6983
  };
@@ -7472,6 +7501,14 @@ const ZodNumberFormat = /* @__PURE__ */ $constructor("ZodNumberFormat", (inst, d
7472
7501
  function int(params) {
7473
7502
  return _int(ZodNumberFormat, params);
7474
7503
  }
7504
+ const ZodBoolean = /* @__PURE__ */ $constructor("ZodBoolean", (inst, def) => {
7505
+ $ZodBoolean.init(inst, def);
7506
+ ZodType.init(inst, def);
7507
+ inst._zod.processJSONSchema = (ctx, json, params) => booleanProcessor(inst, ctx, json, params);
7508
+ });
7509
+ function boolean(params) {
7510
+ return _boolean(ZodBoolean, params);
7511
+ }
7475
7512
  const ZodUnknown = /* @__PURE__ */ $constructor("ZodUnknown", (inst, def) => {
7476
7513
  $ZodUnknown.init(inst, def);
7477
7514
  ZodType.init(inst, def);
@@ -7863,6 +7900,19 @@ var AuthValidationError = class extends Error {
7863
7900
  this.name = "AuthValidationError";
7864
7901
  }
7865
7902
  };
7903
+ var ConnectorApiError = class extends Error {
7904
+ constructor(message, cause) {
7905
+ super(message);
7906
+ this.cause = cause;
7907
+ this.name = "ConnectorApiError";
7908
+ }
7909
+ };
7910
+ var ConnectorValidationError = class extends Error {
7911
+ constructor(message) {
7912
+ super(message);
7913
+ this.name = "ConnectorValidationError";
7914
+ }
7915
+ };
7866
7916
 
7867
7917
  //#endregion
7868
7918
  //#region src/core/consts.ts
@@ -30955,7 +31005,10 @@ const theme = {
30955
31005
  base44OrangeBackground: source_default.bgHex("#E86B3C"),
30956
31006
  shinyOrange: source_default.hex("#FFD700"),
30957
31007
  links: source_default.hex("#00D4FF"),
30958
- white: source_default.white
31008
+ white: source_default.white,
31009
+ success: source_default.green,
31010
+ warning: source_default.yellow,
31011
+ error: source_default.red
30959
31012
  },
30960
31013
  styles: {
30961
31014
  header: source_default.dim,
@@ -38789,6 +38842,374 @@ const siteDeployCommand = new Command("site").description("Manage site deploymen
38789
38842
  await runCommand(() => deployAction(options), { requireAuth: true });
38790
38843
  }));
38791
38844
 
38845
+ //#endregion
38846
+ //#region src/core/connectors/schema.ts
38847
+ /**
38848
+ * Response from POST /api/apps/{app_id}/external-auth/initiate
38849
+ */
38850
+ const InitiateResponseSchema = object({
38851
+ redirect_url: string().optional(),
38852
+ connection_id: string().optional(),
38853
+ already_authorized: boolean().optional(),
38854
+ other_user_email: string().optional(),
38855
+ error: string().optional()
38856
+ });
38857
+ /**
38858
+ * Response from GET /api/apps/{app_id}/external-auth/status
38859
+ */
38860
+ const StatusResponseSchema = object({
38861
+ status: _enum([
38862
+ "ACTIVE",
38863
+ "PENDING",
38864
+ "FAILED"
38865
+ ]),
38866
+ account_email: string().optional(),
38867
+ error: string().optional()
38868
+ }).transform((data) => ({
38869
+ status: data.status,
38870
+ accountEmail: data.account_email,
38871
+ error: data.error
38872
+ }));
38873
+ /**
38874
+ * A connected integration from the list endpoint
38875
+ */
38876
+ const ConnectorSchema = object({
38877
+ integration_type: string(),
38878
+ status: string(),
38879
+ connected_at: string().optional(),
38880
+ account_info: object({
38881
+ email: string().optional(),
38882
+ name: string().optional()
38883
+ }).optional()
38884
+ }).transform((data) => ({
38885
+ integrationType: data.integration_type,
38886
+ status: data.status,
38887
+ connectedAt: data.connected_at,
38888
+ accountInfo: data.account_info
38889
+ }));
38890
+ /**
38891
+ * Response from GET /api/apps/{app_id}/external-auth/list
38892
+ */
38893
+ const ListResponseSchema = object({ integrations: array(ConnectorSchema) });
38894
+ /**
38895
+ * Generic API error response
38896
+ */
38897
+ const ApiErrorSchema = object({
38898
+ error: string(),
38899
+ detail: string().optional()
38900
+ });
38901
+
38902
+ //#endregion
38903
+ //#region src/core/connectors/api.ts
38904
+ /**
38905
+ * Initiates OAuth flow for a connector integration.
38906
+ * Returns a redirect URL to open in the browser.
38907
+ */
38908
+ async function initiateOAuth(integrationType, scopes = null) {
38909
+ const response = await getAppClient().post("external-auth/initiate", {
38910
+ json: {
38911
+ integration_type: integrationType,
38912
+ scopes
38913
+ },
38914
+ throwHttpErrors: false
38915
+ });
38916
+ const json = await response.json();
38917
+ if (!response.ok) {
38918
+ const errorResult = ApiErrorSchema.safeParse(json);
38919
+ if (errorResult.success) throw new ConnectorApiError(errorResult.data.error);
38920
+ throw new ConnectorApiError(`Failed to initiate OAuth: ${response.status} ${response.statusText}`);
38921
+ }
38922
+ const result = InitiateResponseSchema.safeParse(json);
38923
+ if (!result.success) throw new ConnectorValidationError(`Invalid initiate response from server: ${result.error.message}`);
38924
+ return result.data;
38925
+ }
38926
+ /**
38927
+ * Checks the status of an OAuth connection attempt.
38928
+ */
38929
+ async function checkOAuthStatus(integrationType, connectionId) {
38930
+ const response = await getAppClient().get("external-auth/status", {
38931
+ searchParams: {
38932
+ integration_type: integrationType,
38933
+ connection_id: connectionId
38934
+ },
38935
+ throwHttpErrors: false
38936
+ });
38937
+ const json = await response.json();
38938
+ if (!response.ok) {
38939
+ const errorResult = ApiErrorSchema.safeParse(json);
38940
+ if (errorResult.success) throw new ConnectorApiError(errorResult.data.error);
38941
+ throw new ConnectorApiError(`Failed to check OAuth status: ${response.status} ${response.statusText}`);
38942
+ }
38943
+ const result = StatusResponseSchema.safeParse(json);
38944
+ if (!result.success) throw new ConnectorValidationError(`Invalid status response from server: ${result.error.message}`);
38945
+ return result.data;
38946
+ }
38947
+ /**
38948
+ * Lists all connected integrations for the current app.
38949
+ */
38950
+ async function listConnectors() {
38951
+ const response = await getAppClient().get("external-auth/list", { throwHttpErrors: false });
38952
+ const json = await response.json();
38953
+ if (!response.ok) {
38954
+ const errorResult = ApiErrorSchema.safeParse(json);
38955
+ if (errorResult.success) throw new ConnectorApiError(errorResult.data.error);
38956
+ throw new ConnectorApiError(`Failed to list connectors: ${response.status} ${response.statusText}`);
38957
+ }
38958
+ const result = ListResponseSchema.safeParse(json);
38959
+ if (!result.success) throw new ConnectorValidationError(`Invalid list response from server: ${result.error.message}`);
38960
+ return result.data.integrations;
38961
+ }
38962
+ /**
38963
+ * Disconnects (soft delete) a connector integration.
38964
+ */
38965
+ async function disconnectConnector(integrationType) {
38966
+ const response = await getAppClient().delete(`external-auth/integrations/${integrationType}`, { throwHttpErrors: false });
38967
+ if (!response.ok) {
38968
+ const json = await response.json();
38969
+ const errorResult = ApiErrorSchema.safeParse(json);
38970
+ if (errorResult.success) throw new ConnectorApiError(errorResult.data.error);
38971
+ throw new ConnectorApiError(`Failed to disconnect connector: ${response.status} ${response.statusText}`);
38972
+ }
38973
+ }
38974
+ /**
38975
+ * Removes (hard delete) a connector integration.
38976
+ * This permanently removes the connector and cannot be undone.
38977
+ */
38978
+ async function removeConnector(integrationType) {
38979
+ const response = await getAppClient().delete(`external-auth/integrations/${integrationType}/remove`, { throwHttpErrors: false });
38980
+ if (!response.ok) {
38981
+ const json = await response.json();
38982
+ const errorResult = ApiErrorSchema.safeParse(json);
38983
+ if (errorResult.success) throw new ConnectorApiError(errorResult.data.error);
38984
+ throw new ConnectorApiError(`Failed to remove connector: ${response.status} ${response.statusText}`);
38985
+ }
38986
+ }
38987
+
38988
+ //#endregion
38989
+ //#region src/core/connectors/constants.ts
38990
+ /**
38991
+ * Supported OAuth connector integrations.
38992
+ * Based on apper/backend/app/external_auth/models/constants.py
38993
+ */
38994
+ const SUPPORTED_INTEGRATIONS = [
38995
+ "googlecalendar",
38996
+ "googledrive",
38997
+ "gmail",
38998
+ "googlesheets",
38999
+ "googledocs",
39000
+ "googleslides",
39001
+ "slack",
39002
+ "notion",
39003
+ "salesforce",
39004
+ "hubspot",
39005
+ "linkedin",
39006
+ "tiktok"
39007
+ ];
39008
+ /**
39009
+ * Display names for integrations (for CLI output)
39010
+ */
39011
+ const INTEGRATION_DISPLAY_NAMES = {
39012
+ googlecalendar: "Google Calendar",
39013
+ googledrive: "Google Drive",
39014
+ gmail: "Gmail",
39015
+ googlesheets: "Google Sheets",
39016
+ googledocs: "Google Docs",
39017
+ googleslides: "Google Slides",
39018
+ slack: "Slack",
39019
+ notion: "Notion",
39020
+ salesforce: "Salesforce",
39021
+ hubspot: "HubSpot",
39022
+ linkedin: "LinkedIn",
39023
+ tiktok: "TikTok"
39024
+ };
39025
+ function isValidIntegration(type) {
39026
+ return SUPPORTED_INTEGRATIONS.includes(type);
39027
+ }
39028
+ function getIntegrationDisplayName(type) {
39029
+ if (isValidIntegration(type)) return INTEGRATION_DISPLAY_NAMES[type];
39030
+ return type;
39031
+ }
39032
+
39033
+ //#endregion
39034
+ //#region src/cli/commands/connectors/add.ts
39035
+ const POLL_INTERVAL_MS = 2e3;
39036
+ const POLL_TIMEOUT_MS = 300 * 1e3;
39037
+ async function promptForIntegrationType() {
39038
+ const selected = await ve({
39039
+ message: "Select an integration to connect:",
39040
+ options: SUPPORTED_INTEGRATIONS.map((type) => ({
39041
+ value: type,
39042
+ label: getIntegrationDisplayName(type)
39043
+ }))
39044
+ });
39045
+ if (pD(selected)) return null;
39046
+ return selected;
39047
+ }
39048
+ async function waitForOAuthCompletion(integrationType, connectionId) {
39049
+ let accountEmail;
39050
+ let error;
39051
+ try {
39052
+ await runTask("Waiting for authorization...", async (updateMessage) => {
39053
+ await pWaitFor(async () => {
39054
+ const status = await checkOAuthStatus(integrationType, connectionId);
39055
+ if (status.status === "ACTIVE") {
39056
+ accountEmail = status.accountEmail;
39057
+ return true;
39058
+ }
39059
+ if (status.status === "FAILED") {
39060
+ error = status.error || "Authorization failed";
39061
+ throw new Error(error);
39062
+ }
39063
+ updateMessage("Waiting for authorization in browser...");
39064
+ return false;
39065
+ }, {
39066
+ interval: POLL_INTERVAL_MS,
39067
+ timeout: POLL_TIMEOUT_MS
39068
+ });
39069
+ }, {
39070
+ successMessage: "Authorization completed!",
39071
+ errorMessage: "Authorization failed"
39072
+ });
39073
+ return {
39074
+ success: true,
39075
+ accountEmail
39076
+ };
39077
+ } catch (err) {
39078
+ if (err instanceof Error && err.message.includes("timed out")) return {
39079
+ success: false,
39080
+ error: "Authorization timed out. Please try again."
39081
+ };
39082
+ return {
39083
+ success: false,
39084
+ error: error || (err instanceof Error ? err.message : "Unknown error")
39085
+ };
39086
+ }
39087
+ }
39088
+ async function addConnector(integrationType) {
39089
+ let selectedType;
39090
+ if (!integrationType) {
39091
+ const prompted = await promptForIntegrationType();
39092
+ if (!prompted) return { outroMessage: "Cancelled" };
39093
+ selectedType = prompted;
39094
+ } else {
39095
+ if (!isValidIntegration(integrationType)) {
39096
+ const supportedList = SUPPORTED_INTEGRATIONS.join(", ");
39097
+ throw new Error(`Unsupported connector: ${integrationType}\nSupported connectors: ${supportedList}`);
39098
+ }
39099
+ selectedType = integrationType;
39100
+ }
39101
+ const displayName = getIntegrationDisplayName(selectedType);
39102
+ const initiateResponse = await runTask(`Initiating ${displayName} connection...`, async () => {
39103
+ return await initiateOAuth(selectedType);
39104
+ }, {
39105
+ successMessage: `${displayName} OAuth initiated`,
39106
+ errorMessage: `Failed to initiate ${displayName} connection`
39107
+ });
39108
+ if (initiateResponse.already_authorized) return { outroMessage: `Already connected to ${theme.styles.bold(displayName)}` };
39109
+ if (initiateResponse.error === "different_user" && initiateResponse.other_user_email) throw new Error(`This app is already connected to ${displayName} by ${initiateResponse.other_user_email}`);
39110
+ if (!initiateResponse.redirect_url || !initiateResponse.connection_id) throw new Error("Invalid response from server: missing redirect URL or connection ID");
39111
+ M.info(`Opening browser for ${displayName} authorization...`);
39112
+ await open_default(initiateResponse.redirect_url);
39113
+ const result = await waitForOAuthCompletion(selectedType, initiateResponse.connection_id);
39114
+ if (!result.success) throw new Error(result.error || "Authorization failed");
39115
+ const accountInfo = result.accountEmail ? ` as ${theme.styles.bold(result.accountEmail)}` : "";
39116
+ return { outroMessage: `Successfully connected to ${theme.styles.bold(displayName)}${accountInfo}` };
39117
+ }
39118
+ const connectorsAddCommand = new Command("connectors:add").argument("[type]", "Integration type (e.g., slack, notion, googlecalendar)").description("Connect an OAuth integration").action(async (type) => {
39119
+ await runCommand(() => addConnector(type), {
39120
+ requireAuth: true,
39121
+ requireAppConfig: true
39122
+ });
39123
+ });
39124
+
39125
+ //#endregion
39126
+ //#region src/cli/commands/connectors/list.ts
39127
+ function formatConnectorLine(connector) {
39128
+ const name$1 = getIntegrationDisplayName(connector.integrationType);
39129
+ const account = connector.accountInfo?.email || connector.accountInfo?.name;
39130
+ const status = connector.status.toLowerCase();
39131
+ return `${status === "active" ? theme.colors.success("●") : theme.colors.error("○")} ${name$1}${account ? ` - ${account}` : ""}${status !== "active" ? theme.styles.dim(` (${status})`) : ""}`;
39132
+ }
39133
+ async function listConnectorsCommand() {
39134
+ const connectors = await runTask("Fetching connectors...", async () => {
39135
+ return await listConnectors();
39136
+ }, {
39137
+ successMessage: "Connectors loaded",
39138
+ errorMessage: "Failed to fetch connectors"
39139
+ });
39140
+ if (connectors.length === 0) {
39141
+ M.info("No connectors configured for this app.");
39142
+ M.info(`Run ${theme.styles.bold("base44 connectors:add")} to connect an integration.`);
39143
+ return { outroMessage: "" };
39144
+ }
39145
+ console.log();
39146
+ for (const connector of connectors) console.log(formatConnectorLine(connector));
39147
+ console.log();
39148
+ return { outroMessage: `${connectors.length} connector${connectors.length === 1 ? "" : "s"} configured` };
39149
+ }
39150
+ const connectorsListCommand = new Command("connectors:list").description("List all connected OAuth integrations").action(async () => {
39151
+ await runCommand(listConnectorsCommand, {
39152
+ requireAuth: true,
39153
+ requireAppConfig: true
39154
+ });
39155
+ });
39156
+
39157
+ //#endregion
39158
+ //#region src/cli/commands/connectors/remove.ts
39159
+ async function promptForConnectorToRemove(connectors) {
39160
+ const selected = await ve({
39161
+ message: "Select a connector to remove:",
39162
+ options: connectors.map((c$1) => ({
39163
+ value: c$1.integrationType,
39164
+ label: `${getIntegrationDisplayName(c$1.integrationType)}${c$1.accountInfo?.email ? ` (${c$1.accountInfo.email})` : ""}`
39165
+ }))
39166
+ });
39167
+ if (pD(selected)) return null;
39168
+ return selected;
39169
+ }
39170
+ async function removeConnectorCommand(integrationType, options = {}) {
39171
+ const isHardDelete = options.hard === true;
39172
+ const connectors = await runTask("Fetching connectors...", async () => {
39173
+ return await listConnectors();
39174
+ }, {
39175
+ successMessage: "Connectors loaded",
39176
+ errorMessage: "Failed to fetch connectors"
39177
+ });
39178
+ if (connectors.length === 0) return { outroMessage: "No connectors to remove" };
39179
+ let selectedType;
39180
+ if (!integrationType) {
39181
+ const prompted = await promptForConnectorToRemove(connectors);
39182
+ if (!prompted) return { outroMessage: "Cancelled" };
39183
+ selectedType = prompted;
39184
+ } else {
39185
+ if (!isValidIntegration(integrationType)) throw new Error(`Invalid connector type: ${integrationType}`);
39186
+ if (!connectors.some((c$1) => c$1.integrationType === integrationType)) throw new Error(`No ${getIntegrationDisplayName(integrationType)} connector found for this app`);
39187
+ selectedType = integrationType;
39188
+ }
39189
+ const displayName = getIntegrationDisplayName(selectedType);
39190
+ const connector = connectors.find((c$1) => c$1.integrationType === selectedType);
39191
+ const accountInfo = connector?.accountInfo?.email ? ` (${connector.accountInfo.email})` : "";
39192
+ const shouldRemove = await ye({
39193
+ message: `${isHardDelete ? "Permanently remove" : "Disconnect"} ${displayName}${accountInfo}?`,
39194
+ initialValue: false
39195
+ });
39196
+ if (pD(shouldRemove) || !shouldRemove) return { outroMessage: "Cancelled" };
39197
+ await runTask(isHardDelete ? `Removing ${displayName}...` : `Disconnecting ${displayName}...`, async () => {
39198
+ if (isHardDelete) await removeConnector(selectedType);
39199
+ else await disconnectConnector(selectedType);
39200
+ }, {
39201
+ successMessage: isHardDelete ? `${displayName} removed` : `${displayName} disconnected`,
39202
+ errorMessage: isHardDelete ? `Failed to remove ${displayName}` : `Failed to disconnect ${displayName}`
39203
+ });
39204
+ return { outroMessage: `Successfully ${isHardDelete ? "removed" : "disconnected"} ${theme.styles.bold(displayName)}` };
39205
+ }
39206
+ const connectorsRemoveCommand = new Command("connectors:remove").argument("[type]", "Integration type to remove (e.g., slack, notion)").option("--hard", "Permanently remove the connector (cannot be undone)").description("Disconnect an OAuth integration").action(async (type, options) => {
39207
+ await runCommand(() => removeConnectorCommand(type, options), {
39208
+ requireAuth: true,
39209
+ requireAppConfig: true
39210
+ });
39211
+ });
39212
+
38792
39213
  //#endregion
38793
39214
  //#region package.json
38794
39215
  var version = "0.0.15";
@@ -38808,6 +39229,9 @@ program.addCommand(linkCommand);
38808
39229
  program.addCommand(entitiesPushCommand);
38809
39230
  program.addCommand(functionsDeployCommand);
38810
39231
  program.addCommand(siteDeployCommand);
39232
+ program.addCommand(connectorsAddCommand);
39233
+ program.addCommand(connectorsListCommand);
39234
+ program.addCommand(connectorsRemoveCommand);
38811
39235
  program.parse();
38812
39236
 
38813
39237
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@base44-preview/cli",
3
- "version": "0.0.15-pr.98.72adf3c",
3
+ "version": "0.0.15-pr.99.05b68cd",
4
4
  "description": "Base44 CLI - Unified interface for managing Base44 applications",
5
5
  "type": "module",
6
6
  "main": "./dist/cli/index.js",