@base44-preview/cli 0.1.15-pr.630.a6f9272 → 0.1.15-pr.630.ab1ebad

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/cli/index.js CHANGED
@@ -52822,7 +52822,7 @@ function canBreakFn(doc) {
52822
52822
  function canBreak(doc) {
52823
52823
  return findInDoc(doc, canBreakFn, false);
52824
52824
  }
52825
- function indent(contents) {
52825
+ function indent2(contents) {
52826
52826
  assertDoc(contents);
52827
52827
  return { type: DOC_TYPE_INDENT, contents };
52828
52828
  }
@@ -52845,7 +52845,7 @@ function addAlignmentToDoc(doc, size, tabWidth) {
52845
52845
  let aligned = doc;
52846
52846
  if (size > 0) {
52847
52847
  for (let level = 0;level < Math.floor(size / tabWidth); ++level) {
52848
- aligned = indent(aligned);
52848
+ aligned = indent2(aligned);
52849
52849
  }
52850
52850
  aligned = align(size % tabWidth, aligned);
52851
52851
  aligned = align(Number.NEGATIVE_INFINITY, aligned);
@@ -53179,7 +53179,7 @@ function printDocToString(doc, options) {
53179
53179
  propagateBreaks(doc);
53180
53180
  while (commands.length > 0) {
53181
53181
  const {
53182
- indent: indent2,
53182
+ indent: indent22,
53183
53183
  mode,
53184
53184
  doc: doc2
53185
53185
  } = commands.pop();
@@ -53199,7 +53199,7 @@ function printDocToString(doc, options) {
53199
53199
  case DOC_TYPE_ARRAY:
53200
53200
  for (let index = doc2.length - 1;index >= 0; index--) {
53201
53201
  commands.push({
53202
- indent: indent2,
53202
+ indent: indent22,
53203
53203
  mode,
53204
53204
  doc: doc2[index]
53205
53205
  });
@@ -53213,14 +53213,14 @@ function printDocToString(doc, options) {
53213
53213
  break;
53214
53214
  case DOC_TYPE_INDENT:
53215
53215
  commands.push({
53216
- indent: makeIndent(indent2, options),
53216
+ indent: makeIndent(indent22, options),
53217
53217
  mode,
53218
53218
  doc: doc2.contents
53219
53219
  });
53220
53220
  break;
53221
53221
  case DOC_TYPE_ALIGN:
53222
53222
  commands.push({
53223
- indent: makeAlign(indent2, doc2.n, options),
53223
+ indent: makeAlign(indent22, doc2.n, options),
53224
53224
  mode,
53225
53225
  doc: doc2.contents
53226
53226
  });
@@ -53233,7 +53233,7 @@ function printDocToString(doc, options) {
53233
53233
  case MODE_FLAT:
53234
53234
  if (!shouldRemeasure) {
53235
53235
  commands.push({
53236
- indent: indent2,
53236
+ indent: indent22,
53237
53237
  mode: doc2.break ? MODE_BREAK : MODE_FLAT,
53238
53238
  doc: doc2.contents
53239
53239
  });
@@ -53242,7 +53242,7 @@ function printDocToString(doc, options) {
53242
53242
  case MODE_BREAK: {
53243
53243
  shouldRemeasure = false;
53244
53244
  const next = {
53245
- indent: indent2,
53245
+ indent: indent22,
53246
53246
  mode: MODE_FLAT,
53247
53247
  doc: doc2.contents
53248
53248
  };
@@ -53255,7 +53255,7 @@ function printDocToString(doc, options) {
53255
53255
  const mostExpanded = method_at_default(0, doc2.expandedStates, -1);
53256
53256
  if (doc2.break) {
53257
53257
  commands.push({
53258
- indent: indent2,
53258
+ indent: indent22,
53259
53259
  mode: MODE_BREAK,
53260
53260
  doc: mostExpanded
53261
53261
  });
@@ -53264,7 +53264,7 @@ function printDocToString(doc, options) {
53264
53264
  for (let index = 1;index < doc2.expandedStates.length + 1; index++) {
53265
53265
  if (index >= doc2.expandedStates.length) {
53266
53266
  commands.push({
53267
- indent: indent2,
53267
+ indent: indent22,
53268
53268
  mode: MODE_BREAK,
53269
53269
  doc: mostExpanded
53270
53270
  });
@@ -53272,7 +53272,7 @@ function printDocToString(doc, options) {
53272
53272
  } else {
53273
53273
  const state = doc2.expandedStates[index];
53274
53274
  const cmd = {
53275
- indent: indent2,
53275
+ indent: indent22,
53276
53276
  mode: MODE_FLAT,
53277
53277
  doc: state
53278
53278
  };
@@ -53285,7 +53285,7 @@ function printDocToString(doc, options) {
53285
53285
  }
53286
53286
  } else {
53287
53287
  commands.push({
53288
- indent: indent2,
53288
+ indent: indent22,
53289
53289
  mode: MODE_BREAK,
53290
53290
  doc: doc2.contents
53291
53291
  });
@@ -53311,12 +53311,12 @@ function printDocToString(doc, options) {
53311
53311
  const content = parts[offset + 0];
53312
53312
  const whitespace = parts[offset + 1];
53313
53313
  const contentFlatCommand = {
53314
- indent: indent2,
53314
+ indent: indent22,
53315
53315
  mode: MODE_FLAT,
53316
53316
  doc: content
53317
53317
  };
53318
53318
  const contentBreakCommand = {
53319
- indent: indent2,
53319
+ indent: indent22,
53320
53320
  mode: MODE_BREAK,
53321
53321
  doc: content
53322
53322
  };
@@ -53330,12 +53330,12 @@ function printDocToString(doc, options) {
53330
53330
  break;
53331
53331
  }
53332
53332
  const whitespaceFlatCommand = {
53333
- indent: indent2,
53333
+ indent: indent22,
53334
53334
  mode: MODE_FLAT,
53335
53335
  doc: whitespace
53336
53336
  };
53337
53337
  const whitespaceBreakCommand = {
53338
- indent: indent2,
53338
+ indent: indent22,
53339
53339
  mode: MODE_BREAK,
53340
53340
  doc: whitespace
53341
53341
  };
@@ -53349,7 +53349,7 @@ function printDocToString(doc, options) {
53349
53349
  }
53350
53350
  const secondContent = parts[offset + 2];
53351
53351
  const remainingCommand = {
53352
- indent: indent2,
53352
+ indent: indent22,
53353
53353
  mode,
53354
53354
  doc: {
53355
53355
  ...doc2,
@@ -53357,7 +53357,7 @@ function printDocToString(doc, options) {
53357
53357
  }
53358
53358
  };
53359
53359
  const firstAndSecondContentFlatCommand = {
53360
- indent: indent2,
53360
+ indent: indent22,
53361
53361
  mode: MODE_FLAT,
53362
53362
  doc: [content, whitespace, secondContent]
53363
53363
  };
@@ -53376,20 +53376,20 @@ function printDocToString(doc, options) {
53376
53376
  case DOC_TYPE_INDENT_IF_BREAK: {
53377
53377
  const groupMode = doc2.groupId ? groupModeMap[doc2.groupId] : mode;
53378
53378
  if (groupMode === MODE_BREAK) {
53379
- const breakContents = doc2.type === DOC_TYPE_IF_BREAK ? doc2.breakContents : doc2.negate ? doc2.contents : indent(doc2.contents);
53379
+ const breakContents = doc2.type === DOC_TYPE_IF_BREAK ? doc2.breakContents : doc2.negate ? doc2.contents : indent2(doc2.contents);
53380
53380
  if (breakContents) {
53381
53381
  commands.push({
53382
- indent: indent2,
53382
+ indent: indent22,
53383
53383
  mode,
53384
53384
  doc: breakContents
53385
53385
  });
53386
53386
  }
53387
53387
  }
53388
53388
  if (groupMode === MODE_FLAT) {
53389
- const flatContents = doc2.type === DOC_TYPE_IF_BREAK ? doc2.flatContents : doc2.negate ? indent(doc2.contents) : doc2.contents;
53389
+ const flatContents = doc2.type === DOC_TYPE_IF_BREAK ? doc2.flatContents : doc2.negate ? indent2(doc2.contents) : doc2.contents;
53390
53390
  if (flatContents) {
53391
53391
  commands.push({
53392
- indent: indent2,
53392
+ indent: indent22,
53393
53393
  mode,
53394
53394
  doc: flatContents
53395
53395
  });
@@ -53399,7 +53399,7 @@ function printDocToString(doc, options) {
53399
53399
  }
53400
53400
  case DOC_TYPE_LINE_SUFFIX:
53401
53401
  lineSuffix2.push({
53402
- indent: indent2,
53402
+ indent: indent22,
53403
53403
  mode,
53404
53404
  doc: doc2.contents
53405
53405
  });
@@ -53407,7 +53407,7 @@ function printDocToString(doc, options) {
53407
53407
  case DOC_TYPE_LINE_SUFFIX_BOUNDARY:
53408
53408
  if (lineSuffix2.length > 0) {
53409
53409
  commands.push({
53410
- indent: indent2,
53410
+ indent: indent22,
53411
53411
  mode,
53412
53412
  doc: hardlineWithoutBreakParent
53413
53413
  });
@@ -53428,7 +53428,7 @@ function printDocToString(doc, options) {
53428
53428
  case MODE_BREAK:
53429
53429
  if (lineSuffix2.length > 0) {
53430
53430
  commands.push({
53431
- indent: indent2,
53431
+ indent: indent22,
53432
53432
  mode,
53433
53433
  doc: doc2
53434
53434
  }, ...lineSuffix2.reverse());
@@ -53438,23 +53438,23 @@ function printDocToString(doc, options) {
53438
53438
  if (doc2.literal) {
53439
53439
  output += newLine;
53440
53440
  position = 0;
53441
- if (indent2.root) {
53442
- if (indent2.root.value) {
53443
- output += indent2.root.value;
53441
+ if (indent22.root) {
53442
+ if (indent22.root.value) {
53443
+ output += indent22.root.value;
53444
53444
  }
53445
- position = indent2.root.length;
53445
+ position = indent22.root.length;
53446
53446
  }
53447
53447
  } else {
53448
53448
  trim2();
53449
- output += newLine + indent2.value;
53450
- position = indent2.length;
53449
+ output += newLine + indent22.value;
53450
+ position = indent22.length;
53451
53451
  }
53452
53452
  break;
53453
53453
  }
53454
53454
  break;
53455
53455
  case DOC_TYPE_LABEL:
53456
53456
  commands.push({
53457
- indent: indent2,
53457
+ indent: indent22,
53458
53458
  mode,
53459
53459
  doc: doc2.contents
53460
53460
  });
@@ -53616,7 +53616,7 @@ var init_doc = __esm(() => {
53616
53616
  breakParent,
53617
53617
  ifBreak,
53618
53618
  trim: trim2,
53619
- indent,
53619
+ indent: indent2,
53620
53620
  indentIfBreak,
53621
53621
  align,
53622
53622
  addAlignmentToDoc,
@@ -140754,7 +140754,7 @@ import { pathToFileURL as pathToFileURL5 } from "url";
140754
140754
  import path122 from "path";
140755
140755
  import path142 from "path";
140756
140756
  import url22 from "url";
140757
- function diffLines(oldStr, newStr, options8) {
140757
+ function diffLines2(oldStr, newStr, options8) {
140758
140758
  return lineDiff.diff(oldStr, newStr, options8);
140759
140759
  }
140760
140760
  function tokenize4(value, options8) {
@@ -140796,10 +140796,10 @@ function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, ne
140796
140796
  throw new Error("newlineIsToken may not be used with patch-generation functions, only with diffing functions");
140797
140797
  }
140798
140798
  if (!optionsObj.callback) {
140799
- return diffLinesResultToPatch(diffLines(oldStr, newStr, optionsObj));
140799
+ return diffLinesResultToPatch(diffLines2(oldStr, newStr, optionsObj));
140800
140800
  } else {
140801
140801
  const { callback } = optionsObj;
140802
- diffLines(oldStr, newStr, Object.assign(Object.assign({}, optionsObj), { callback: (diff) => {
140802
+ diffLines2(oldStr, newStr, Object.assign(Object.assign({}, optionsObj), { callback: (diff) => {
140803
140803
  const patch = diffLinesResultToPatch(diff);
140804
140804
  callback(patch);
140805
140805
  } }));
@@ -146441,7 +146441,7 @@ var require2, __filename2, __dirname3, __create2, __defProp3, __getOwnPropDesc2,
146441
146441
  }
146442
146442
  }
146443
146443
  }, ast_path_default, is_object_default, skipWhitespace, skipSpaces, skipToLineEnd, skipEverythingButNewLine, isNewlineCharacter = (character) => character === `
146444
- ` || character === "\r" || character === "\u2028" || character === "\u2029", skip_newline_default, has_newline_default, is_non_empty_array_default, get_sorted_child_nodes_default, childNodesCache, returnFalse = () => false, isAllEmptyAndNoLineBreak = (text) => !/[\S\n\u2028\u2029]/u.test(text), is_previous_line_empty_default, breakParent2, hardline2, indent2, join32, line22, lineSuffix2, create_print_pre_check_function_default = () => noop_default2, core_options_evaluate_default, arrayToReversed, toReversed, method_to_reversed_default, import_n_readlines, get_interpreter_default, getFileBasename = (file) => {
146444
+ ` || character === "\r" || character === "\u2028" || character === "\u2029", skip_newline_default, has_newline_default, is_non_empty_array_default, get_sorted_child_nodes_default, childNodesCache, returnFalse = () => false, isAllEmptyAndNoLineBreak = (text) => !/[\S\n\u2028\u2029]/u.test(text), is_previous_line_empty_default, breakParent2, hardline2, indent3, join32, line22, lineSuffix2, create_print_pre_check_function_default = () => noop_default2, core_options_evaluate_default, arrayToReversed, toReversed, method_to_reversed_default, import_n_readlines, get_interpreter_default, getFileBasename = (file) => {
146445
146445
  try {
146446
146446
  return path112.basename(toPath4(file));
146447
146447
  } catch {
@@ -156469,7 +156469,7 @@ ${codeblock}`, options8);
156469
156469
  ({
156470
156470
  breakParent: breakParent2,
156471
156471
  hardline: hardline2,
156472
- indent: indent2,
156472
+ indent: indent3,
156473
156473
  join: join32,
156474
156474
  line: line22,
156475
156475
  lineSuffix: lineSuffix2
@@ -269450,6 +269450,467 @@ function getAgentsCommand() {
269450
269450
  return new Command2("agents").description("Manage project agents").addCommand(getAgentsPushCommand()).addCommand(getAgentsPullCommand());
269451
269451
  }
269452
269452
 
269453
+ // src/cli/commands/auth/password-login.ts
269454
+ import { dirname as dirname15, join as join21 } from "node:path";
269455
+ async function passwordLoginAction({ log, runTask }, action) {
269456
+ const shouldEnable = action === "enable";
269457
+ const { project } = await readProjectConfig();
269458
+ const configDir = dirname15(project.configPath);
269459
+ const authDir = join21(configDir, project.authDir);
269460
+ const updated = await runTask("Updating local auth config", async () => {
269461
+ const current = await readAuthConfig(authDir) ?? DEFAULT_AUTH_CONFIG;
269462
+ const merged = { ...current, enableUsernamePassword: shouldEnable };
269463
+ await writeAuthConfig(authDir, merged);
269464
+ return merged;
269465
+ });
269466
+ if (!shouldEnable && !hasAnyLoginMethod(updated)) {
269467
+ log.warn("Disabling password auth will leave no login methods enabled. Users will be locked out.");
269468
+ }
269469
+ const newStatus = shouldEnable ? "enabled" : "disabled";
269470
+ return {
269471
+ outroMessage: `Username & password authentication ${newStatus} in local config. Run \`base44 auth push\` or \`base44 deploy\` to apply.`
269472
+ };
269473
+ }
269474
+ function getPasswordLoginCommand() {
269475
+ return new Base44Command("password-login").description("Enable or disable username & password authentication").addArgument(new Argument2("<action>", "enable or disable password authentication").choices(["enable", "disable"])).action(passwordLoginAction);
269476
+ }
269477
+
269478
+ // src/cli/commands/auth/pull.ts
269479
+ import { dirname as dirname16, join as join22 } from "node:path";
269480
+ async function pullAuthAction({
269481
+ log,
269482
+ runTask
269483
+ }) {
269484
+ const { project } = await readProjectConfig();
269485
+ const configDir = dirname16(project.configPath);
269486
+ const authDir = join22(configDir, project.authDir);
269487
+ const remoteConfig = await runTask("Fetching auth config from Base44", async () => {
269488
+ return await pullAuthConfig();
269489
+ }, {
269490
+ successMessage: "Auth config fetched successfully",
269491
+ errorMessage: "Failed to fetch auth config"
269492
+ });
269493
+ const { written } = await runTask("Syncing auth config file", async () => {
269494
+ return await writeAuthConfig(authDir, remoteConfig);
269495
+ }, {
269496
+ successMessage: "Auth config file synced successfully",
269497
+ errorMessage: "Failed to sync auth config file"
269498
+ });
269499
+ if (written) {
269500
+ log.success("Auth config written to local file");
269501
+ } else {
269502
+ log.info("Auth config is already up to date");
269503
+ }
269504
+ return {
269505
+ outroMessage: `Pulled auth config to ${authDir} (overwrites local file)`
269506
+ };
269507
+ }
269508
+ function getAuthPullCommand() {
269509
+ return new Base44Command("pull").description("Pull auth config from Base44 to local file").action(pullAuthAction);
269510
+ }
269511
+
269512
+ // src/cli/commands/auth/push.ts
269513
+ async function pushAuthAction({ isNonInteractive, log, runTask }, options) {
269514
+ const { authConfig } = await readProjectConfig();
269515
+ if (authConfig.length === 0) {
269516
+ log.info("No local auth config found");
269517
+ return {
269518
+ outroMessage: "No auth config to push. Run `base44 auth pull` to fetch the remote config first."
269519
+ };
269520
+ }
269521
+ if (!hasAnyLoginMethod(authConfig[0])) {
269522
+ log.warn("This config has no login methods enabled. Pushing it will lock out all users.");
269523
+ }
269524
+ if (!options.yes) {
269525
+ if (isNonInteractive) {
269526
+ throw new InvalidInputError("--yes is required in non-interactive mode");
269527
+ }
269528
+ const shouldPush = await Re({
269529
+ message: "Push auth config to Base44?"
269530
+ });
269531
+ if (Ct(shouldPush) || !shouldPush) {
269532
+ return { outroMessage: "Push cancelled" };
269533
+ }
269534
+ }
269535
+ await runTask("Pushing auth config to Base44", async () => {
269536
+ return await pushAuthConfig(authConfig[0] ?? null);
269537
+ }, {
269538
+ successMessage: "Auth config pushed successfully",
269539
+ errorMessage: "Failed to push auth config"
269540
+ });
269541
+ return {
269542
+ outroMessage: "Auth config pushed to Base44"
269543
+ };
269544
+ }
269545
+ function getAuthPushCommand() {
269546
+ return new Base44Command("push").description("Push local auth config to Base44").option("-y, --yes", "Skip confirmation prompt").action(pushAuthAction);
269547
+ }
269548
+
269549
+ // src/cli/commands/auth/social-login.ts
269550
+ import { dirname as dirname17, join as join23, resolve as resolve6 } from "node:path";
269551
+ var PROVIDER_LABELS = {
269552
+ google: "Google",
269553
+ microsoft: "Microsoft",
269554
+ facebook: "Facebook",
269555
+ apple: "Apple"
269556
+ };
269557
+ var VALID_PROVIDER_NAMES = Object.keys(SOCIAL_PROVIDERS);
269558
+ var PROVIDER_OAUTH_CLI = {
269559
+ google: {
269560
+ envVar: "google_oauth_client_secret",
269561
+ promptMessage: "Enter Google OAuth client secret"
269562
+ }
269563
+ };
269564
+ function hasSecretOptions(options) {
269565
+ return Boolean(options.clientSecret || options.clientSecretStdin || options.envFile);
269566
+ }
269567
+ function hasCustomOAuthOptions(options) {
269568
+ return Boolean(options.clientId || hasSecretOptions(options));
269569
+ }
269570
+ async function socialLoginAction({ log, isNonInteractive, runTask }, provider, action, options) {
269571
+ const shouldEnable = action === "enable";
269572
+ const providerInfo = SOCIAL_PROVIDERS[provider];
269573
+ const label = PROVIDER_LABELS[provider];
269574
+ const hasOAuthOptions = hasCustomOAuthOptions(options);
269575
+ if (hasOAuthOptions && !providerInfo.customOAuth) {
269576
+ throw new InvalidInputError(`Custom OAuth options are only supported for providers with custom OAuth (e.g., google). Use: base44 auth social-login ${provider} ${action}`);
269577
+ }
269578
+ if (hasOAuthOptions && !shouldEnable) {
269579
+ throw new InvalidInputError(`Custom OAuth options cannot be used with disable. To disable ${label} login: base44 auth social-login ${provider} disable`);
269580
+ }
269581
+ if (hasSecretOptions(options) && !options.clientId) {
269582
+ throw new InvalidInputError(`--client-id is required when providing a client secret. Use: base44 auth social-login ${provider} enable --client-id <id> --client-secret <secret>`);
269583
+ }
269584
+ const oauth = providerInfo.customOAuth;
269585
+ const oauthCli = PROVIDER_OAUTH_CLI[provider];
269586
+ const useCustomOAuth = shouldEnable && hasOAuthOptions && oauth != null;
269587
+ let clientSecret;
269588
+ if (useCustomOAuth && oauth && oauthCli && hasSecretOptions(options)) {
269589
+ if (options.envFile) {
269590
+ const secrets = await parseEnvFile(resolve6(options.envFile));
269591
+ const value = secrets[oauthCli.envVar];
269592
+ if (!value) {
269593
+ throw new InvalidInputError(`Key "${oauthCli.envVar}" not found in ${options.envFile}.`);
269594
+ }
269595
+ clientSecret = value;
269596
+ } else {
269597
+ clientSecret = await resolveSecret({
269598
+ flagValue: options.clientSecret,
269599
+ fromStdin: options.clientSecretStdin,
269600
+ envVar: oauthCli.envVar,
269601
+ promptMessage: oauthCli.promptMessage,
269602
+ isNonInteractive,
269603
+ name: "client secret",
269604
+ hints: [
269605
+ {
269606
+ message: `Provide via flag: base44 auth social-login ${provider} enable --client-id <id> --client-secret <secret>`,
269607
+ command: `base44 auth social-login ${provider} enable --client-id <id> --client-secret <secret>`
269608
+ },
269609
+ {
269610
+ message: `Provide via stdin: echo <secret> | base44 auth social-login ${provider} enable --client-id <id> --client-secret-stdin`
269611
+ },
269612
+ {
269613
+ message: `Provide via env: ${oauthCli.envVar}=<secret> base44 auth social-login ${provider} enable --client-id <id>`
269614
+ }
269615
+ ]
269616
+ });
269617
+ }
269618
+ }
269619
+ const { project } = await readProjectConfig();
269620
+ const configDir = dirname17(project.configPath);
269621
+ const authDir = join23(configDir, project.authDir);
269622
+ const { config: updated } = await runTask("Updating local auth config", async () => updateSocialLoginConfig(authDir, provider, shouldEnable, useCustomOAuth && options.clientId ? { clientId: options.clientId } : undefined));
269623
+ if (clientSecret) {
269624
+ await runTask("Saving client secret", async () => pushCustomOAuthSecret(provider, clientSecret));
269625
+ }
269626
+ if (!shouldEnable && !hasAnyLoginMethod(updated)) {
269627
+ log.warn(`Disabling ${label} login will leave no login methods enabled. Users will be locked out.`);
269628
+ }
269629
+ const newStatus = shouldEnable ? "enabled" : "disabled";
269630
+ const oauthNote = useCustomOAuth ? " with custom OAuth" : "";
269631
+ let outroMessage = `${label} login ${newStatus}${oauthNote} in local config. Run \`base44 auth push\` or \`base44 deploy\` to apply.`;
269632
+ if (useCustomOAuth && !clientSecret) {
269633
+ outroMessage += `
269634
+ Remember to push the client secret separately: base44 secrets set --env-file <path>`;
269635
+ }
269636
+ return { outroMessage };
269637
+ }
269638
+ function getSocialLoginCommand() {
269639
+ return new Base44Command("social-login").description("Enable or disable social login providers (google, microsoft, facebook, apple)").addArgument(new Argument2("<provider>", "social login provider").choices(VALID_PROVIDER_NAMES)).addArgument(new Argument2("<action>", "enable or disable the provider").choices([
269640
+ "enable",
269641
+ "disable"
269642
+ ])).option("--client-id <id>", "custom OAuth client ID (Google only)").option("--client-secret <secret>", "custom OAuth client secret (Google only)").option("--client-secret-stdin", "read client secret from stdin (Google only)").option("--env-file <path>", "read client secret from a .env file (Google only)").action(socialLoginAction);
269643
+ }
269644
+
269645
+ // src/cli/commands/auth/sso.ts
269646
+ import { dirname as dirname18, join as join24, resolve as resolve7 } from "node:path";
269647
+ var SSOConfigFileSchema = object({
269648
+ provider: _enum(Object.values(KNOWN_SSO_PROVIDERS)),
269649
+ clientId: string2(),
269650
+ clientSecret: string2(),
269651
+ scope: string2().optional(),
269652
+ discoveryUrl: string2().optional(),
269653
+ tenantId: string2().optional(),
269654
+ oktaDomain: string2().optional(),
269655
+ authEndpoint: string2().optional(),
269656
+ tokenEndpoint: string2().optional(),
269657
+ userinfoEndpoint: string2().optional(),
269658
+ jwksUri: string2().optional(),
269659
+ ssoName: string2().optional()
269660
+ });
269661
+ async function loadSSOConfigFile(filePath) {
269662
+ const resolved = resolve7(filePath);
269663
+ const raw = await readJsonFile(resolved);
269664
+ const result = SSOConfigFileSchema.safeParse(raw);
269665
+ if (!result.success) {
269666
+ throw new SchemaValidationError("Invalid SSO config file", result.error, filePath);
269667
+ }
269668
+ return result.data;
269669
+ }
269670
+ function mergeFileWithFlags(fileConfig, options) {
269671
+ return {
269672
+ provider: options.provider ?? fileConfig.provider,
269673
+ clientId: options.clientId ?? fileConfig.clientId,
269674
+ clientSecret: options.clientSecret ?? fileConfig.clientSecret,
269675
+ clientSecretStdin: options.clientSecretStdin,
269676
+ envFile: options.envFile,
269677
+ scope: options.scope ?? fileConfig.scope,
269678
+ discoveryUrl: options.discoveryUrl ?? fileConfig.discoveryUrl,
269679
+ tenantId: options.tenantId ?? fileConfig.tenantId,
269680
+ oktaDomain: options.oktaDomain ?? fileConfig.oktaDomain,
269681
+ authEndpoint: options.authEndpoint ?? fileConfig.authEndpoint,
269682
+ tokenEndpoint: options.tokenEndpoint ?? fileConfig.tokenEndpoint,
269683
+ userinfoEndpoint: options.userinfoEndpoint ?? fileConfig.userinfoEndpoint,
269684
+ jwksUri: options.jwksUri ?? fileConfig.jwksUri,
269685
+ ssoName: options.ssoName ?? fileConfig.ssoName
269686
+ };
269687
+ }
269688
+ var providerNames = Object.keys(KNOWN_SSO_PROVIDERS);
269689
+ var SECRET_KEY_TO_FLAG = {
269690
+ ["sso_name" /* Name */]: "--sso-name",
269691
+ ["sso_client_id" /* ClientId */]: "--client-id",
269692
+ ["sso_client_secret" /* ClientSecret */]: "--client-secret",
269693
+ ["sso_scope" /* Scope */]: "--scope",
269694
+ ["sso_discovery_url" /* DiscoveryUrl */]: "--discovery-url",
269695
+ ["sso_tenant_id" /* TenantId */]: "--tenant-id",
269696
+ ["sso_auth_endpoint" /* AuthEndpoint */]: "--auth-endpoint",
269697
+ ["sso_token_endpoint" /* TokenEndpoint */]: "--token-endpoint",
269698
+ ["sso_userinfo_endpoint" /* UserinfoEndpoint */]: "--userinfo-endpoint",
269699
+ ["sso_okta_domain" /* OktaDomain */]: "--okta-domain",
269700
+ ["sso_jwks_uri" /* JwksUri */]: "--jwks-uri"
269701
+ };
269702
+ function secretKeyToFlag(key) {
269703
+ return SECRET_KEY_TO_FLAG[key];
269704
+ }
269705
+ function exampleCommand(provider) {
269706
+ let cmd = `base44 auth sso enable --provider ${provider} --client-id <id> --client-secret <secret>`;
269707
+ if (provider === KNOWN_SSO_PROVIDERS.microsoft)
269708
+ cmd += " --tenant-id <id>";
269709
+ if (provider === KNOWN_SSO_PROVIDERS.okta)
269710
+ cmd += " --okta-domain <domain>";
269711
+ if (provider === KNOWN_SSO_PROVIDERS.custom)
269712
+ cmd += " --sso-name <name> --auth-endpoint <url> --token-endpoint <url> --userinfo-endpoint <url> --jwks-uri <url>";
269713
+ return cmd;
269714
+ }
269715
+ function validateProvider(provider) {
269716
+ if (!provider) {
269717
+ throw new InvalidInputError("Missing --provider.", {
269718
+ hints: [
269719
+ {
269720
+ message: `Valid providers: ${providerNames.join(", ")}`,
269721
+ command: "base44 auth sso enable --provider <provider> --client-id <id> --client-secret <secret>"
269722
+ }
269723
+ ]
269724
+ });
269725
+ }
269726
+ return provider;
269727
+ }
269728
+ async function ssoEnableAction({ isNonInteractive, runTask }, options) {
269729
+ if (options.file && options.envFile) {
269730
+ throw new InvalidInputError("--file and --env-file cannot be used together. Provide the client secret either inside --file or via --env-file.");
269731
+ }
269732
+ let merged = options;
269733
+ if (options.file) {
269734
+ const fileConfig = await loadSSOConfigFile(options.file);
269735
+ merged = mergeFileWithFlags(fileConfig, options);
269736
+ }
269737
+ const provider = validateProvider(merged.provider);
269738
+ if (!merged.clientId) {
269739
+ throw new InvalidInputError("Missing --client-id.", {
269740
+ hints: [
269741
+ {
269742
+ message: `Example: base44 auth sso enable --provider ${provider} --client-id <id> --client-secret <secret>`,
269743
+ command: `base44 auth sso enable --provider ${provider} --client-id <id> --client-secret <secret>`
269744
+ }
269745
+ ]
269746
+ });
269747
+ }
269748
+ let clientSecret;
269749
+ if (merged.envFile && !merged.clientSecret) {
269750
+ const secrets = await parseEnvFile(resolve7(merged.envFile));
269751
+ const value = secrets.sso_client_secret;
269752
+ if (!value) {
269753
+ throw new InvalidInputError(`Key "sso_client_secret" not found in ${merged.envFile}.`);
269754
+ }
269755
+ clientSecret = value;
269756
+ } else {
269757
+ clientSecret = await resolveSecret({
269758
+ flagValue: merged.clientSecret,
269759
+ fromStdin: merged.clientSecretStdin,
269760
+ envVar: "sso_client_secret",
269761
+ promptMessage: "Enter SSO client secret",
269762
+ isNonInteractive,
269763
+ name: "client secret",
269764
+ hints: [
269765
+ {
269766
+ message: `Provide via flag: base44 auth sso enable --provider ${provider} --client-id <id> --client-secret <secret>`,
269767
+ command: `base44 auth sso enable --provider ${provider} --client-id <id> --client-secret <secret>`
269768
+ },
269769
+ {
269770
+ message: `Provide via stdin: echo <secret> | base44 auth sso enable --provider ${provider} --client-id <id> --client-secret-stdin`
269771
+ },
269772
+ {
269773
+ message: `Provide via env: sso_client_secret=<secret> base44 auth sso enable --provider ${provider} --client-id <id>`
269774
+ }
269775
+ ]
269776
+ });
269777
+ }
269778
+ const secretOptions = {
269779
+ clientId: merged.clientId,
269780
+ clientSecret,
269781
+ scope: merged.scope,
269782
+ discoveryUrl: merged.discoveryUrl,
269783
+ tenantId: merged.tenantId,
269784
+ oktaDomain: merged.oktaDomain,
269785
+ authEndpoint: merged.authEndpoint,
269786
+ tokenEndpoint: merged.tokenEndpoint,
269787
+ userinfoEndpoint: merged.userinfoEndpoint,
269788
+ jwksUri: merged.jwksUri,
269789
+ ssoName: merged.ssoName
269790
+ };
269791
+ let secrets;
269792
+ try {
269793
+ secrets = buildSSOSecrets(provider, secretOptions);
269794
+ } catch (error) {
269795
+ if (error instanceof MissingSSOFieldsError) {
269796
+ const flagNames = error.missingKeys.map(secretKeyToFlag);
269797
+ throw new InvalidInputError(`Missing required fields for ${error.provider}: ${flagNames.join(", ")}`, {
269798
+ hints: [
269799
+ {
269800
+ message: `Example: ${exampleCommand(provider)}`,
269801
+ command: exampleCommand(provider)
269802
+ }
269803
+ ]
269804
+ });
269805
+ }
269806
+ throw error;
269807
+ }
269808
+ const { project } = await readProjectConfig();
269809
+ const configDir = dirname18(project.configPath);
269810
+ const authDir = join24(configDir, project.authDir);
269811
+ await runTask("Updating local auth config", async () => updateSSOConfig(authDir, provider, true));
269812
+ await runTask("Saving SSO credentials", async () => pushSSOSecrets(secrets));
269813
+ return {
269814
+ outroMessage: `SSO configured with ${provider} in local config. Run \`base44 auth push\` or \`base44 deploy\` to apply.`
269815
+ };
269816
+ }
269817
+ function hasEnableOnlyOptions(options) {
269818
+ return Boolean(options.provider || options.clientId || options.clientSecret || options.clientSecretStdin || options.envFile || options.file || options.scope || options.discoveryUrl || options.tenantId || options.oktaDomain || options.authEndpoint || options.tokenEndpoint || options.userinfoEndpoint || options.jwksUri || options.ssoName);
269819
+ }
269820
+ async function ssoDisableAction({ log, runTask }, options) {
269821
+ if (hasEnableOnlyOptions(options)) {
269822
+ throw new InvalidInputError("Configuration options cannot be used with disable. To disable SSO: base44 auth sso disable");
269823
+ }
269824
+ const { project } = await readProjectConfig();
269825
+ const configDir = dirname18(project.configPath);
269826
+ const authDir = join24(configDir, project.authDir);
269827
+ const updated = await runTask("Updating local auth config", async () => updateSSOConfig(authDir, null, false));
269828
+ await runTask("Removing SSO credentials", async () => deleteSSOSecrets());
269829
+ if (!hasAnyLoginMethod(updated)) {
269830
+ log.warn("Disabling SSO will leave no login methods enabled. Users will be locked out.");
269831
+ }
269832
+ return {
269833
+ outroMessage: "SSO disabled in local config and credentials removed. Run `base44 auth push` or `base44 deploy` to apply."
269834
+ };
269835
+ }
269836
+ async function ssoAction(context, action, options) {
269837
+ if (action === "disable") {
269838
+ return ssoDisableAction(context, options);
269839
+ }
269840
+ return ssoEnableAction(context, options);
269841
+ }
269842
+ function getSSOCommand() {
269843
+ return new Base44Command("sso").description("Configure SSO identity provider (google, microsoft, github, okta, custom). SSO and social login are mutually exclusive — enabling one disables the other in the local auth config.").addArgument(new Argument2("<action>", "enable or disable SSO").choices([
269844
+ "enable",
269845
+ "disable"
269846
+ ])).addOption(new Option2("--provider <provider>", "SSO provider").choices(Object.values(KNOWN_SSO_PROVIDERS))).option("--client-id <id>", "OAuth client ID").option("--client-secret <secret>", "OAuth client secret").option("--client-secret-stdin", "Read client secret from stdin").option("--env-file <path>", "Read client secret from a .env file (key: sso_client_secret)").option("--file <path>", "JSON config file with all SSO settings").option("--scope <scope>", "OAuth scope (defaults per provider)").option("--discovery-url <url>", "OIDC discovery URL").option("--tenant-id <id>", "Microsoft tenant ID (required for microsoft)").option("--okta-domain <domain>", "Okta domain (required for okta)").option("--auth-endpoint <url>", "Authorization endpoint (required for custom)").option("--token-endpoint <url>", "Token endpoint (required for custom)").option("--userinfo-endpoint <url>", "Userinfo endpoint (required for custom)").option("--jwks-uri <url>", "JWKS URI (required for custom)").option("--sso-name <name>", "Provider display name (required for custom)").action(ssoAction);
269847
+ }
269848
+
269849
+ // src/cli/commands/auth/index.ts
269850
+ function getAuthCommand() {
269851
+ return new Command2("auth").description("Manage app authentication settings").addCommand(getPasswordLoginCommand()).addCommand(getSocialLoginCommand()).addCommand(getSSOCommand()).addCommand(getAuthPullCommand()).addCommand(getAuthPushCommand());
269852
+ }
269853
+
269854
+ // src/cli/commands/auth/login.ts
269855
+ function getLoginCommand() {
269856
+ return new Base44Command("login", {
269857
+ requireAuth: false,
269858
+ requireAppContext: false
269859
+ }).description("Authenticate with Base44").action(login);
269860
+ }
269861
+
269862
+ // src/cli/commands/auth/logout.ts
269863
+ async function logout(_ctx) {
269864
+ await deleteAuth();
269865
+ return { outroMessage: "Logged out successfully" };
269866
+ }
269867
+ function getLogoutCommand() {
269868
+ return new Base44Command("logout", {
269869
+ requireAuth: false,
269870
+ requireAppContext: false
269871
+ }).description("Logout from current device").action(logout);
269872
+ }
269873
+
269874
+ // src/cli/commands/auth/whoami.ts
269875
+ async function whoami(_ctx) {
269876
+ const workspaceApiKey = getWorkspaceApiKeyFromEnv();
269877
+ if (workspaceApiKey && isWorkspaceApiKey(workspaceApiKey)) {
269878
+ return {
269879
+ outroMessage: `Using workspace API key: ${theme.styles.bold(workspaceApiKey.slice(0, 10))}`
269880
+ };
269881
+ }
269882
+ const auth = await readAuth();
269883
+ return { outroMessage: `Logged in as: ${theme.styles.bold(auth.email)}` };
269884
+ }
269885
+ function getWhoamiCommand() {
269886
+ return new Base44Command("whoami", { requireAppContext: false }).description("Display current authenticated user").action(whoami);
269887
+ }
269888
+
269889
+ // src/cli/commands/branches/index.ts
269890
+ async function listBranchesAction({
269891
+ log,
269892
+ runTask,
269893
+ jsonMode
269894
+ }) {
269895
+ const remote = await runTask("Fetching branches", () => listBranches());
269896
+ const branches = [
269897
+ { name: "main", status: "active" },
269898
+ ...remote.map((branch) => ({
269899
+ name: branch.branch_name,
269900
+ status: branch.status
269901
+ }))
269902
+ ];
269903
+ if (jsonMode)
269904
+ return { stdout: `${JSON.stringify({ branches })}
269905
+ ` };
269906
+ for (const branch of branches)
269907
+ log.message(`${branch.name} (${branch.status})`);
269908
+ return { outroMessage: `${branches.length} branches` };
269909
+ }
269910
+ function getBranchesCommand() {
269911
+ return new Command2("branches").description("Discover an app's branches").addCommand(new Base44Command("list").description("List main and active branch names for use with --branch").action(listBranchesAction));
269912
+ }
269913
+
269453
269914
  // src/core/model.ts
269454
269915
  var MODELS = [
269455
269916
  {
@@ -269502,7 +269963,7 @@ async function saveBuilderModel(userId, modelId) {
269502
269963
  }
269503
269964
  var displayName = (id) => MODELS.find((m) => m.id === id)?.name ?? id ?? "Automatic";
269504
269965
 
269505
- // src/cli/commands/app/model.ts
269966
+ // src/cli/commands/builder/model.ts
269506
269967
  async function modelAction({ log, jsonMode }, input) {
269507
269968
  const me = await getMe();
269508
269969
  const current = me.builder_model ?? null;
@@ -269523,7 +269984,7 @@ async function modelAction({ log, jsonMode }, input) {
269523
269984
  log.message(`${marker} ${active ? theme.styles.bold(m.name) : m.name}${note}`);
269524
269985
  }
269525
269986
  return {
269526
- outroMessage: `Current: ${theme.styles.bold(displayName(current))}. Set with \`base44 app model <name>\`.`
269987
+ outroMessage: `Current: ${theme.styles.bold(displayName(current))}. Set with \`base44 builder model <name>\`.`
269527
269988
  };
269528
269989
  }
269529
269990
  const pick = resolvePick(input);
@@ -269544,9 +270005,9 @@ function getModelCommand() {
269544
270005
  return command;
269545
270006
  }
269546
270007
 
269547
- // src/cli/commands/app/shared.ts
270008
+ // src/cli/commands/builder/shared.ts
269548
270009
  import { mkdir as mkdir3, writeFile as writeFile2 } from "node:fs/promises";
269549
- import { join as join21 } from "node:path";
270010
+ import { basename as basename6, join as join25, relative as relative6, resolve as resolve8 } from "node:path";
269550
270011
 
269551
270012
  // src/cli/commands/code/render.ts
269552
270013
  var TOOL_ALIASES = {
@@ -269554,6 +270015,10 @@ var TOOL_ALIASES = {
269554
270015
  read_repo_file: "read",
269555
270016
  write_repo_file: "write",
269556
270017
  edit_repo_file: "edit",
270018
+ read_file: "read",
270019
+ write_file: "write",
270020
+ find_replace: "edit",
270021
+ delete_file: "delete",
269557
270022
  set_secrets: "secrets",
269558
270023
  generate_development_secrets: "secrets",
269559
270024
  create_pull_request: "pr",
@@ -269567,7 +270032,55 @@ var TOOL_ALIASES = {
269567
270032
  preview_screenshot: "screenshot",
269568
270033
  connect_github_account: "github"
269569
270034
  };
269570
- var QUIET_OK_RESULTS = new Set(["read", "write", "edit", "reload"]);
270035
+ var QUIET_OK_RESULTS = new Set(["read", "write", "edit", "reload", "delete"]);
270036
+ var RESULT_LINE_MAX = 110;
270037
+ var DIFF_LINES_MAX = 12;
270038
+ var ERROR_LINES_MAX = 8;
270039
+ function renderEntry(entry, options = {}) {
270040
+ if (typeof entry === "string")
270041
+ return entry;
270042
+ if ("running" in entry)
270043
+ return runningLine(entry.running, entry.startedAt);
270044
+ return eventLine(entry.event, entry.elapsedMs, options) ?? "";
270045
+ }
270046
+ var RUNNING_DOT = "#E86B3C";
270047
+ var PULSE_MS = 500;
270048
+ function runningLine(event, startedAt, now = Date.now()) {
270049
+ const lit = Math.floor(now / PULSE_MS) % 2 === 0;
270050
+ const dot = lit ? source_default.hex(RUNNING_DOT)("●") : source_default.dim("●");
270051
+ const alias = toolAlias(event.name);
270052
+ const title = source_default.bold(event.label || alias);
270053
+ const inline = !event.label && event.summary ? ` ${source_default.dim(event.summary)}` : "";
270054
+ const seconds = Math.round((now - startedAt) / 1000);
270055
+ const took = seconds >= 3 ? ` ${source_default.dim(`· ${seconds}s`)}` : "";
270056
+ const detail = event.label && event.summary ? `
270057
+ ${source_default.dim(`└ ${alias}: ${event.summary}`)}` : "";
270058
+ return `${dot} ${title}${inline}${took}${detail}`;
270059
+ }
270060
+ function fold2(lines, max, options) {
270061
+ if (options.verbose || lines.length <= max)
270062
+ return lines;
270063
+ const hint = options.foldHint ? ` (${options.foldHint})` : "";
270064
+ return [
270065
+ ...lines.slice(0, max),
270066
+ source_default.dim(`… +${lines.length - max} lines${hint}`)
270067
+ ];
270068
+ }
270069
+ var str = (v) => typeof v === "string" ? v : undefined;
270070
+ function diffLines(args) {
270071
+ const find = str(args.find);
270072
+ const replace = str(args.replace);
270073
+ if (find == null && replace == null)
270074
+ return null;
270075
+ return [
270076
+ ...(find ?? "").split(`
270077
+ `).map((l) => source_default.red(`- ${l}`)),
270078
+ ...(replace ?? "").split(`
270079
+ `).map((l) => source_default.green(`+ ${l}`))
270080
+ ];
270081
+ }
270082
+ var indent = (lines) => lines.map((l) => ` ${l}`).join(`
270083
+ `);
269571
270084
  function toolAlias(name) {
269572
270085
  return TOOL_ALIASES[name] ?? name;
269573
270086
  }
@@ -269632,7 +270145,7 @@ function formatDuration2(ms) {
269632
270145
  return `${seconds}s`;
269633
270146
  return `${Math.floor(seconds / 60)}m ${String(seconds % 60).padStart(2, "0")}s`;
269634
270147
  }
269635
- function eventLine(event, elapsedMs) {
270148
+ function eventLine(event, elapsedMs, options = {}) {
269636
270149
  switch (event.kind) {
269637
270150
  case "thinking":
269638
270151
  return source_default.dim(`✻ ${event.text}`);
@@ -269653,12 +270166,40 @@ function eventLine(event, elapsedMs) {
269653
270166
  const paramsLine = event.label && event.summary ? `
269654
270167
  ${source_default.dim(`${alias}: ${event.summary}`)}` : "";
269655
270168
  const head = `${mark} ${title}${inlineDetail}${took}${paramsLine}`;
270169
+ const args = event.args ?? null;
270170
+ if (event.ok && alias === "edit" && args) {
270171
+ const diff = diffLines(args);
270172
+ if (diff)
270173
+ return `${head}
270174
+ ${indent(fold2(diff, DIFF_LINES_MAX, options))}`;
270175
+ }
270176
+ if (event.ok && alias === "write" && args && str(args.content) != null) {
270177
+ const lines = str(args.content).split(`
270178
+ `);
270179
+ const count = source_default.dim(`+${lines.length} lines`);
270180
+ if (!options.verbose)
270181
+ return `${head} ${count}`;
270182
+ return `${head} ${count}
270183
+ ${indent(lines.map((l) => source_default.dim(l)))}`;
270184
+ }
269656
270185
  if (event.ok && (QUIET_OK_RESULTS.has(alias) || !event.result)) {
269657
270186
  return head;
269658
270187
  }
269659
- const result = event.ok ? source_default.dim(linkifyUrls(event.result)) : source_default.red(linkifyUrls(event.result));
269660
- return `${head}${event.result ? `
269661
- ${result}` : ""}`;
270188
+ const paint = event.ok ? source_default.dim : source_default.red;
270189
+ const lines = event.result.split(`
270190
+ `).map((l) => l.trimEnd()).filter((l, i, all) => l.length > 0 || i > 0 && i < all.length - 1);
270191
+ if (event.ok && !options.verbose) {
270192
+ const first = lines[0] ?? "";
270193
+ const cut = first.length > RESULT_LINE_MAX ? `${first.slice(0, RESULT_LINE_MAX)}…` : first;
270194
+ const more = lines.length - 1;
270195
+ const hint = options.foldHint ? ` (${options.foldHint})` : "";
270196
+ const tail = more > 0 ? source_default.dim(` … +${more} lines${hint}`) : "";
270197
+ return `${head}
270198
+ ${paint(linkifyUrls(cut))}${tail}`;
270199
+ }
270200
+ const shown = fold2(lines.map((l) => paint(linkifyUrls(l))), event.ok ? Number.POSITIVE_INFINITY : ERROR_LINES_MAX, options);
270201
+ return `${head}
270202
+ ${indent(shown)}`;
269662
270203
  }
269663
270204
  }
269664
270205
  }
@@ -269764,7 +270305,10 @@ function createTurnStream(interactive, write = (text) => process.stdout.write(te
269764
270305
  elapsedMs = Date.now() - started;
269765
270306
  running.delete(event.id);
269766
270307
  }
269767
- const line = eventLine(event, elapsedMs);
270308
+ const line = eventLine(event, elapsedMs, {
270309
+ verbose: options.verbose,
270310
+ foldHint: "--verbose shows everything"
270311
+ });
269768
270312
  if (line == null)
269769
270313
  return;
269770
270314
  if (interactive) {
@@ -269799,8 +270343,15 @@ var CreatedAppSchema = object({
269799
270343
  name: string2().nullish(),
269800
270344
  imported_repo_url: string2().nullish()
269801
270345
  });
270346
+ var WixCreatedAppSchema = object({
270347
+ app_id: string2().min(1),
270348
+ client_creation_id: string2().min(1)
270349
+ });
269802
270350
  var AppStateSchema = object({
269803
270351
  id: string2(),
270352
+ app_type: string2().nullish(),
270353
+ is_managed_source_code: boolean2().nullish(),
270354
+ imported_repo_url: string2().nullish(),
269804
270355
  status: object({
269805
270356
  state: string2().nullish(),
269806
270357
  message: string2().nullish()
@@ -269852,6 +270403,23 @@ function parseOrThrow(schema, payload, what) {
269852
270403
  function branchScope(branchId) {
269853
270404
  return branchId ? { branch_id: branchId } : {};
269854
270405
  }
270406
+ async function createWixLaunchedApp(options) {
270407
+ let response;
270408
+ try {
270409
+ response = await base44Client.post("api/wix/create-app", {
270410
+ timeout: false,
270411
+ json: {
270412
+ prompt: options.prompt,
270413
+ signed_instance: options.signedInstance,
270414
+ ...options.wixClientId ? { wix_client_id: options.wixClientId } : {}
270415
+ }
270416
+ });
270417
+ } catch (error) {
270418
+ throw await ApiError.fromHttpError(error, "creating app via Wix launch");
270419
+ }
270420
+ const created = parseOrThrow(WixCreatedAppSchema, await response.json(), "app");
270421
+ return { id: created.app_id, client_creation_id: created.client_creation_id };
270422
+ }
269855
270423
  async function createApp(options) {
269856
270424
  let response;
269857
270425
  try {
@@ -269900,7 +270468,9 @@ async function getAppState(appId) {
269900
270468
  let response;
269901
270469
  try {
269902
270470
  response = await base44Client.get(`api/apps/${appId}`, {
269903
- searchParams: { fields: "id,status" }
270471
+ searchParams: {
270472
+ fields: "id,status,app_type,is_managed_source_code,imported_repo_url"
270473
+ }
269904
270474
  });
269905
270475
  } catch (error) {
269906
270476
  throw await ApiError.fromHttpError(error, "reading app status");
@@ -269961,7 +270531,7 @@ async function getPreviewUrl() {
269961
270531
  return /^https?:\/\//.test(url) ? url : `https://${url}`;
269962
270532
  }
269963
270533
 
269964
- // src/cli/commands/app/shared.ts
270534
+ // src/cli/commands/builder/shared.ts
269965
270535
  var APP_NAME_RE = /^[A-Za-z0-9._-]+$/;
269966
270536
  var NAME_STOPWORDS = new Set("a an the and or of for with to in on that this its it my our your me".split(" "));
269967
270537
  var FALLBACK_WORDS = [
@@ -269979,17 +270549,51 @@ function inventAppName(prompt) {
269979
270549
  function repoBasename(repoUrl) {
269980
270550
  return repoUrl.replace(/\/+$/, "").replace(/\.git$/, "").split("/").pop() ?? "app";
269981
270551
  }
270552
+ function parseWixLaunchUrl(raw) {
270553
+ let url;
270554
+ try {
270555
+ url = new URL(raw.trim());
270556
+ } catch {
270557
+ throw new InvalidInputError("--wix-launch expects the full launch URL.");
270558
+ }
270559
+ const fragment = new URLSearchParams(url.hash.replace(/^#/, ""));
270560
+ const signedInstance = fragment.get("wix-signed-instance")?.trim();
270561
+ if (!signedInstance) {
270562
+ throw new InvalidInputError("The launch URL has no #wix-signed-instance — copy the whole URL the funnel printed.");
270563
+ }
270564
+ const prompt = url.searchParams.get("prompt")?.trim();
270565
+ const wixClientId = fragment.get("wix-client-id")?.trim();
270566
+ return {
270567
+ ...prompt ? { prompt } : {},
270568
+ signedInstance,
270569
+ ...wixClientId ? { wixClientId } : {}
270570
+ };
270571
+ }
269982
270572
  async function createAndLinkApp(options) {
269983
270573
  if (options.name && !APP_NAME_RE.test(options.name)) {
269984
270574
  throw new InvalidInputError("The name becomes a directory — letters, digits, dots, dashes and underscores only.");
269985
270575
  }
269986
- const name = options.name ?? (options.importRepo ? repoBasename(options.importRepo) : inventAppName(options.prompt));
269987
- const targetDir = join21(process.cwd(), name);
270576
+ const cwd = process.cwd();
270577
+ const fallbackName = () => options.importRepo ? repoBasename(options.importRepo) : inventAppName(options.prompt ?? options.wixLaunch?.prompt);
270578
+ const chosenDir = options.path ? resolve8(cwd, options.path) : await isDirEmpty(cwd) ? cwd : undefined;
270579
+ const dirBase = chosenDir ? basename6(chosenDir) : undefined;
270580
+ const name = options.name ?? (dirBase && APP_NAME_RE.test(dirBase) ? dirBase : fallbackName());
270581
+ const targetDir = chosenDir ?? join25(cwd, name);
270582
+ const here = targetDir === cwd;
270583
+ const dirName = here ? "." : relative6(cwd, targetDir) || name;
269988
270584
  await mkdir3(targetDir, { recursive: true });
269989
270585
  if (await appConfigExists(targetDir)) {
269990
- throw new InvalidInputError(`./${name} is already linked to a Base44 app. Pick another name.`);
269991
- }
269992
- const created = options.importRepo ? await createImportedApp({
270586
+ throw new InvalidInputError(here ? "This directory is already linked to a Base44 app. Run `base44 code` here to keep building it, or pass --path for a new one." : `./${dirName} is already linked to a Base44 app. Pick another name or --path.`);
270587
+ }
270588
+ let clientCreationId;
270589
+ const created = options.wixLaunch ? await createWixLaunchedApp({
270590
+ prompt: options.prompt ?? options.wixLaunch.prompt ?? "",
270591
+ signedInstance: options.wixLaunch.signedInstance,
270592
+ wixClientId: options.wixLaunch.wixClientId
270593
+ }).then((c) => {
270594
+ clientCreationId = c.client_creation_id;
270595
+ return c;
270596
+ }) : options.importRepo ? await createImportedApp({
269993
270597
  appName: name,
269994
270598
  repoUrl: options.importRepo,
269995
270599
  sourceMode: options.mode ?? "direct",
@@ -269998,9 +270602,9 @@ async function createAndLinkApp(options) {
269998
270602
  prompt: options.prompt
269999
270603
  }) : await createApp({ appName: name, prompt: options.prompt });
270000
270604
  await writeAppConfig(targetDir, created.id);
270001
- await mkdir3(join21(targetDir, "base44"), { recursive: true });
270605
+ await mkdir3(join25(targetDir, "base44"), { recursive: true });
270002
270606
  try {
270003
- await writeFile2(join21(targetDir, "base44", "config.jsonc"), `// Base44 project configuration.
270607
+ await writeFile2(join25(targetDir, "base44", "config.jsonc"), `// Base44 project configuration.
270004
270608
  {
270005
270609
  "name": ${JSON.stringify(name)}
270006
270610
  }
@@ -270011,16 +270615,49 @@ async function createAndLinkApp(options) {
270011
270615
  id: created.id,
270012
270616
  editorUrl: `${getBase44ApiUrl()}/apps/${created.id}/editor/preview`,
270013
270617
  repoUrl: created.imported_repo_url ?? undefined,
270014
- dirName: name,
270015
- targetDir
270016
- };
270618
+ dirName,
270619
+ targetDir,
270620
+ here,
270621
+ ...clientCreationId ? { clientCreationId } : {}
270622
+ };
270623
+ }
270624
+ function repoLabel(url) {
270625
+ return url.replace(/^https?:\/\//, "").replace(/\.git$/, "").replace(/\/$/, "");
270626
+ }
270627
+ function appTypeChip(state) {
270628
+ switch (state.app_type ?? "user_app") {
270629
+ case "imported_app":
270630
+ return state.imported_repo_url ? repoLabel(state.imported_repo_url) : "repository app";
270631
+ case "user_game":
270632
+ return "game";
270633
+ case "mobile_app":
270634
+ return "mobile app";
270635
+ case "slide":
270636
+ return "slides";
270637
+ case "user_agent":
270638
+ return "superagent";
270639
+ default:
270640
+ return "web app";
270641
+ }
270642
+ }
270643
+ async function assertBuilderApp(appId) {
270644
+ const state = await getAppState(appId);
270645
+ if (state.is_managed_source_code === false) {
270646
+ throw new InvalidInputError("This project is code-first (base44 create): you own the source and the builder cannot work on it. Run `base44 builder new` for an agent-built app.");
270647
+ }
270648
+ if (state.app_type === "user_agent") {
270649
+ throw new InvalidInputError("This is a Superagent, not an app the builder works on. Superagent has no CLI surface yet.");
270650
+ }
270651
+ return state;
270017
270652
  }
270018
270653
  function nextStepsLines(app) {
270654
+ const cd = app.here ? "" : `cd ${app.dirName} && `;
270019
270655
  return [
270020
270656
  "",
270021
- `${source_default.bold("Your app lives in")} ./${app.dirName}`,
270022
- source_default.dim(` cd ${app.dirName} && base44 code # keep building with the agent`),
270023
- source_default.dim(` cd ${app.dirName} && base44 app send "…" # one non-interactive turn`)
270657
+ `${source_default.bold("Your app lives in")} ${app.here ? "./ (this directory)" : `./${app.dirName}`}`,
270658
+ source_default.dim(` ${cd}base44 code # keep building with the agent`),
270659
+ source_default.dim(` ${cd}base44 builder send "…" # one non-interactive turn`),
270660
+ source_default.dim(` files live remotely — ${cd}base44 sandbox ls to look, base44 eject for a copy`)
270024
270661
  ];
270025
270662
  }
270026
270663
  async function githubReauthLines(error) {
@@ -270043,12 +270680,27 @@ function newStreamState() {
270043
270680
  return { perMessage: new Map };
270044
270681
  }
270045
270682
  var TOOL_SETTLED = new Set(["success", "error", "stopped"]);
270683
+ function fullText(value) {
270684
+ if (typeof value === "string")
270685
+ return value.trimEnd();
270686
+ if (value == null)
270687
+ return "";
270688
+ return JSON.stringify(value);
270689
+ }
270690
+ function toolArgs(argumentsString) {
270691
+ try {
270692
+ const parsed = JSON.parse(argumentsString ?? "");
270693
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
270694
+ } catch {
270695
+ return null;
270696
+ }
270697
+ }
270046
270698
  function oneLine(value, max) {
270047
270699
  const text = typeof value === "string" ? value : value == null ? "" : JSON.stringify(value);
270048
270700
  const flat = text.replace(/\s+/g, " ").trim();
270049
270701
  return flat.length > max ? `${flat.slice(0, max)}…` : flat;
270050
270702
  }
270051
- var SALIENT_KEYS = ["command", "path", "file_path", "title"];
270703
+ var SALIENT_KEYS = ["command", "path", "file_path", "title", "find"];
270052
270704
  function salvageKey(raw, keys) {
270053
270705
  for (const key of keys) {
270054
270706
  const match = raw.match(new RegExp(`"${key}"\\s*:\\s*"((?:[^"\\\\]|\\\\.)*)"`));
@@ -270077,6 +270729,10 @@ function toolMeta(name, argumentsString) {
270077
270729
  read_repo_file: pick("path") ?? pick("file_path"),
270078
270730
  write_repo_file: pick("path") ?? pick("file_path"),
270079
270731
  edit_repo_file: pick("path") ?? pick("file_path"),
270732
+ read_file: pick("file_path") ?? pick("path"),
270733
+ write_file: pick("file_path") ?? pick("path"),
270734
+ find_replace: pick("file_path") ?? pick("path"),
270735
+ delete_file: pick("file_path") ?? pick("path"),
270080
270736
  create_pull_request: pick("title"),
270081
270737
  reload_preview: ""
270082
270738
  };
@@ -270127,7 +270783,10 @@ function diffConversation(state, messages) {
270127
270783
  }
270128
270784
  for (const tool of message.tool_calls ?? []) {
270129
270785
  if (!progress.announcedTools.has(tool.id)) {
270130
- progress.announcedTools.set(tool.id, toolMeta(tool.name, tool.arguments_string));
270786
+ progress.announcedTools.set(tool.id, {
270787
+ ...toolMeta(tool.name, tool.arguments_string),
270788
+ args: toolArgs(tool.arguments_string)
270789
+ });
270131
270790
  const meta = progress.announcedTools.get(tool.id);
270132
270791
  events.push({
270133
270792
  kind: "tool_start",
@@ -270158,7 +270817,8 @@ function diffConversation(state, messages) {
270158
270817
  label: labelTense(meta.label, "done"),
270159
270818
  summary: meta.summary,
270160
270819
  ok: status === "success",
270161
- result: oneLine(tool.results, 110)
270820
+ result: fullText(tool.results),
270821
+ args: meta.args
270162
270822
  });
270163
270823
  }
270164
270824
  }
@@ -270232,9 +270892,90 @@ async function streamConversationUntilSettled(onEvent, options = {}) {
270232
270892
  return "timeout";
270233
270893
  }
270234
270894
 
270235
- // src/cli/commands/app/new.ts
270895
+ // src/cli/commands/builder/send.ts
270896
+ function lastAssistantReply(turn) {
270897
+ const messages = turn.conversation?.messages ?? [];
270898
+ for (let i = messages.length - 1;i >= 0; i--) {
270899
+ const { role, content } = messages[i];
270900
+ if (role === "assistant" && typeof content === "string" && content.trim()) {
270901
+ return content.trim();
270902
+ }
270903
+ }
270904
+ return;
270905
+ }
270906
+ function ndjsonWriter() {
270907
+ return (record) => process.stdout.write(`${JSON.stringify(record)}
270908
+ `);
270909
+ }
270910
+ async function sendAction(ctx, message, options) {
270911
+ if (options.streamJson && ctx.jsonMode) {
270912
+ throw new InvalidInputError("--stream-json and --json are exclusive.");
270913
+ }
270914
+ if (ctx.app)
270915
+ await assertBuilderApp(ctx.app.id);
270916
+ const branchId = await resolveBranchId(ctx);
270917
+ if (options.streamJson) {
270918
+ const write = ndjsonWriter();
270919
+ const turn = await streamConversationDuring(() => sendTurn(message, branchId), ({ kind, ...event }) => write({ type: kind, ...event }), { branchId });
270920
+ write({
270921
+ type: "result",
270922
+ queued: turn.queued === true,
270923
+ status: turn.status?.state ?? "ready",
270924
+ error_source: turn.status?.error_source ?? null,
270925
+ reply: lastAssistantReply(turn) ?? null
270926
+ });
270927
+ return {};
270928
+ }
270929
+ if (ctx.jsonMode) {
270930
+ const turn = await ctx.runTask("Agent working (a turn can take minutes)", () => sendTurn(message, branchId));
270931
+ if (turn.queued)
270932
+ return { stdout: `${JSON.stringify({ queued: true })}
270933
+ ` };
270934
+ return {
270935
+ stdout: `${JSON.stringify({
270936
+ status: turn.status?.state ?? "ready",
270937
+ error_source: turn.status?.error_source ?? null,
270938
+ reply: lastAssistantReply(turn) ?? null
270939
+ })}
270940
+ `
270941
+ };
270942
+ }
270943
+ const stream = createTurnStream(process.stdout.isTTY === true, undefined, {
270944
+ verbose: options.verbose
270945
+ });
270946
+ let turn;
270947
+ try {
270948
+ turn = await streamConversationDuring(() => sendTurn(message, branchId), stream.onEvent, { branchId });
270949
+ } finally {
270950
+ stream.stop();
270951
+ }
270952
+ if (turn.queued) {
270953
+ return {
270954
+ outroMessage: "The agent is busy with an earlier message — yours was queued and runs next."
270955
+ };
270956
+ }
270957
+ if (turn.status?.state === "error") {
270958
+ return {
270959
+ outroMessage: `Turn failed (${turn.status.error_source ?? "unknown"}) — see the editor for details.`
270960
+ };
270961
+ }
270962
+ return { outroMessage: "Turn finished." };
270963
+ }
270964
+ function getSendCommand() {
270965
+ const command = new Base44Command("send", { supportsBranch: true });
270966
+ command.description("Send the agent one message and stream the turn until it finishes").argument("<message>", "What you want the agent to do").option("--verbose", "Show every tool result in full (no folding)").option("--stream-json", "Emit each stream event as a JSON line as it happens, then a final result line").action(sendAction);
270967
+ return command;
270968
+ }
270969
+
270970
+ // src/cli/commands/builder/new.ts
270236
270971
  var POLL_TIMEOUT_MS = 20 * 60000;
270237
270972
  var MODES = ["direct", "fork", "copy"];
270973
+ async function readStdin2() {
270974
+ const chunks = [];
270975
+ for await (const chunk of process.stdin)
270976
+ chunks.push(chunk);
270977
+ return Buffer.concat(chunks).toString("utf8");
270978
+ }
270238
270979
  async function newAction({ log, runTask, jsonMode }, prompt, options) {
270239
270980
  if (options.mode && !MODES.includes(options.mode)) {
270240
270981
  throw new InvalidInputError("--mode must be direct, fork, or copy.");
@@ -270242,9 +270983,20 @@ async function newAction({ log, runTask, jsonMode }, prompt, options) {
270242
270983
  if ((options.mode || options.repoName || options.fromBranch) && !options.import) {
270243
270984
  throw new InvalidInputError("--mode, --repo-name and --from-branch apply only with --import <repo>.");
270244
270985
  }
270245
- if (!prompt && !options.import) {
270246
- throw new InvalidInputError('Describe the app ("<prompt>") or pass --import <repo>.');
270986
+ if (options.wixLaunch && options.import) {
270987
+ throw new InvalidInputError("--wix-launch and --import are exclusive.");
270247
270988
  }
270989
+ const wixLaunch = options.wixLaunch ? parseWixLaunchUrl(options.wixLaunch === "-" ? await readStdin2() : options.wixLaunch) : undefined;
270990
+ if (wixLaunch && !prompt && !wixLaunch.prompt) {
270991
+ throw new InvalidInputError("The launch URL has no ?prompt= — pass the prompt as the argument.");
270992
+ }
270993
+ if (!prompt && !options.import && !wixLaunch) {
270994
+ throw new InvalidInputError('Describe the app ("<prompt>") or pass --import <repo> / --wix-launch <url>.');
270995
+ }
270996
+ if (options.streamJson && jsonMode) {
270997
+ throw new InvalidInputError("--stream-json and --json are exclusive.");
270998
+ }
270999
+ const ndjson = options.streamJson ? ndjsonWriter() : null;
270248
271000
  let app;
270249
271001
  try {
270250
271002
  app = await runTask(options.import ? "Importing the repository" : "Creating your app", () => createAndLinkApp({
@@ -270253,18 +271005,33 @@ async function newAction({ log, runTask, jsonMode }, prompt, options) {
270253
271005
  importRepo: options.import,
270254
271006
  mode: options.mode,
270255
271007
  repoName: options.repoName,
270256
- fromBranch: options.fromBranch
271008
+ fromBranch: options.fromBranch,
271009
+ path: options.path,
271010
+ wixLaunch
270257
271011
  }));
270258
271012
  } catch (error) {
270259
271013
  for (const line of await githubReauthLines(error) ?? [])
270260
271014
  log.message(line);
270261
271015
  throw error;
270262
271016
  }
270263
- if (!jsonMode) {
271017
+ if (ndjson) {
271018
+ ndjson({
271019
+ type: "created",
271020
+ id: app.id,
271021
+ repo_url: app.repoUrl ?? null,
271022
+ editor_url: app.editorUrl,
271023
+ dir: app.dirName,
271024
+ path: app.targetDir,
271025
+ ...app.clientCreationId ? { client_creation_id: app.clientCreationId } : {}
271026
+ });
271027
+ } else if (!jsonMode) {
270264
271028
  if (app.repoUrl)
270265
271029
  log.message(source_default.dim(`repo ${app.repoUrl}`));
271030
+ if (app.clientCreationId) {
271031
+ log.message(source_default.dim("wix launched — connector connected before the first turn"));
271032
+ }
270266
271033
  log.message(source_default.dim(`editor ${app.editorUrl}`));
270267
- log.message(source_default.dim(`linked ./${app.dirName}`));
271034
+ log.message(source_default.dim(`linked ${app.here ? "./ (this directory)" : `./${app.dirName}`}`));
270268
271035
  }
270269
271036
  let finalState;
270270
271037
  let previewUrl;
@@ -270273,10 +271040,16 @@ async function newAction({ log, runTask, jsonMode }, prompt, options) {
270273
271040
  const branchId = await resolveActiveBranchId().catch(() => {
270274
271041
  return;
270275
271042
  });
270276
- const stream = createTurnStream(process.stdout.isTTY === true && !jsonMode, undefined, { idleLabel: "provisioning the sandbox and starting the build" });
271043
+ const stream = createTurnStream(process.stdout.isTTY === true && !jsonMode && !ndjson, undefined, {
271044
+ idleLabel: "provisioning the sandbox and starting the build",
271045
+ verbose: options.verbose
271046
+ });
270277
271047
  try {
270278
271048
  const settled = await streamConversationUntilSettled((event) => {
270279
- if (!jsonMode)
271049
+ if (ndjson) {
271050
+ const { kind, ...rest } = event;
271051
+ ndjson({ type: kind, ...rest });
271052
+ } else if (!jsonMode)
270280
271053
  stream.onEvent(event);
270281
271054
  }, { branchId, timeoutMs: POLL_TIMEOUT_MS });
270282
271055
  finalState = settled === "timeout" ? "processing" : (await getAppState(app.id)).status?.state ?? "ready";
@@ -270289,6 +271062,15 @@ async function newAction({ log, runTask, jsonMode }, prompt, options) {
270289
271062
  stream.stop();
270290
271063
  }
270291
271064
  }
271065
+ if (ndjson) {
271066
+ ndjson({
271067
+ type: "result",
271068
+ id: app.id,
271069
+ preview_url: previewUrl ?? null,
271070
+ status: finalState ?? "created"
271071
+ });
271072
+ return {};
271073
+ }
270292
271074
  if (jsonMode) {
270293
271075
  return {
270294
271076
  stdout: `${JSON.stringify({
@@ -270298,7 +271080,8 @@ async function newAction({ log, runTask, jsonMode }, prompt, options) {
270298
271080
  preview_url: previewUrl ?? null,
270299
271081
  dir: app.dirName,
270300
271082
  path: app.targetDir,
270301
- status: finalState ?? "created"
271083
+ status: finalState ?? "created",
271084
+ ...app.clientCreationId ? { client_creation_id: app.clientCreationId } : {}
270302
271085
  })}
270303
271086
  `
270304
271087
  };
@@ -270314,7 +271097,7 @@ async function newAction({ log, runTask, jsonMode }, prompt, options) {
270314
271097
  }
270315
271098
  if (finalState === "processing") {
270316
271099
  return {
270317
- outroMessage: `Still building — follow it with \`base44 app status\`.`
271100
+ outroMessage: `Still building — follow it with \`base44 builder status\`.`
270318
271101
  };
270319
271102
  }
270320
271103
  return {
@@ -270323,78 +271106,11 @@ async function newAction({ log, runTask, jsonMode }, prompt, options) {
270323
271106
  }
270324
271107
  function getNewCommand() {
270325
271108
  const command = new Base44Command("new", { requireAppContext: false });
270326
- command.description("Create an app and start building: from a prompt (the Base44 template), or over an existing GitHub repo with --import").argument("[prompt]", "What to build; the first agent turn starts immediately").option("--import <repo>", "Build over an existing GitHub repository instead of the template").option("--mode <mode>", "How to import: direct, fork, or copy (default: direct)").option("--name <name>", "Directory and app name (invented when omitted)").option("--repo-name <name>", "Name for the new GitHub repo when forking/copying").option("--from-branch <name>", "Import a specific branch of the repo").action(newAction);
271109
+ command.description("Create an app and start building: from a prompt (the Base44 template), or over an existing GitHub repo with --import").argument("[prompt]", "What to build; the first agent turn starts immediately").option("--import <repo>", "Build over an existing GitHub repository instead of the template").option("--mode <mode>", "How to import: direct, fork, or copy (default: direct)").option("--name <name>", "Directory and app name (invented when omitted)").option("--path <dir>", "Directory to link (default: the current directory when empty, else ./<name>)").option("--repo-name <name>", "Name for the new GitHub repo when forking/copying").option("--from-branch <name>", "Import a specific branch of the repo").addOption(new Option2("--wix-launch <url>", `Create through a Wix launch URL (the funnel's ?prompt= and #wix-signed-instance); "-" reads it from stdin. A prompt argument is what the agent runs instead of the URL's`).env("BASE44_WIX_LAUNCH_URL")).option("--verbose", "Show every tool result in full (no folding)").option("--stream-json", "Emit each stream event as a JSON line as it happens, then a final result line").action(newAction);
270327
271110
  return command;
270328
271111
  }
270329
271112
 
270330
- // src/cli/commands/app/preview.ts
270331
- async function previewAction(ctx) {
270332
- const url = await ctx.runTask("Resolving preview URL (boots the sandbox if needed)", () => getPreviewUrl());
270333
- if (ctx.jsonMode)
270334
- return { stdout: `${JSON.stringify({ preview_url: url })}
270335
- ` };
270336
- ctx.log.message(url);
270337
- return { outroMessage: "Preview is live." };
270338
- }
270339
- function getPreviewCommand() {
270340
- const command = new Base44Command("preview");
270341
- command.description("Print the app's live preview URL").action(previewAction);
270342
- return command;
270343
- }
270344
-
270345
- // src/cli/commands/app/send.ts
270346
- function lastAssistantReply(turn) {
270347
- const messages = turn.conversation?.messages ?? [];
270348
- for (let i = messages.length - 1;i >= 0; i--) {
270349
- const { role, content } = messages[i];
270350
- if (role === "assistant" && typeof content === "string" && content.trim()) {
270351
- return content.trim();
270352
- }
270353
- }
270354
- return;
270355
- }
270356
- async function sendAction(ctx, message) {
270357
- const branchId = await resolveBranchId(ctx);
270358
- if (ctx.jsonMode) {
270359
- const turn = await ctx.runTask("Agent working (a turn can take minutes)", () => sendTurn(message, branchId));
270360
- if (turn.queued)
270361
- return { stdout: `${JSON.stringify({ queued: true })}
270362
- ` };
270363
- return {
270364
- stdout: `${JSON.stringify({
270365
- status: turn.status?.state ?? "ready",
270366
- error_source: turn.status?.error_source ?? null,
270367
- reply: lastAssistantReply(turn) ?? null
270368
- })}
270369
- `
270370
- };
270371
- }
270372
- const stream = createTurnStream(process.stdout.isTTY === true);
270373
- let turn;
270374
- try {
270375
- turn = await streamConversationDuring(() => sendTurn(message, branchId), stream.onEvent, { branchId });
270376
- } finally {
270377
- stream.stop();
270378
- }
270379
- if (turn.queued) {
270380
- return {
270381
- outroMessage: "The agent is busy with an earlier message — yours was queued and runs next."
270382
- };
270383
- }
270384
- if (turn.status?.state === "error") {
270385
- return {
270386
- outroMessage: `Turn failed (${turn.status.error_source ?? "unknown"}) — see the editor for details.`
270387
- };
270388
- }
270389
- return { outroMessage: "Turn finished." };
270390
- }
270391
- function getSendCommand() {
270392
- const command = new Base44Command("send", { supportsBranch: true });
270393
- command.description("Send the agent one message and stream the turn until it finishes").argument("<message>", "What you want the agent to do").action(sendAction);
270394
- return command;
270395
- }
270396
-
270397
- // src/cli/commands/app/status.ts
271113
+ // src/cli/commands/builder/status.ts
270398
271114
  async function statusAction(ctx) {
270399
271115
  const id = ctx.app?.id;
270400
271116
  const app = await ctx.runTask("Reading app status", () => getAppState(id));
@@ -270416,7 +271132,7 @@ function getStatusCommand() {
270416
271132
  return command;
270417
271133
  }
270418
271134
 
270419
- // src/cli/commands/app/stop.ts
271135
+ // src/cli/commands/builder/stop.ts
270420
271136
  async function stopAction(ctx) {
270421
271137
  const branchId = await resolveBranchId(ctx);
270422
271138
  await ctx.runTask("Stopping the running turn", () => stopTurn(branchId));
@@ -270431,470 +271147,9 @@ function getStopCommand() {
270431
271147
  return command;
270432
271148
  }
270433
271149
 
270434
- // src/cli/commands/app/index.ts
270435
- function getAppCommand() {
270436
- return new Command2("app").description("Build a Base44 app with the agent, non-interactively: create it, send turns, read status, get the preview").addCommand(getNewCommand()).addCommand(getSendCommand()).addCommand(getStatusCommand()).addCommand(getPreviewCommand()).addCommand(getStopCommand()).addCommand(getModelCommand());
270437
- }
270438
-
270439
- // src/cli/commands/auth/password-login.ts
270440
- import { dirname as dirname15, join as join22 } from "node:path";
270441
- async function passwordLoginAction({ log, runTask }, action) {
270442
- const shouldEnable = action === "enable";
270443
- const { project } = await readProjectConfig();
270444
- const configDir = dirname15(project.configPath);
270445
- const authDir = join22(configDir, project.authDir);
270446
- const updated = await runTask("Updating local auth config", async () => {
270447
- const current = await readAuthConfig(authDir) ?? DEFAULT_AUTH_CONFIG;
270448
- const merged = { ...current, enableUsernamePassword: shouldEnable };
270449
- await writeAuthConfig(authDir, merged);
270450
- return merged;
270451
- });
270452
- if (!shouldEnable && !hasAnyLoginMethod(updated)) {
270453
- log.warn("Disabling password auth will leave no login methods enabled. Users will be locked out.");
270454
- }
270455
- const newStatus = shouldEnable ? "enabled" : "disabled";
270456
- return {
270457
- outroMessage: `Username & password authentication ${newStatus} in local config. Run \`base44 auth push\` or \`base44 deploy\` to apply.`
270458
- };
270459
- }
270460
- function getPasswordLoginCommand() {
270461
- return new Base44Command("password-login").description("Enable or disable username & password authentication").addArgument(new Argument2("<action>", "enable or disable password authentication").choices(["enable", "disable"])).action(passwordLoginAction);
270462
- }
270463
-
270464
- // src/cli/commands/auth/pull.ts
270465
- import { dirname as dirname16, join as join23 } from "node:path";
270466
- async function pullAuthAction({
270467
- log,
270468
- runTask
270469
- }) {
270470
- const { project } = await readProjectConfig();
270471
- const configDir = dirname16(project.configPath);
270472
- const authDir = join23(configDir, project.authDir);
270473
- const remoteConfig = await runTask("Fetching auth config from Base44", async () => {
270474
- return await pullAuthConfig();
270475
- }, {
270476
- successMessage: "Auth config fetched successfully",
270477
- errorMessage: "Failed to fetch auth config"
270478
- });
270479
- const { written } = await runTask("Syncing auth config file", async () => {
270480
- return await writeAuthConfig(authDir, remoteConfig);
270481
- }, {
270482
- successMessage: "Auth config file synced successfully",
270483
- errorMessage: "Failed to sync auth config file"
270484
- });
270485
- if (written) {
270486
- log.success("Auth config written to local file");
270487
- } else {
270488
- log.info("Auth config is already up to date");
270489
- }
270490
- return {
270491
- outroMessage: `Pulled auth config to ${authDir} (overwrites local file)`
270492
- };
270493
- }
270494
- function getAuthPullCommand() {
270495
- return new Base44Command("pull").description("Pull auth config from Base44 to local file").action(pullAuthAction);
270496
- }
270497
-
270498
- // src/cli/commands/auth/push.ts
270499
- async function pushAuthAction({ isNonInteractive, log, runTask }, options) {
270500
- const { authConfig } = await readProjectConfig();
270501
- if (authConfig.length === 0) {
270502
- log.info("No local auth config found");
270503
- return {
270504
- outroMessage: "No auth config to push. Run `base44 auth pull` to fetch the remote config first."
270505
- };
270506
- }
270507
- if (!hasAnyLoginMethod(authConfig[0])) {
270508
- log.warn("This config has no login methods enabled. Pushing it will lock out all users.");
270509
- }
270510
- if (!options.yes) {
270511
- if (isNonInteractive) {
270512
- throw new InvalidInputError("--yes is required in non-interactive mode");
270513
- }
270514
- const shouldPush = await Re({
270515
- message: "Push auth config to Base44?"
270516
- });
270517
- if (Ct(shouldPush) || !shouldPush) {
270518
- return { outroMessage: "Push cancelled" };
270519
- }
270520
- }
270521
- await runTask("Pushing auth config to Base44", async () => {
270522
- return await pushAuthConfig(authConfig[0] ?? null);
270523
- }, {
270524
- successMessage: "Auth config pushed successfully",
270525
- errorMessage: "Failed to push auth config"
270526
- });
270527
- return {
270528
- outroMessage: "Auth config pushed to Base44"
270529
- };
270530
- }
270531
- function getAuthPushCommand() {
270532
- return new Base44Command("push").description("Push local auth config to Base44").option("-y, --yes", "Skip confirmation prompt").action(pushAuthAction);
270533
- }
270534
-
270535
- // src/cli/commands/auth/social-login.ts
270536
- import { dirname as dirname17, join as join24, resolve as resolve6 } from "node:path";
270537
- var PROVIDER_LABELS = {
270538
- google: "Google",
270539
- microsoft: "Microsoft",
270540
- facebook: "Facebook",
270541
- apple: "Apple"
270542
- };
270543
- var VALID_PROVIDER_NAMES = Object.keys(SOCIAL_PROVIDERS);
270544
- var PROVIDER_OAUTH_CLI = {
270545
- google: {
270546
- envVar: "google_oauth_client_secret",
270547
- promptMessage: "Enter Google OAuth client secret"
270548
- }
270549
- };
270550
- function hasSecretOptions(options) {
270551
- return Boolean(options.clientSecret || options.clientSecretStdin || options.envFile);
270552
- }
270553
- function hasCustomOAuthOptions(options) {
270554
- return Boolean(options.clientId || hasSecretOptions(options));
270555
- }
270556
- async function socialLoginAction({ log, isNonInteractive, runTask }, provider, action, options) {
270557
- const shouldEnable = action === "enable";
270558
- const providerInfo = SOCIAL_PROVIDERS[provider];
270559
- const label = PROVIDER_LABELS[provider];
270560
- const hasOAuthOptions = hasCustomOAuthOptions(options);
270561
- if (hasOAuthOptions && !providerInfo.customOAuth) {
270562
- throw new InvalidInputError(`Custom OAuth options are only supported for providers with custom OAuth (e.g., google). Use: base44 auth social-login ${provider} ${action}`);
270563
- }
270564
- if (hasOAuthOptions && !shouldEnable) {
270565
- throw new InvalidInputError(`Custom OAuth options cannot be used with disable. To disable ${label} login: base44 auth social-login ${provider} disable`);
270566
- }
270567
- if (hasSecretOptions(options) && !options.clientId) {
270568
- throw new InvalidInputError(`--client-id is required when providing a client secret. Use: base44 auth social-login ${provider} enable --client-id <id> --client-secret <secret>`);
270569
- }
270570
- const oauth = providerInfo.customOAuth;
270571
- const oauthCli = PROVIDER_OAUTH_CLI[provider];
270572
- const useCustomOAuth = shouldEnable && hasOAuthOptions && oauth != null;
270573
- let clientSecret;
270574
- if (useCustomOAuth && oauth && oauthCli && hasSecretOptions(options)) {
270575
- if (options.envFile) {
270576
- const secrets = await parseEnvFile(resolve6(options.envFile));
270577
- const value = secrets[oauthCli.envVar];
270578
- if (!value) {
270579
- throw new InvalidInputError(`Key "${oauthCli.envVar}" not found in ${options.envFile}.`);
270580
- }
270581
- clientSecret = value;
270582
- } else {
270583
- clientSecret = await resolveSecret({
270584
- flagValue: options.clientSecret,
270585
- fromStdin: options.clientSecretStdin,
270586
- envVar: oauthCli.envVar,
270587
- promptMessage: oauthCli.promptMessage,
270588
- isNonInteractive,
270589
- name: "client secret",
270590
- hints: [
270591
- {
270592
- message: `Provide via flag: base44 auth social-login ${provider} enable --client-id <id> --client-secret <secret>`,
270593
- command: `base44 auth social-login ${provider} enable --client-id <id> --client-secret <secret>`
270594
- },
270595
- {
270596
- message: `Provide via stdin: echo <secret> | base44 auth social-login ${provider} enable --client-id <id> --client-secret-stdin`
270597
- },
270598
- {
270599
- message: `Provide via env: ${oauthCli.envVar}=<secret> base44 auth social-login ${provider} enable --client-id <id>`
270600
- }
270601
- ]
270602
- });
270603
- }
270604
- }
270605
- const { project } = await readProjectConfig();
270606
- const configDir = dirname17(project.configPath);
270607
- const authDir = join24(configDir, project.authDir);
270608
- const { config: updated } = await runTask("Updating local auth config", async () => updateSocialLoginConfig(authDir, provider, shouldEnable, useCustomOAuth && options.clientId ? { clientId: options.clientId } : undefined));
270609
- if (clientSecret) {
270610
- await runTask("Saving client secret", async () => pushCustomOAuthSecret(provider, clientSecret));
270611
- }
270612
- if (!shouldEnable && !hasAnyLoginMethod(updated)) {
270613
- log.warn(`Disabling ${label} login will leave no login methods enabled. Users will be locked out.`);
270614
- }
270615
- const newStatus = shouldEnable ? "enabled" : "disabled";
270616
- const oauthNote = useCustomOAuth ? " with custom OAuth" : "";
270617
- let outroMessage = `${label} login ${newStatus}${oauthNote} in local config. Run \`base44 auth push\` or \`base44 deploy\` to apply.`;
270618
- if (useCustomOAuth && !clientSecret) {
270619
- outroMessage += `
270620
- Remember to push the client secret separately: base44 secrets set --env-file <path>`;
270621
- }
270622
- return { outroMessage };
270623
- }
270624
- function getSocialLoginCommand() {
270625
- return new Base44Command("social-login").description("Enable or disable social login providers (google, microsoft, facebook, apple)").addArgument(new Argument2("<provider>", "social login provider").choices(VALID_PROVIDER_NAMES)).addArgument(new Argument2("<action>", "enable or disable the provider").choices([
270626
- "enable",
270627
- "disable"
270628
- ])).option("--client-id <id>", "custom OAuth client ID (Google only)").option("--client-secret <secret>", "custom OAuth client secret (Google only)").option("--client-secret-stdin", "read client secret from stdin (Google only)").option("--env-file <path>", "read client secret from a .env file (Google only)").action(socialLoginAction);
270629
- }
270630
-
270631
- // src/cli/commands/auth/sso.ts
270632
- import { dirname as dirname18, join as join25, resolve as resolve7 } from "node:path";
270633
- var SSOConfigFileSchema = object({
270634
- provider: _enum(Object.values(KNOWN_SSO_PROVIDERS)),
270635
- clientId: string2(),
270636
- clientSecret: string2(),
270637
- scope: string2().optional(),
270638
- discoveryUrl: string2().optional(),
270639
- tenantId: string2().optional(),
270640
- oktaDomain: string2().optional(),
270641
- authEndpoint: string2().optional(),
270642
- tokenEndpoint: string2().optional(),
270643
- userinfoEndpoint: string2().optional(),
270644
- jwksUri: string2().optional(),
270645
- ssoName: string2().optional()
270646
- });
270647
- async function loadSSOConfigFile(filePath) {
270648
- const resolved = resolve7(filePath);
270649
- const raw = await readJsonFile(resolved);
270650
- const result = SSOConfigFileSchema.safeParse(raw);
270651
- if (!result.success) {
270652
- throw new SchemaValidationError("Invalid SSO config file", result.error, filePath);
270653
- }
270654
- return result.data;
270655
- }
270656
- function mergeFileWithFlags(fileConfig, options) {
270657
- return {
270658
- provider: options.provider ?? fileConfig.provider,
270659
- clientId: options.clientId ?? fileConfig.clientId,
270660
- clientSecret: options.clientSecret ?? fileConfig.clientSecret,
270661
- clientSecretStdin: options.clientSecretStdin,
270662
- envFile: options.envFile,
270663
- scope: options.scope ?? fileConfig.scope,
270664
- discoveryUrl: options.discoveryUrl ?? fileConfig.discoveryUrl,
270665
- tenantId: options.tenantId ?? fileConfig.tenantId,
270666
- oktaDomain: options.oktaDomain ?? fileConfig.oktaDomain,
270667
- authEndpoint: options.authEndpoint ?? fileConfig.authEndpoint,
270668
- tokenEndpoint: options.tokenEndpoint ?? fileConfig.tokenEndpoint,
270669
- userinfoEndpoint: options.userinfoEndpoint ?? fileConfig.userinfoEndpoint,
270670
- jwksUri: options.jwksUri ?? fileConfig.jwksUri,
270671
- ssoName: options.ssoName ?? fileConfig.ssoName
270672
- };
270673
- }
270674
- var providerNames = Object.keys(KNOWN_SSO_PROVIDERS);
270675
- var SECRET_KEY_TO_FLAG = {
270676
- ["sso_name" /* Name */]: "--sso-name",
270677
- ["sso_client_id" /* ClientId */]: "--client-id",
270678
- ["sso_client_secret" /* ClientSecret */]: "--client-secret",
270679
- ["sso_scope" /* Scope */]: "--scope",
270680
- ["sso_discovery_url" /* DiscoveryUrl */]: "--discovery-url",
270681
- ["sso_tenant_id" /* TenantId */]: "--tenant-id",
270682
- ["sso_auth_endpoint" /* AuthEndpoint */]: "--auth-endpoint",
270683
- ["sso_token_endpoint" /* TokenEndpoint */]: "--token-endpoint",
270684
- ["sso_userinfo_endpoint" /* UserinfoEndpoint */]: "--userinfo-endpoint",
270685
- ["sso_okta_domain" /* OktaDomain */]: "--okta-domain",
270686
- ["sso_jwks_uri" /* JwksUri */]: "--jwks-uri"
270687
- };
270688
- function secretKeyToFlag(key) {
270689
- return SECRET_KEY_TO_FLAG[key];
270690
- }
270691
- function exampleCommand(provider) {
270692
- let cmd = `base44 auth sso enable --provider ${provider} --client-id <id> --client-secret <secret>`;
270693
- if (provider === KNOWN_SSO_PROVIDERS.microsoft)
270694
- cmd += " --tenant-id <id>";
270695
- if (provider === KNOWN_SSO_PROVIDERS.okta)
270696
- cmd += " --okta-domain <domain>";
270697
- if (provider === KNOWN_SSO_PROVIDERS.custom)
270698
- cmd += " --sso-name <name> --auth-endpoint <url> --token-endpoint <url> --userinfo-endpoint <url> --jwks-uri <url>";
270699
- return cmd;
270700
- }
270701
- function validateProvider(provider) {
270702
- if (!provider) {
270703
- throw new InvalidInputError("Missing --provider.", {
270704
- hints: [
270705
- {
270706
- message: `Valid providers: ${providerNames.join(", ")}`,
270707
- command: "base44 auth sso enable --provider <provider> --client-id <id> --client-secret <secret>"
270708
- }
270709
- ]
270710
- });
270711
- }
270712
- return provider;
270713
- }
270714
- async function ssoEnableAction({ isNonInteractive, runTask }, options) {
270715
- if (options.file && options.envFile) {
270716
- throw new InvalidInputError("--file and --env-file cannot be used together. Provide the client secret either inside --file or via --env-file.");
270717
- }
270718
- let merged = options;
270719
- if (options.file) {
270720
- const fileConfig = await loadSSOConfigFile(options.file);
270721
- merged = mergeFileWithFlags(fileConfig, options);
270722
- }
270723
- const provider = validateProvider(merged.provider);
270724
- if (!merged.clientId) {
270725
- throw new InvalidInputError("Missing --client-id.", {
270726
- hints: [
270727
- {
270728
- message: `Example: base44 auth sso enable --provider ${provider} --client-id <id> --client-secret <secret>`,
270729
- command: `base44 auth sso enable --provider ${provider} --client-id <id> --client-secret <secret>`
270730
- }
270731
- ]
270732
- });
270733
- }
270734
- let clientSecret;
270735
- if (merged.envFile && !merged.clientSecret) {
270736
- const secrets = await parseEnvFile(resolve7(merged.envFile));
270737
- const value = secrets.sso_client_secret;
270738
- if (!value) {
270739
- throw new InvalidInputError(`Key "sso_client_secret" not found in ${merged.envFile}.`);
270740
- }
270741
- clientSecret = value;
270742
- } else {
270743
- clientSecret = await resolveSecret({
270744
- flagValue: merged.clientSecret,
270745
- fromStdin: merged.clientSecretStdin,
270746
- envVar: "sso_client_secret",
270747
- promptMessage: "Enter SSO client secret",
270748
- isNonInteractive,
270749
- name: "client secret",
270750
- hints: [
270751
- {
270752
- message: `Provide via flag: base44 auth sso enable --provider ${provider} --client-id <id> --client-secret <secret>`,
270753
- command: `base44 auth sso enable --provider ${provider} --client-id <id> --client-secret <secret>`
270754
- },
270755
- {
270756
- message: `Provide via stdin: echo <secret> | base44 auth sso enable --provider ${provider} --client-id <id> --client-secret-stdin`
270757
- },
270758
- {
270759
- message: `Provide via env: sso_client_secret=<secret> base44 auth sso enable --provider ${provider} --client-id <id>`
270760
- }
270761
- ]
270762
- });
270763
- }
270764
- const secretOptions = {
270765
- clientId: merged.clientId,
270766
- clientSecret,
270767
- scope: merged.scope,
270768
- discoveryUrl: merged.discoveryUrl,
270769
- tenantId: merged.tenantId,
270770
- oktaDomain: merged.oktaDomain,
270771
- authEndpoint: merged.authEndpoint,
270772
- tokenEndpoint: merged.tokenEndpoint,
270773
- userinfoEndpoint: merged.userinfoEndpoint,
270774
- jwksUri: merged.jwksUri,
270775
- ssoName: merged.ssoName
270776
- };
270777
- let secrets;
270778
- try {
270779
- secrets = buildSSOSecrets(provider, secretOptions);
270780
- } catch (error) {
270781
- if (error instanceof MissingSSOFieldsError) {
270782
- const flagNames = error.missingKeys.map(secretKeyToFlag);
270783
- throw new InvalidInputError(`Missing required fields for ${error.provider}: ${flagNames.join(", ")}`, {
270784
- hints: [
270785
- {
270786
- message: `Example: ${exampleCommand(provider)}`,
270787
- command: exampleCommand(provider)
270788
- }
270789
- ]
270790
- });
270791
- }
270792
- throw error;
270793
- }
270794
- const { project } = await readProjectConfig();
270795
- const configDir = dirname18(project.configPath);
270796
- const authDir = join25(configDir, project.authDir);
270797
- await runTask("Updating local auth config", async () => updateSSOConfig(authDir, provider, true));
270798
- await runTask("Saving SSO credentials", async () => pushSSOSecrets(secrets));
270799
- return {
270800
- outroMessage: `SSO configured with ${provider} in local config. Run \`base44 auth push\` or \`base44 deploy\` to apply.`
270801
- };
270802
- }
270803
- function hasEnableOnlyOptions(options) {
270804
- return Boolean(options.provider || options.clientId || options.clientSecret || options.clientSecretStdin || options.envFile || options.file || options.scope || options.discoveryUrl || options.tenantId || options.oktaDomain || options.authEndpoint || options.tokenEndpoint || options.userinfoEndpoint || options.jwksUri || options.ssoName);
270805
- }
270806
- async function ssoDisableAction({ log, runTask }, options) {
270807
- if (hasEnableOnlyOptions(options)) {
270808
- throw new InvalidInputError("Configuration options cannot be used with disable. To disable SSO: base44 auth sso disable");
270809
- }
270810
- const { project } = await readProjectConfig();
270811
- const configDir = dirname18(project.configPath);
270812
- const authDir = join25(configDir, project.authDir);
270813
- const updated = await runTask("Updating local auth config", async () => updateSSOConfig(authDir, null, false));
270814
- await runTask("Removing SSO credentials", async () => deleteSSOSecrets());
270815
- if (!hasAnyLoginMethod(updated)) {
270816
- log.warn("Disabling SSO will leave no login methods enabled. Users will be locked out.");
270817
- }
270818
- return {
270819
- outroMessage: "SSO disabled in local config and credentials removed. Run `base44 auth push` or `base44 deploy` to apply."
270820
- };
270821
- }
270822
- async function ssoAction(context, action, options) {
270823
- if (action === "disable") {
270824
- return ssoDisableAction(context, options);
270825
- }
270826
- return ssoEnableAction(context, options);
270827
- }
270828
- function getSSOCommand() {
270829
- return new Base44Command("sso").description("Configure SSO identity provider (google, microsoft, github, okta, custom). SSO and social login are mutually exclusive — enabling one disables the other in the local auth config.").addArgument(new Argument2("<action>", "enable or disable SSO").choices([
270830
- "enable",
270831
- "disable"
270832
- ])).addOption(new Option2("--provider <provider>", "SSO provider").choices(Object.values(KNOWN_SSO_PROVIDERS))).option("--client-id <id>", "OAuth client ID").option("--client-secret <secret>", "OAuth client secret").option("--client-secret-stdin", "Read client secret from stdin").option("--env-file <path>", "Read client secret from a .env file (key: sso_client_secret)").option("--file <path>", "JSON config file with all SSO settings").option("--scope <scope>", "OAuth scope (defaults per provider)").option("--discovery-url <url>", "OIDC discovery URL").option("--tenant-id <id>", "Microsoft tenant ID (required for microsoft)").option("--okta-domain <domain>", "Okta domain (required for okta)").option("--auth-endpoint <url>", "Authorization endpoint (required for custom)").option("--token-endpoint <url>", "Token endpoint (required for custom)").option("--userinfo-endpoint <url>", "Userinfo endpoint (required for custom)").option("--jwks-uri <url>", "JWKS URI (required for custom)").option("--sso-name <name>", "Provider display name (required for custom)").action(ssoAction);
270833
- }
270834
-
270835
- // src/cli/commands/auth/index.ts
270836
- function getAuthCommand() {
270837
- return new Command2("auth").description("Manage app authentication settings").addCommand(getPasswordLoginCommand()).addCommand(getSocialLoginCommand()).addCommand(getSSOCommand()).addCommand(getAuthPullCommand()).addCommand(getAuthPushCommand());
270838
- }
270839
-
270840
- // src/cli/commands/auth/login.ts
270841
- function getLoginCommand() {
270842
- return new Base44Command("login", {
270843
- requireAuth: false,
270844
- requireAppContext: false
270845
- }).description("Authenticate with Base44").action(login);
270846
- }
270847
-
270848
- // src/cli/commands/auth/logout.ts
270849
- async function logout(_ctx) {
270850
- await deleteAuth();
270851
- return { outroMessage: "Logged out successfully" };
270852
- }
270853
- function getLogoutCommand() {
270854
- return new Base44Command("logout", {
270855
- requireAuth: false,
270856
- requireAppContext: false
270857
- }).description("Logout from current device").action(logout);
270858
- }
270859
-
270860
- // src/cli/commands/auth/whoami.ts
270861
- async function whoami(_ctx) {
270862
- const workspaceApiKey = getWorkspaceApiKeyFromEnv();
270863
- if (workspaceApiKey && isWorkspaceApiKey(workspaceApiKey)) {
270864
- return {
270865
- outroMessage: `Using workspace API key: ${theme.styles.bold(workspaceApiKey.slice(0, 10))}`
270866
- };
270867
- }
270868
- const auth = await readAuth();
270869
- return { outroMessage: `Logged in as: ${theme.styles.bold(auth.email)}` };
270870
- }
270871
- function getWhoamiCommand() {
270872
- return new Base44Command("whoami", { requireAppContext: false }).description("Display current authenticated user").action(whoami);
270873
- }
270874
-
270875
- // src/cli/commands/branches/index.ts
270876
- async function listBranchesAction({
270877
- log,
270878
- runTask,
270879
- jsonMode
270880
- }) {
270881
- const remote = await runTask("Fetching branches", () => listBranches());
270882
- const branches = [
270883
- { name: "main", status: "active" },
270884
- ...remote.map((branch) => ({
270885
- name: branch.branch_name,
270886
- status: branch.status
270887
- }))
270888
- ];
270889
- if (jsonMode)
270890
- return { stdout: `${JSON.stringify({ branches })}
270891
- ` };
270892
- for (const branch of branches)
270893
- log.message(`${branch.name} (${branch.status})`);
270894
- return { outroMessage: `${branches.length} branches` };
270895
- }
270896
- function getBranchesCommand() {
270897
- return new Command2("branches").description("Discover an app's branches").addCommand(new Base44Command("list").description("List main and active branch names for use with --branch").action(listBranchesAction));
271150
+ // src/cli/commands/builder/index.ts
271151
+ function getBuilderCommand() {
271152
+ return new Command2("builder").description("Build an app with the Base44 builder agent, non-interactively: create it, send turns, read status, stop, pick the model").addCommand(getNewCommand()).addCommand(getSendCommand()).addCommand(getStatusCommand()).addCommand(getStopCommand()).addCommand(getModelCommand());
270898
271153
  }
270899
271154
 
270900
271155
  // ../../node_modules/ink/build/render.js
@@ -275860,6 +276115,119 @@ var build_default = TextInput;
275860
276115
  // src/cli/commands/code/session.tsx
275861
276116
  var import_react23 = __toESM(require_react(), 1);
275862
276117
 
276118
+ // src/cli/commands/code/logo.ts
276119
+ var ROWS = 6;
276120
+ var ASPECT = 0.44;
276121
+ var SAMPLES = 4;
276122
+ var FILL = 0.6;
276123
+ var GAP_ROW = 3.5 + 0.5;
276124
+ var GAP_HEIGHT_ROWS = 0.8;
276125
+ var LOGO_COLS = Math.max(2, Math.floor(ROWS / ASPECT + 0.5));
276126
+ var OCTANT = {
276127
+ rx: 2,
276128
+ ry: 4,
276129
+ table: Array.from(" \uD83E\uDF82\uD833\uDD00▘\uD833\uDD01\uD833\uDD02\uD833\uDD03\uD833\uDD04▝\uD833\uDD05\uD833\uDD06\uD833\uDD07\uD833\uDD08▀\uD833\uDD09\uD833\uDD0A\uD833\uDD0B\uD833\uDD0C\uD833\uDD00\uD833\uDD0D\uD833\uDD0E\uD833\uDD0F\uD833\uDD10\uD833\uDD11\uD833\uDD12\uD833\uDD13\uD833\uDD14\uD833\uDD15\uD833\uDD16\uD833\uDD17\uD833\uDD18\uD833\uDD19\uD833\uDD1A\uD833\uDD1B\uD833\uDD1C\uD833\uDD1D\uD833\uDD1E\uD833\uDD1F\uD833\uDD03\uD833\uDD20\uD833\uDD21\uD833\uDD22\uD833\uDD23\uD833\uDD24\uD833\uDD25\uD833\uDD26\uD833\uDD27\uD833\uDD28\uD833\uDD29\uD833\uDD2A\uD833\uDD2B\uD833\uDD2C\uD833\uDD2D\uD833\uDD2E\uD833\uDD2F\uD833\uDD30\uD833\uDD31\uD833\uDD32\uD833\uDD33\uD833\uDD34\uD833\uDD35\uD83E\uDF85 \uD833\uDD36\uD833\uDD37\uD833\uDD38\uD833\uDD39\uD833\uDD3A\uD833\uDD3B\uD833\uDD3C\uD833\uDD3D\uD833\uDD3E\uD833\uDD3F\uD833\uDD40\uD833\uDD41\uD833\uDD42\uD833\uDD43\uD833\uDD44▖\uD833\uDD45\uD833\uDD46\uD833\uDD47\uD833\uDD48▌\uD833\uDD49\uD833\uDD4A\uD833\uDD4B\uD833\uDD4C▞\uD833\uDD4D\uD833\uDD4E\uD833\uDD4F\uD833\uDD50▛\uD833\uDD51\uD833\uDD52\uD833\uDD53\uD833\uDD54\uD833\uDD55\uD833\uDD56\uD833\uDD57\uD833\uDD58\uD833\uDD59\uD833\uDD5A\uD833\uDD5B\uD833\uDD5C\uD833\uDD5D\uD833\uDD5E\uD833\uDD5F\uD833\uDD60\uD833\uDD61\uD833\uDD62\uD833\uDD63\uD833\uDD64\uD833\uDD65\uD833\uDD66\uD833\uDD67\uD833\uDD68\uD833\uDD69\uD833\uDD6A\uD833\uDD6B\uD833\uDD6C\uD833\uDD6D\uD833\uDD6E\uD833\uDD6F\uD833\uDD70 \uD833\uDD71\uD833\uDD72\uD833\uDD73\uD833\uDD74\uD833\uDD75\uD833\uDD76\uD833\uDD77\uD833\uDD78\uD833\uDD79\uD833\uDD7A\uD833\uDD7B\uD833\uDD7C\uD833\uDD7D\uD833\uDD7E\uD833\uDD7F\uD833\uDD80\uD833\uDD81\uD833\uDD82\uD833\uDD83\uD833\uDD84\uD833\uDD85\uD833\uDD86\uD833\uDD87\uD833\uDD88\uD833\uDD89\uD833\uDD8A\uD833\uDD8B\uD833\uDD8C\uD833\uDD8D\uD833\uDD8E\uD833\uDD8F▗\uD833\uDD90\uD833\uDD91\uD833\uDD92\uD833\uDD93▚\uD833\uDD94\uD833\uDD95\uD833\uDD96\uD833\uDD97▐\uD833\uDD98\uD833\uDD99\uD833\uDD9A\uD833\uDD9B▜\uD833\uDD9C\uD833\uDD9D\uD833\uDD9E\uD833\uDD9F\uD833\uDDA0\uD833\uDDA1\uD833\uDDA2\uD833\uDDA3\uD833\uDDA4\uD833\uDDA5\uD833\uDDA6\uD833\uDDA7\uD833\uDDA8\uD833\uDDA9\uD833\uDDAA\uD833\uDDAB▂\uD833\uDDAC\uD833\uDDAD\uD833\uDDAE\uD833\uDDAF\uD833\uDDB0\uD833\uDDB1\uD833\uDDB2\uD833\uDDB3\uD833\uDDB4\uD833\uDDB5\uD833\uDDB6\uD833\uDDB7\uD833\uDDB8\uD833\uDDB9\uD833\uDDBA\uD833\uDDBB\uD833\uDDBC\uD833\uDDBD\uD833\uDDBE\uD833\uDDBF\uD833\uDDC0\uD833\uDDC1\uD833\uDDC2\uD833\uDDC3\uD833\uDDC4\uD833\uDDC5\uD833\uDDC6\uD833\uDDC7\uD833\uDDC8\uD833\uDDC9\uD833\uDDCA\uD833\uDDCB\uD833\uDDCC\uD833\uDDCD\uD833\uDDCE\uD833\uDDCF\uD833\uDDD0\uD833\uDDD1\uD833\uDDD2\uD833\uDDD3\uD833\uDDD4\uD833\uDDD5\uD833\uDDD6\uD833\uDDD7\uD833\uDDD8\uD833\uDDD9\uD833\uDDDA▄\uD833\uDDDB\uD833\uDDDC\uD833\uDDDD\uD833\uDDDE▙\uD833\uDDDF\uD833\uDDE0\uD833\uDDE1\uD833\uDDE2▟\uD833\uDDE3▆\uD833\uDDE4\uD833\uDDE5█")
276130
+ };
276131
+ var QUAD = {
276132
+ rx: 2,
276133
+ ry: 2,
276134
+ table: Array.from(" ▘▝▀▖▌▞▛▗▚▐▜▄▙▟█")
276135
+ };
276136
+ function detectTier(env = process.env) {
276137
+ const term = env.TERM ?? "";
276138
+ const prog = env.TERM_PROGRAM ?? "";
276139
+ if (prog === "ghostty" || term.includes("ghostty"))
276140
+ return "octant";
276141
+ if (env.KITTY_WINDOW_ID || term.includes("kitty"))
276142
+ return "octant";
276143
+ if (env.WEZTERM_PANE || env.WEZTERM_EXECUTABLE)
276144
+ return "octant";
276145
+ if (term.startsWith("foot") || prog === "contour")
276146
+ return "octant";
276147
+ return "quad";
276148
+ }
276149
+ function coverageGrid(rows, cols, aspect, pixelH) {
276150
+ const worldH = rows * pixelH;
276151
+ const worldW = cols * aspect;
276152
+ const radius = Math.min(worldH, worldW) / 2;
276153
+ const cx = worldW / 2;
276154
+ const cy = worldH / 2;
276155
+ const step = 1 / SAMPLES;
276156
+ const grid = [];
276157
+ for (let py = 0;py < rows; py++) {
276158
+ const y0 = py * pixelH;
276159
+ const y1 = y0 + pixelH;
276160
+ const dyLo = y0 <= cy && cy <= y1 ? 0 : Math.min(Math.abs(y0 - cy), Math.abs(y1 - cy));
276161
+ const dyHi = Math.max(Math.abs(y0 - cy), Math.abs(y1 - cy));
276162
+ const line = [];
276163
+ for (let px = 0;px < cols; px++) {
276164
+ const x0 = px * aspect;
276165
+ const x1 = x0 + aspect;
276166
+ const dxLo = x0 <= cx && cx <= x1 ? 0 : Math.min(Math.abs(x0 - cx), Math.abs(x1 - cx));
276167
+ const dxHi = Math.max(Math.abs(x0 - cx), Math.abs(x1 - cx));
276168
+ if (Math.hypot(dxLo, dyLo) >= radius) {
276169
+ line.push(0);
276170
+ continue;
276171
+ }
276172
+ if (Math.hypot(dxHi, dyHi) <= radius) {
276173
+ line.push(1);
276174
+ continue;
276175
+ }
276176
+ let hits = 0;
276177
+ for (let j = 0;j < SAMPLES; j++) {
276178
+ const dy = y0 + (j + 0.5) * step * pixelH - cy;
276179
+ for (let i = 0;i < SAMPLES; i++) {
276180
+ const dx = x0 + (i + 0.5) * step * aspect - cx;
276181
+ if (Math.hypot(dx, dy) <= radius)
276182
+ hits++;
276183
+ }
276184
+ }
276185
+ line.push(hits / (SAMPLES * SAMPLES));
276186
+ }
276187
+ grid.push(line);
276188
+ }
276189
+ return grid;
276190
+ }
276191
+ function carveGap(grid, ry) {
276192
+ const n = grid.length;
276193
+ const thick = Math.max(1, Math.round(GAP_HEIGHT_ROWS * ry));
276194
+ const centre = GAP_ROW >= 0 ? GAP_ROW * ry : n + GAP_ROW * ry;
276195
+ const start = Math.max(0, Math.min(n - thick, centre - thick / 2));
276196
+ for (let i = Math.trunc(start);i < Math.trunc(start + thick); i++) {
276197
+ grid[i].fill(0);
276198
+ }
276199
+ }
276200
+ function logoRows(color, tier = detectTier()) {
276201
+ const { rx, ry, table } = tier === "octant" ? OCTANT : QUAD;
276202
+ const grid = coverageGrid(ROWS * ry, LOGO_COLS * rx, ASPECT / rx, 1 / ry);
276203
+ carveGap(grid, ry);
276204
+ const fg = color ? source_default.hex(color) : (s) => s;
276205
+ const bg = color ? source_default.bgHex(color) : (s) => s;
276206
+ const full = (1 << rx * ry) - 1;
276207
+ const out = [];
276208
+ for (let r = 0;r < ROWS; r++) {
276209
+ let row = "";
276210
+ for (let c = 0;c < LOGO_COLS; c++) {
276211
+ let mask = 0;
276212
+ for (let sr = 0;sr < ry; sr++) {
276213
+ for (let sc = 0;sc < rx; sc++) {
276214
+ if (grid[r * ry + sr][c * rx + sc] >= FILL)
276215
+ mask |= 1 << sr * rx + sc;
276216
+ }
276217
+ }
276218
+ const glyph = table[mask];
276219
+ if (glyph === " ")
276220
+ row += " ";
276221
+ else if (mask === full && color)
276222
+ row += bg(" ");
276223
+ else
276224
+ row += fg(glyph);
276225
+ }
276226
+ out.push(row);
276227
+ }
276228
+ return out;
276229
+ }
276230
+
275863
276231
  // src/cli/commands/code/paste.ts
275864
276232
  import { PassThrough as PassThrough3 } from "node:stream";
275865
276233
  var START = "\x1B[200~";
@@ -275995,22 +276363,27 @@ function createSessionEngine(options) {
275995
276363
  if (!prime) {
275996
276364
  for (const event of events) {
275997
276365
  if (event.kind === "tool_start") {
276366
+ const startedAt = Date.now();
275998
276367
  running.set(event.id, {
275999
276368
  alias: toolAlias(event.name),
276000
276369
  label: event.label,
276001
276370
  summary: event.summary,
276002
- startedAt: Date.now()
276371
+ startedAt
276003
276372
  });
276373
+ options.onLine({ running: event, startedAt });
276004
276374
  continue;
276005
276375
  }
276006
- let elapsedMs;
276007
276376
  if (event.kind === "tool_end") {
276008
276377
  const started = running.get(event.id)?.startedAt;
276009
- if (started != null)
276010
- elapsedMs = Date.now() - started;
276011
276378
  running.delete(event.id);
276379
+ lastEventAt = Date.now();
276380
+ options.onLine({
276381
+ event,
276382
+ elapsedMs: started != null ? Date.now() - started : undefined
276383
+ });
276384
+ continue;
276012
276385
  }
276013
- const line = eventLine(event, elapsedMs);
276386
+ const line = eventLine(event);
276014
276387
  if (line != null) {
276015
276388
  lastEventAt = Date.now();
276016
276389
  options.onLine(line);
@@ -276131,6 +276504,17 @@ function exitAltScreen() {
276131
276504
  altScreenActive = false;
276132
276505
  process.stdout.write("\x1B[?1007l\x1B[?1049l");
276133
276506
  }
276507
+ function withEntry(items, entry) {
276508
+ if (typeof entry !== "string" && "event" in entry) {
276509
+ const i = items.findIndex((e) => typeof e !== "string" && ("running" in e) && e.running.id === entry.event.id);
276510
+ if (i >= 0) {
276511
+ const next = [...items];
276512
+ next[i] = entry;
276513
+ return next;
276514
+ }
276515
+ }
276516
+ return [...items, entry];
276517
+ }
276134
276518
  function statusText(status, musingSeed) {
276135
276519
  const frame = FRAMES2[Math.floor(Date.now() / 120) % FRAMES2.length];
276136
276520
  switch (status.phase) {
@@ -276138,13 +276522,6 @@ function statusText(status, musingSeed) {
276138
276522
  return source_default.dim(`${frame} ${status.awaitingLabel} (${formatDuration2(Date.now() - status.awaitingSince)})`);
276139
276523
  case "running": {
276140
276524
  const turnFor = formatDuration2(Date.now() - (status.turnStartedAt ?? Date.now()));
276141
- const tool = status.runningTool;
276142
- if (tool) {
276143
- const toolFor = Math.round((Date.now() - tool.startedAt) / 1000);
276144
- const others = tool.others > 0 ? ` (+${tool.others})` : "";
276145
- const what = tool.label || `${tool.alias}${tool.summary ? ` ${tool.summary}` : ""}`;
276146
- return source_default.dim(`${frame} ${what}${others} · ${toolFor}s (turn ${turnFor})`);
276147
- }
276148
276525
  const quiet = status.quietForMs > 60000 ? " · a long private step — details render in the editor" : "";
276149
276526
  return `${source_default.magenta("✻")} ${source_default.dim(`${idleMusing(musingSeed)} (${turnFor})${quiet}`)}`;
276150
276527
  }
@@ -276160,6 +276537,7 @@ function statusText(status, musingSeed) {
276160
276537
  function SessionView({ engine, footer, subscribe }) {
276161
276538
  const { exit } = use_app_default();
276162
276539
  const [items, setItems] = import_react23.useState([]);
276540
+ const [verbose, setVerbose] = import_react23.useState(false);
276163
276541
  const [input, setInput] = import_react23.useState("");
276164
276542
  const [scroll, setScroll] = import_react23.useState(0);
276165
276543
  const [, tick] = import_react23.useReducer((x) => x + 1, 0);
@@ -276168,10 +276546,8 @@ function SessionView({ engine, footer, subscribe }) {
276168
276546
  const [pickerIndex, setPickerIndex] = import_react23.useState(null);
276169
276547
  const maxScrollRef = import_react23.useRef(0);
276170
276548
  const meIdRef = import_react23.useRef(null);
276171
- const emit = (line) => setItems((h) => [...h, `${line}
276172
- `]);
276173
- import_react23.useEffect(() => subscribe((line) => setItems((h) => [...h, `${line}
276174
- `])), [subscribe]);
276549
+ const emit = (entry) => setItems((h) => withEntry(h, entry));
276550
+ import_react23.useEffect(() => subscribe((entry) => setItems((h) => withEntry(h, entry))), [subscribe]);
276175
276551
  import_react23.useEffect(() => {
276176
276552
  const timer = setInterval(tick, 120);
276177
276553
  return () => clearInterval(timer);
@@ -276239,6 +276615,10 @@ function SessionView({ engine, footer, subscribe }) {
276239
276615
  exit();
276240
276616
  return;
276241
276617
  }
276618
+ if (key.ctrl && char === "o") {
276619
+ setVerbose((v) => !v);
276620
+ return;
276621
+ }
276242
276622
  if (key.upArrow) {
276243
276623
  setScroll((s) => Math.min(s + 3, maxScrollRef.current));
276244
276624
  return;
@@ -276264,14 +276644,15 @@ function SessionView({ engine, footer, subscribe }) {
276264
276644
  });
276265
276645
  const columns = process.stdout.columns || 80;
276266
276646
  const rows = process.stdout.rows || 24;
276267
- const width = Math.min(columns, 100);
276647
+ const width = columns;
276268
276648
  const innerWidth = Math.max(10, width - 4);
276269
276649
  const inputRows = Math.max(1, Math.ceil((input.length + 3) / innerWidth));
276270
276650
  const pickerOpen = pickerIndex !== null;
276271
276651
  const inputBlockHeight = pickerOpen ? MODELS.length + 3 : inputRows + 2;
276272
276652
  const widgetHeight = inputBlockHeight + 3 + (footer.length ? 1 : 0);
276273
276653
  const viewHeight = Math.max(3, rows - widgetHeight - 1);
276274
- const lines = items.flatMap((item) => hardWrapAnsi(item, columns));
276654
+ const lines = items.flatMap((item) => hardWrapAnsi(`${renderEntry(item, { verbose, foldHint: "Ctrl+O to expand" })}
276655
+ `, columns));
276275
276656
  const maxScroll = Math.max(0, lines.length - viewHeight);
276276
276657
  maxScrollRef.current = maxScroll;
276277
276658
  const clamped = Math.min(scroll, maxScroll);
@@ -276354,48 +276735,16 @@ function SessionView({ engine, footer, subscribe }) {
276354
276735
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
276355
276736
  dimColor: true,
276356
276737
  wrap: "truncate-end",
276357
- children: pickerOpen ? " ↑↓ to move · Enter to select · Esc to cancel" : engine.turnRunning() ? " Esc to stop · type to queue · scroll to read · Ctrl+C to exit" : " Enter to send · /model to switch model · Esc for live · Ctrl+C to exit"
276738
+ children: pickerOpen ? " ↑↓ to move · Enter to select · Esc to cancel" : engine.turnRunning() ? " Esc to stop · type to queue · scroll to read · Ctrl+O to expand · Ctrl+C to exit" : " Enter to send · /model to switch model · Esc for live · Ctrl+O to expand · Ctrl+C to exit"
276358
276739
  }, undefined, false, undefined, this)
276359
276740
  ]
276360
276741
  }, undefined, true, undefined, this);
276361
276742
  }
276362
- var LOGO_ROWS = 6;
276363
- var LOGO_GAP_SUBROW = 8;
276364
- var QUAD = " ▘▝▀▖▌▞▛▗▚▐▜▄▙▟█";
276365
- function buildLogoRows() {
276366
- const sy = 2 * LOGO_ROWS;
276367
- const sx = 2 * sy;
276368
- const cx = (sx - 1) / 2;
276369
- const cy = (sy - 1) / 2;
276370
- const rad = sy / 2 - 0.5;
276371
- const on = (px, py) => {
276372
- if (py === LOGO_GAP_SUBROW)
276373
- return false;
276374
- const dx = (px - cx) * 0.5;
276375
- const dy = py - cy;
276376
- return dx * dx + dy * dy <= rad * rad;
276377
- };
276378
- const rows = [];
276379
- for (let ty = 0;ty < sy; ty += 2) {
276380
- let row = "";
276381
- for (let tx = 0;tx < sx; tx += 2) {
276382
- const bits = (on(tx, ty) ? 1 : 0) | (on(tx + 1, ty) ? 2 : 0) | (on(tx, ty + 1) ? 4 : 0) | (on(tx + 1, ty + 1) ? 8 : 0);
276383
- row += QUAD[bits];
276384
- }
276385
- rows.push(row.trim());
276386
- }
276387
- return rows.map((row) => row.trim());
276388
- }
276389
276743
  function renderHeader(who, mode) {
276390
276744
  const orange = source_default.hex(BRAND_ORANGE);
276391
276745
  const cwd = process.cwd().replace(process.env.HOME ?? "", "~");
276392
- const logo = buildLogoRows();
276393
- const logoW = Math.max(...logo.map((r) => r.length));
276394
- const center = (s) => {
276395
- const total = Math.max(0, logoW - s.length);
276396
- const left = Math.floor(total / 2);
276397
- return " ".repeat(left) + s + " ".repeat(total - left);
276398
- };
276746
+ const logo = logoRows(BRAND_ORANGE);
276747
+ const logoW = LOGO_COLS;
276399
276748
  const text = [
276400
276749
  `${orange.bold("Base44 Code")} ${source_default.dim(`v${package_default.version}`)}`,
276401
276750
  source_default.bold(who ? `Welcome back, ${who}!` : "Welcome!"),
@@ -276407,7 +276756,7 @@ function renderHeader(who, mode) {
276407
276756
  const textTop = Math.max(0, Math.floor((logo.length - text.length) / 2));
276408
276757
  const out = [];
276409
276758
  for (let i = 0;i < height; i++) {
276410
- const left = i < logo.length ? orange(center(logo[i])) : " ".repeat(logoW);
276759
+ const left = i < logo.length ? logo[i] : " ".repeat(logoW);
276411
276760
  const right = text[i - textTop] ?? "";
276412
276761
  out.push(` ${left} ${right}`.trimEnd());
276413
276762
  }
@@ -276598,21 +276947,21 @@ async function runGenesisSession(options) {
276598
276947
 
276599
276948
  // src/cli/commands/code/index.ts
276600
276949
  var BRAND_ORANGE2 = "#E86B3C";
276601
- async function bootstrapApp(prompt, footer, emit, onCreated, importRepo) {
276950
+ async function bootstrapApp(prompt, footer, emit, onCreated, importRepo, path, wixLaunch) {
276602
276951
  let app;
276603
276952
  try {
276604
- app = await createAndLinkApp({ prompt, importRepo });
276953
+ app = await createAndLinkApp({ prompt, importRepo, path, wixLaunch });
276605
276954
  } catch (error) {
276606
276955
  for (const line of await githubReauthLines(error) ?? [])
276607
276956
  emit(line);
276608
276957
  throw error;
276609
276958
  }
276610
276959
  onCreated(app);
276611
- footer.push(source_default.dim(`dir ./${app.dirName}`));
276960
+ footer.push(source_default.dim(`dir ${app.here ? "./" : `./${app.dirName}`}`));
276612
276961
  if (app.repoUrl)
276613
276962
  footer.push(terminalLink("repo", app.repoUrl));
276614
276963
  footer.push(terminalLink("editor", app.editorUrl));
276615
- emit(source_default.dim(`linked ./${app.dirName} (cd ${app.dirName} after the session)`));
276964
+ emit(source_default.dim(app.here ? "linked ./ (this directory)" : `linked ./${app.dirName} (cd ${app.dirName} after the session)`));
276616
276965
  const branchId = await resolveActiveBranchId().catch(() => {
276617
276966
  return;
276618
276967
  });
@@ -276636,46 +276985,58 @@ async function bootstrapApp(prompt, footer, emit, onCreated, importRepo) {
276636
276985
  }
276637
276986
  };
276638
276987
  }
276639
- async function codeAction({ log }, options) {
276988
+ async function codeAction({ log }, options, appId) {
276989
+ const orange = source_default.hex(BRAND_ORANGE2);
276990
+ const chip = (label) => source_default.dim(`${orange("●")} ${label}`);
276640
276991
  if (process.stdout.isTTY !== true) {
276641
276992
  throw new InvalidInputError("base44 code is an interactive session and needs a terminal.");
276642
276993
  }
276643
276994
  let linked = false;
276644
276995
  try {
276645
- await initAppContext();
276996
+ await initAppContext(appId ? { appId } : {});
276646
276997
  linked = true;
276647
276998
  } catch {}
276648
276999
  if (linked) {
276649
- if (options.import) {
276650
- throw new InvalidInputError("--import creates a new app; run it outside a linked project.");
277000
+ if (options.import || options.path || options.wixLaunch) {
277001
+ throw new InvalidInputError("--import, --path and --wix-launch create a new app; run them outside a linked project, without --app-id.");
276651
277002
  }
277003
+ const { id, projectRoot } = getAppContext();
277004
+ const state = await assertBuilderApp(id);
276652
277005
  const branchId = await resolveActiveBranchId().catch(() => {
276653
277006
  return;
276654
277007
  });
276655
277008
  await runInteractiveSession({
276656
277009
  branchId,
276657
- footer: [],
277010
+ footer: [chip(appTypeChip(state))],
276658
277011
  primeFirstPoll: true,
276659
277012
  idleHint: "what should the agent do next?"
276660
277013
  });
276661
- log.message(source_default.dim(`app dir ${getAppContext().projectRoot}`));
277014
+ if (projectRoot) {
277015
+ log.message(source_default.dim(`app dir ${projectRoot}`));
277016
+ return {
277017
+ outroMessage: "Session closed. Run `base44 code` here to resume."
277018
+ };
277019
+ }
276662
277020
  return {
276663
- outroMessage: "Session closed. Run `base44 code` here to resume."
277021
+ outroMessage: `Session closed. Resume with \`base44 code --app-id ${id}\`.`
276664
277022
  };
276665
277023
  }
277024
+ if (options.wixLaunch && options.import) {
277025
+ throw new InvalidInputError("--wix-launch and --import are exclusive.");
277026
+ }
277027
+ const wixLaunch = options.wixLaunch ? parseWixLaunchUrl(options.wixLaunch) : undefined;
276666
277028
  let created;
276667
- const orange = source_default.hex(BRAND_ORANGE2);
276668
277029
  const footer = [
276669
- source_default.dim(`${orange("●")} ${options.import ? "import" : "builder"}`)
277030
+ chip(options.import ? repoLabel(options.import) : wixLaunch ? "web app · wix" : "web app")
276670
277031
  ];
276671
277032
  await runGenesisSession({
276672
- idleHint: options.import ? "describe what to build over the repo" : "describe the app you want to build",
277033
+ idleHint: options.import ? "describe what to build over the repository" : "describe the app you want to build · have one already? base44 code --app-id <id>, or base44 link",
276673
277034
  creatingLabel: options.import ? "importing the repository" : "creating your app",
276674
- modeLabel: options.import ? `Import — ${options.import}` : "Builder — Base44 template + agent",
277035
+ modeLabel: options.import ? `Repository — ${repoLabel(options.import)}` : wixLaunch ? "Web app Wix launch (connector connected first)" : "Web app — Base44 template + builder agent",
276675
277036
  footer,
276676
277037
  createApp: (prompt, emit) => bootstrapApp(prompt, footer, emit, (app) => {
276677
277038
  created = app;
276678
- }, options.import)
277039
+ }, options.import, options.path, wixLaunch)
276679
277040
  });
276680
277041
  if (!created) {
276681
277042
  return { outroMessage: "Session closed. No app was created." };
@@ -276687,7 +277048,7 @@ async function codeAction({ log }, options) {
276687
277048
  }
276688
277049
  function getCodeCommand() {
276689
277050
  const command = new Base44Command("code", { requireAppContext: false });
276690
- command.description("Open Base44 Code: an interactive agent session in an empty directory your first prompt creates the app (add --import <repo> to build over an existing repository)").option("--import <repo>", "Build over an existing GitHub repository instead of the Base44 template").action(codeAction);
277051
+ command.description("Open Base44 Code, an interactive builder session. In a linked directory (or with --app-id <id>) it opens that app; anywhere else your first prompt creates one (--import <repo> to build over your own repository). Attach a directory to an existing app with base44 link.").option("--import <repo>", "Build over an existing GitHub repository instead of the Base44 template").option("--path <dir>", "Directory to link the new app to (default: the current directory when empty, else ./<name>)").option("--wix-launch <url>", "Create the new app through a Wix launch URL (your first prompt is what the agent runs)").action((ctx, options) => codeAction(ctx, options, command.optsWithGlobals().appId));
276691
277052
  return command;
276692
277053
  }
276693
277054
 
@@ -277485,10 +277846,10 @@ function getConnectorsListAvailableCommand() {
277485
277846
  }
277486
277847
 
277487
277848
  // src/cli/commands/connectors/pull.ts
277488
- import { dirname as dirname19, join as join27, resolve as resolve8 } from "node:path";
277849
+ import { dirname as dirname19, join as join27, resolve as resolve9 } from "node:path";
277489
277850
  async function resolveConnectorsDir(options) {
277490
277851
  if (!getAppContext().projectRoot) {
277491
- return resolve8(options.dir ?? "connectors");
277852
+ return resolve9(options.dir ?? "connectors");
277492
277853
  }
277493
277854
  const { project } = await readProjectConfig();
277494
277855
  return join27(dirname19(project.configPath), project.connectorsDir);
@@ -277532,10 +277893,10 @@ function getConnectorsPullCommand() {
277532
277893
  }
277533
277894
 
277534
277895
  // src/cli/commands/connectors/push.ts
277535
- import { resolve as resolve9 } from "node:path";
277896
+ import { resolve as resolve10 } from "node:path";
277536
277897
  async function readConnectorsToPush(options) {
277537
277898
  if (!getAppContext().projectRoot) {
277538
- return readAllConnectors(resolve9(options.dir ?? "connectors"));
277899
+ return readAllConnectors(resolve10(options.dir ?? "connectors"));
277539
277900
  }
277540
277901
  const { connectors } = await readProjectConfig();
277541
277902
  return connectors;
@@ -278004,7 +278365,7 @@ function getBuildCommand() {
278004
278365
  }
278005
278366
 
278006
278367
  // src/cli/commands/project/create.ts
278007
- import { basename as basename6, resolve as resolve10 } from "node:path";
278368
+ import { basename as basename7, resolve as resolve11 } from "node:path";
278008
278369
  var import_kebabCase2 = __toESM(require_kebabCase(), 1);
278009
278370
 
278010
278371
  // src/cli/commands/project/scaffold-shared.ts
@@ -278169,8 +278530,8 @@ async function createInteractive(options, ctx) {
278169
278530
  name: () => {
278170
278531
  return options.name ? Promise.resolve(options.name) : Ze({
278171
278532
  message: "What is the name of your project?",
278172
- placeholder: basename6(process.cwd()),
278173
- initialValue: basename6(process.cwd()),
278533
+ placeholder: basename7(process.cwd()),
278534
+ initialValue: basename7(process.cwd()),
278174
278535
  validate: (value) => {
278175
278536
  if (!value || value.trim().length === 0) {
278176
278537
  return "Every project deserves a name";
@@ -278200,7 +278561,7 @@ async function createInteractive(options, ctx) {
278200
278561
  }, ctx);
278201
278562
  }
278202
278563
  async function createNonInteractive(options, ctx) {
278203
- ctx.log.info(`Creating a new project at ${resolve10(options.path)}`);
278564
+ ctx.log.info(`Creating a new project at ${resolve11(options.path)}`);
278204
278565
  const template = await getTemplateById(options.template ?? DEFAULT_TEMPLATE_ID);
278205
278566
  return await executeCreate({
278206
278567
  template,
@@ -278224,7 +278585,7 @@ async function executeCreate({
278224
278585
  }, ctx) {
278225
278586
  const { log, runTask } = ctx;
278226
278587
  const name = rawName.trim();
278227
- const resolvedPath = resolve10(projectPath);
278588
+ const resolvedPath = resolve11(projectPath);
278228
278589
  const organizationId = await resolveWorkspaceId(ctx, flagWorkspaceId, isInteractive);
278229
278590
  const { projectId } = await runTask("Setting up your project...", async () => {
278230
278591
  return await createProjectFiles({
@@ -278865,7 +279226,7 @@ function getLogsCommand() {
278865
279226
  }
278866
279227
 
278867
279228
  // src/cli/commands/project/scaffold.ts
278868
- import { basename as basename7, resolve as resolve11 } from "node:path";
279229
+ import { basename as basename8, resolve as resolve12 } from "node:path";
278869
279230
  function resolveAppId(options) {
278870
279231
  const appId = options.appId;
278871
279232
  if (!appId) {
@@ -278881,8 +279242,8 @@ function resolveAppId(options) {
278881
279242
  async function scaffoldAction(ctx, name, options, command) {
278882
279243
  const { log, runTask } = ctx;
278883
279244
  const appId = resolveAppId(command.optsWithGlobals());
278884
- const resolvedPath = resolve11("./");
278885
- const projectName = (name ?? basename7(resolvedPath)).trim();
279245
+ const resolvedPath = resolve12("./");
279246
+ const projectName = (name ?? basename8(resolvedPath)).trim();
278886
279247
  const template = await getTemplateById("backend-only");
278887
279248
  log.info(`Scaffolding project at ${resolvedPath}`);
278888
279249
  const { projectId } = await runTask("Setting up your project...", async () => {
@@ -279199,6 +279560,21 @@ function getSandboxListDirectoryCommand() {
279199
279560
  return new Base44Command("ls", { supportsBranch: true }).description("List directory entries in an app's remote sandbox").argument("[path]", "Directory relative to the app root (default: app root)").option("--recursive", "List nested entries").option("--max-depth <n>", "Max depth when recursive (1-10, default 3)").option("--include-hidden", "Include dotfiles").action(listDirectoryAction);
279200
279561
  }
279201
279562
 
279563
+ // src/cli/commands/sandbox/preview.ts
279564
+ async function previewAction(ctx) {
279565
+ const url = await ctx.runTask("Resolving preview URL (boots the sandbox if needed)", () => getPreviewUrl());
279566
+ if (ctx.jsonMode)
279567
+ return { stdout: `${JSON.stringify({ preview_url: url })}
279568
+ ` };
279569
+ ctx.log.message(url);
279570
+ return { outroMessage: "Preview is live." };
279571
+ }
279572
+ function getSandboxPreviewCommand() {
279573
+ const command = new Base44Command("preview");
279574
+ command.description("Print the app's live preview URL").action(previewAction);
279575
+ return command;
279576
+ }
279577
+
279202
279578
  // src/cli/commands/sandbox/read-file.ts
279203
279579
  async function readFileAction({ runTask, branchId }, paths, options) {
279204
279580
  const { id: appId } = getAppContext();
@@ -279252,7 +279628,7 @@ Examples:
279252
279628
 
279253
279629
  // src/cli/commands/sandbox/index.ts
279254
279630
  function getSandboxCommand() {
279255
- return new Command2("sandbox").description("Develop an app remotely via its server-side sandbox").addCommand(getSandboxListDirectoryCommand()).addCommand(getSandboxReadFileCommand()).addCommand(getSandboxWriteFileCommand()).addCommand(getSandboxEditFileCommand()).addCommand(getSandboxGrepCommand()).addCommand(getSandboxRunCommandCommand()).addCommand(getSandboxCheckpointCommand());
279631
+ return new Command2("sandbox").description("Develop an app remotely via its server-side sandbox").addCommand(getSandboxListDirectoryCommand()).addCommand(getSandboxReadFileCommand()).addCommand(getSandboxWriteFileCommand()).addCommand(getSandboxEditFileCommand()).addCommand(getSandboxGrepCommand()).addCommand(getSandboxRunCommandCommand()).addCommand(getSandboxCheckpointCommand()).addCommand(getSandboxPreviewCommand());
279256
279632
  }
279257
279633
 
279258
279634
  // src/cli/commands/secrets/delete.ts
@@ -279298,7 +279674,7 @@ function getSecretsListCommand() {
279298
279674
  }
279299
279675
 
279300
279676
  // src/cli/commands/secrets/set.ts
279301
- import { resolve as resolve12 } from "node:path";
279677
+ import { resolve as resolve13 } from "node:path";
279302
279678
  function parseEntries(entries) {
279303
279679
  const secrets = {};
279304
279680
  for (const entry of entries) {
@@ -279329,7 +279705,7 @@ async function setSecretsAction({ log, runTask }, entries, options) {
279329
279705
  validateInput(entries, options);
279330
279706
  let secrets;
279331
279707
  if (options.envFile) {
279332
- secrets = await parseEnvFile(resolve12(options.envFile));
279708
+ secrets = await parseEnvFile(resolve13(options.envFile));
279333
279709
  if (Object.keys(secrets).length === 0) {
279334
279710
  throw new InvalidInputError("The env file contains no valid KEY=VALUE entries.");
279335
279711
  }
@@ -279358,7 +279734,7 @@ function getSecretsCommand() {
279358
279734
  }
279359
279735
 
279360
279736
  // src/cli/commands/site/deploy.ts
279361
- import { resolve as resolve13 } from "node:path";
279737
+ import { resolve as resolve14 } from "node:path";
279362
279738
  async function deployAction2(ctx, options) {
279363
279739
  const { isNonInteractive } = ctx;
279364
279740
  if (isNonInteractive && !options.yes) {
@@ -279436,7 +279812,7 @@ async function deployTarball({ runTask }, project) {
279436
279812
  }
279437
279813
  function siteOutputDir(project) {
279438
279814
  const outputDirectory = project.site?.outputDirectory;
279439
- return outputDirectory ? resolve13(project.root, outputDirectory) : null;
279815
+ return outputDirectory ? resolve14(project.root, outputDirectory) : null;
279440
279816
  }
279441
279817
  function getSiteDeployCommand() {
279442
279818
  const command = new Base44Command("deploy").description("Deploy built site files to Base44 hosting").option("-y, --yes", "Skip confirmation prompt").option("--build", "Build the site before deploying (skips the prompt)").option("--no-build", "Deploy without building (skips the prompt)");
@@ -281938,7 +282314,7 @@ function createCustomIntegrationRoutes(remoteProxy, logger) {
281938
282314
 
281939
282315
  // src/cli/dev/dev-server/watcher.ts
281940
282316
  import { EventEmitter as EventEmitter6 } from "node:events";
281941
- import { relative as relative9 } from "node:path";
282317
+ import { relative as relative10 } from "node:path";
281942
282318
 
281943
282319
  // ../../node_modules/chokidar/index.js
281944
282320
  import { EventEmitter as EventEmitter5 } from "node:events";
@@ -283623,7 +283999,7 @@ class WatchBase44 extends EventEmitter6 {
283623
283999
  ignoreInitial: true
283624
284000
  });
283625
284001
  watcher.on("all", import_debounce4.default(async (_event, path) => {
283626
- this.emit("change", name, relative9(targetPath, path));
284002
+ this.emit("change", name, relative10(targetPath, path));
283627
284003
  }, WATCH_DEBOUNCE_MS));
283628
284004
  watcher.on("error", (err) => {
283629
284005
  this.logger.error(`Watch handler failed for ${targetPath}`, err);
@@ -284055,7 +284431,7 @@ async function runScript(options) {
284055
284431
  }
284056
284432
  }
284057
284433
  // src/cli/commands/exec.ts
284058
- function readStdin2() {
284434
+ function readStdin3() {
284059
284435
  return new Promise((resolve, reject) => {
284060
284436
  let data = "";
284061
284437
  process.stdin.setEncoding("utf-8");
@@ -284099,7 +284475,7 @@ async function execAction({ app, isNonInteractive }, options) {
284099
284475
  if (!isNonInteractive) {
284100
284476
  throw noInputError;
284101
284477
  }
284102
- const code = await readStdin2();
284478
+ const code = await readStdin3();
284103
284479
  if (!code.trim()) {
284104
284480
  throw noInputError;
284105
284481
  }
@@ -284133,7 +284509,7 @@ Examples:
284133
284509
  }
284134
284510
 
284135
284511
  // src/cli/commands/project/eject.ts
284136
- import { resolve as resolve17 } from "node:path";
284512
+ import { resolve as resolve18 } from "node:path";
284137
284513
  var import_kebabCase3 = __toESM(require_kebabCase(), 1);
284138
284514
  async function eject(ctx, options, command) {
284139
284515
  const { log, runTask, isNonInteractive } = ctx;
@@ -284197,7 +284573,7 @@ async function eject(ctx, options, command) {
284197
284573
  Ne("Operation cancelled.");
284198
284574
  throw new CLIExitError(0);
284199
284575
  }
284200
- const resolvedPath = resolve17(selectedPath);
284576
+ const resolvedPath = resolve18(selectedPath);
284201
284577
  await runTask("Downloading your project's code...", async (updateMessage) => {
284202
284578
  await createProjectFilesForExistingProject({
284203
284579
  projectId,
@@ -284279,7 +284655,7 @@ function createProgram(context) {
284279
284655
  program.addCommand(getSecretsCommand());
284280
284656
  program.addCommand(getSandboxCommand());
284281
284657
  program.addCommand(getBranchesCommand());
284282
- program.addCommand(getAppCommand());
284658
+ program.addCommand(getBuilderCommand());
284283
284659
  program.addCommand(getCodeCommand());
284284
284660
  program.addCommand(getAuthCommand());
284285
284661
  program.addCommand(getSiteCommand());
@@ -288334,4 +288710,4 @@ export {
288334
288710
  runCLI
288335
288711
  };
288336
288712
 
288337
- //# debugId=72FC25F5FAAAB35A64756E2164756E21
288713
+ //# debugId=9A8433398282BCF064756E2164756E21