@base44-preview/cli 0.1.15-pr.630.196c93f → 0.1.15-pr.630.30a3a78
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 +1506 -1364
- package/dist/cli/index.js.map +21 -20
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -269450,1442 +269450,1482 @@ function getAgentsCommand() {
|
|
|
269450
269450
|
return new Command2("agents").description("Manage project agents").addCommand(getAgentsPushCommand()).addCommand(getAgentsPullCommand());
|
|
269451
269451
|
}
|
|
269452
269452
|
|
|
269453
|
-
// src/
|
|
269454
|
-
|
|
269455
|
-
|
|
269456
|
-
|
|
269457
|
-
{
|
|
269458
|
-
|
|
269459
|
-
|
|
269460
|
-
|
|
269461
|
-
|
|
269462
|
-
|
|
269463
|
-
|
|
269464
|
-
|
|
269465
|
-
|
|
269466
|
-
|
|
269467
|
-
|
|
269468
|
-
return byExact;
|
|
269469
|
-
const byContains = MODELS.filter((m) => fold(m.name).includes(key) || m.id && fold(m.id).includes(key));
|
|
269470
|
-
if (byContains.length === 1)
|
|
269471
|
-
return byContains[0];
|
|
269472
|
-
const names = MODELS.map((m) => m.name).join(", ");
|
|
269473
|
-
throw new InvalidInputError(byContains.length > 1 ? `"${input}" is ambiguous — matches ${byContains.map((m) => m.name).join(", ")}.` : `Unknown model "${input}". Choose one of: ${names}.`);
|
|
269474
|
-
}
|
|
269475
|
-
async function getMe() {
|
|
269476
|
-
try {
|
|
269477
|
-
return await base44Client.get("api/auth/me").json();
|
|
269478
|
-
} catch (error) {
|
|
269479
|
-
throw await ApiError.fromHttpError(error, "reading your account");
|
|
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.");
|
|
269480
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
|
+
};
|
|
269481
269473
|
}
|
|
269482
|
-
|
|
269483
|
-
|
|
269484
|
-
|
|
269485
|
-
|
|
269486
|
-
|
|
269487
|
-
|
|
269488
|
-
|
|
269489
|
-
|
|
269490
|
-
|
|
269491
|
-
|
|
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");
|
|
269492
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);
|
|
269493
269510
|
}
|
|
269494
|
-
var displayName = (id) => MODELS.find((m) => m.id === id)?.name ?? id ?? "default";
|
|
269495
269511
|
|
|
269496
|
-
// src/cli/commands/
|
|
269497
|
-
async function
|
|
269498
|
-
const
|
|
269499
|
-
|
|
269500
|
-
|
|
269501
|
-
if (jsonMode) {
|
|
269502
|
-
return {
|
|
269503
|
-
stdout: `${JSON.stringify({
|
|
269504
|
-
current,
|
|
269505
|
-
models: MODELS.map((m) => ({ name: m.name, id: m.id }))
|
|
269506
|
-
})}
|
|
269507
|
-
`
|
|
269508
|
-
};
|
|
269509
|
-
}
|
|
269510
|
-
for (const m of MODELS) {
|
|
269511
|
-
const active = (m.id ?? null) === current;
|
|
269512
|
-
const marker = active ? theme.styles.bold("●") : theme.styles.dim("○");
|
|
269513
|
-
const note = m.note ? theme.styles.dim(` (${m.note})`) : "";
|
|
269514
|
-
log.message(`${marker} ${active ? theme.styles.bold(m.name) : m.name}${note}`);
|
|
269515
|
-
}
|
|
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");
|
|
269516
269517
|
return {
|
|
269517
|
-
outroMessage:
|
|
269518
|
+
outroMessage: "No auth config to push. Run `base44 auth pull` to fetch the remote config first."
|
|
269518
269519
|
};
|
|
269519
269520
|
}
|
|
269520
|
-
|
|
269521
|
-
|
|
269522
|
-
return { outroMessage: `Already on ${theme.styles.bold(pick.name)}.` };
|
|
269521
|
+
if (!hasAnyLoginMethod(authConfig[0])) {
|
|
269522
|
+
log.warn("This config has no login methods enabled. Pushing it will lock out all users.");
|
|
269523
269523
|
}
|
|
269524
|
-
|
|
269525
|
-
|
|
269526
|
-
|
|
269527
|
-
|
|
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
|
+
});
|
|
269528
269541
|
return {
|
|
269529
|
-
outroMessage:
|
|
269542
|
+
outroMessage: "Auth config pushed to Base44"
|
|
269530
269543
|
};
|
|
269531
269544
|
}
|
|
269532
|
-
function
|
|
269533
|
-
|
|
269534
|
-
command.description("Pick the builder model for your turns (account-wide). No argument lists models and the current pick; `default` clears it").argument("[model]", 'Model name or id, e.g. "Opus 5" or default').action(modelAction);
|
|
269535
|
-
return command;
|
|
269545
|
+
function getAuthPushCommand() {
|
|
269546
|
+
return new Base44Command("push").description("Push local auth config to Base44").option("-y, --yes", "Skip confirmation prompt").action(pushAuthAction);
|
|
269536
269547
|
}
|
|
269537
269548
|
|
|
269538
|
-
// src/cli/commands/
|
|
269539
|
-
import {
|
|
269540
|
-
|
|
269541
|
-
|
|
269542
|
-
|
|
269543
|
-
|
|
269544
|
-
|
|
269545
|
-
read_repo_file: "read",
|
|
269546
|
-
write_repo_file: "write",
|
|
269547
|
-
edit_repo_file: "edit",
|
|
269548
|
-
set_secrets: "secrets",
|
|
269549
|
-
generate_development_secrets: "secrets",
|
|
269550
|
-
create_pull_request: "pr",
|
|
269551
|
-
merge_pull_request: "merge",
|
|
269552
|
-
list_pr_threads: "pr threads",
|
|
269553
|
-
reply_to_pr_thread: "pr reply",
|
|
269554
|
-
comment_on_pr: "pr comment",
|
|
269555
|
-
resolve_pr_thread: "pr resolve",
|
|
269556
|
-
reload_preview: "reload",
|
|
269557
|
-
preview_execute_code: "preview js",
|
|
269558
|
-
preview_screenshot: "screenshot",
|
|
269559
|
-
connect_github_account: "github"
|
|
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"
|
|
269560
269556
|
};
|
|
269561
|
-
var
|
|
269562
|
-
|
|
269563
|
-
|
|
269564
|
-
|
|
269565
|
-
|
|
269566
|
-
var BEL = String.fromCharCode(7);
|
|
269567
|
-
var OSC8_CLOSE = `${ESC_CHAR}]8;;${BEL}`;
|
|
269568
|
-
function terminalLink(label, url) {
|
|
269569
|
-
return `${ESC_CHAR}]8;;${url}${BEL}${source_default.dim.underline(label)}${OSC8_CLOSE}`;
|
|
269570
|
-
}
|
|
269571
|
-
function linkifyUrls(text) {
|
|
269572
|
-
let n = 0;
|
|
269573
|
-
return text.replace(/https?:\/\/[^\s]+/g, (raw) => {
|
|
269574
|
-
const trailing = raw.match(/[.,;:!?)\]}'"]+$/)?.[0] ?? "";
|
|
269575
|
-
const url = trailing ? raw.slice(0, -trailing.length) : raw;
|
|
269576
|
-
const id = `b44-${n++}`;
|
|
269577
|
-
return `${ESC_CHAR}]8;id=${id};${url}${BEL}${url}${OSC8_CLOSE}${trailing}`;
|
|
269578
|
-
});
|
|
269579
|
-
}
|
|
269580
|
-
function hardWrapAnsi(text, width) {
|
|
269581
|
-
const ESC = new RegExp(`^(?:${ESC_CHAR}\\[[0-9;]*m|${ESC_CHAR}\\]8;[^${BEL}]*${BEL})`);
|
|
269582
|
-
const RESET = `${ESC_CHAR}[0m`;
|
|
269583
|
-
const out = [];
|
|
269584
|
-
for (const logical of text.split(`
|
|
269585
|
-
`)) {
|
|
269586
|
-
let line = "";
|
|
269587
|
-
let visible = 0;
|
|
269588
|
-
let active = [];
|
|
269589
|
-
let link = "";
|
|
269590
|
-
let i = 0;
|
|
269591
|
-
while (i < logical.length) {
|
|
269592
|
-
const esc = ESC.exec(logical.slice(i));
|
|
269593
|
-
if (esc) {
|
|
269594
|
-
const seq = esc[0];
|
|
269595
|
-
line += seq;
|
|
269596
|
-
if (seq === RESET)
|
|
269597
|
-
active = [];
|
|
269598
|
-
else if (seq.endsWith("m"))
|
|
269599
|
-
active.push(seq);
|
|
269600
|
-
else if (seq === OSC8_CLOSE)
|
|
269601
|
-
link = "";
|
|
269602
|
-
else
|
|
269603
|
-
link = seq;
|
|
269604
|
-
i += seq.length;
|
|
269605
|
-
continue;
|
|
269606
|
-
}
|
|
269607
|
-
if (visible >= width) {
|
|
269608
|
-
out.push(`${line}${link ? OSC8_CLOSE : ""}${RESET}`);
|
|
269609
|
-
line = active.join("") + link;
|
|
269610
|
-
visible = 0;
|
|
269611
|
-
}
|
|
269612
|
-
line += logical[i];
|
|
269613
|
-
visible++;
|
|
269614
|
-
i++;
|
|
269615
|
-
}
|
|
269616
|
-
out.push(line);
|
|
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"
|
|
269617
269562
|
}
|
|
269618
|
-
|
|
269563
|
+
};
|
|
269564
|
+
function hasSecretOptions(options) {
|
|
269565
|
+
return Boolean(options.clientSecret || options.clientSecretStdin || options.envFile);
|
|
269619
269566
|
}
|
|
269620
|
-
function
|
|
269621
|
-
|
|
269622
|
-
if (seconds < 90)
|
|
269623
|
-
return `${seconds}s`;
|
|
269624
|
-
return `${Math.floor(seconds / 60)}m ${String(seconds % 60).padStart(2, "0")}s`;
|
|
269567
|
+
function hasCustomOAuthOptions(options) {
|
|
269568
|
+
return Boolean(options.clientId || hasSecretOptions(options));
|
|
269625
269569
|
}
|
|
269626
|
-
function
|
|
269627
|
-
|
|
269628
|
-
|
|
269629
|
-
|
|
269630
|
-
|
|
269631
|
-
|
|
269632
|
-
|
|
269633
|
-
|
|
269634
|
-
|
|
269635
|
-
|
|
269636
|
-
|
|
269637
|
-
|
|
269638
|
-
|
|
269639
|
-
|
|
269640
|
-
|
|
269641
|
-
|
|
269642
|
-
|
|
269643
|
-
|
|
269644
|
-
|
|
269645
|
-
|
|
269646
|
-
const
|
|
269647
|
-
|
|
269648
|
-
|
|
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}.`);
|
|
269649
269594
|
}
|
|
269650
|
-
|
|
269651
|
-
|
|
269652
|
-
|
|
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
|
+
});
|
|
269653
269617
|
}
|
|
269654
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 };
|
|
269655
269637
|
}
|
|
269656
|
-
|
|
269657
|
-
|
|
269658
|
-
|
|
269659
|
-
|
|
269660
|
-
"
|
|
269661
|
-
"Noodling",
|
|
269662
|
-
"Marinating",
|
|
269663
|
-
"Brewing",
|
|
269664
|
-
"Simmering",
|
|
269665
|
-
"Conjuring",
|
|
269666
|
-
"Tinkering",
|
|
269667
|
-
"Scheming",
|
|
269668
|
-
"Pondering",
|
|
269669
|
-
"Mulling",
|
|
269670
|
-
"Whirring",
|
|
269671
|
-
"Crunching",
|
|
269672
|
-
"Weaving",
|
|
269673
|
-
"Sketching",
|
|
269674
|
-
"Hatching",
|
|
269675
|
-
"Riffing",
|
|
269676
|
-
"Cooking",
|
|
269677
|
-
"Composting ideas",
|
|
269678
|
-
"Rummaging",
|
|
269679
|
-
"Vibing responsibly",
|
|
269680
|
-
"Untangling",
|
|
269681
|
-
"Squinting at the repo"
|
|
269682
|
-
];
|
|
269683
|
-
var MUSING_ROTATE_MS = 6000;
|
|
269684
|
-
function idleMusing(seed) {
|
|
269685
|
-
return `${MUSINGS[(seed + Math.floor(Date.now() / MUSING_ROTATE_MS)) % MUSINGS.length]}…`;
|
|
269686
|
-
}
|
|
269687
|
-
function createTurnStream(interactive, write = (text) => process.stdout.write(text), options = {}) {
|
|
269688
|
-
const running = new Map;
|
|
269689
|
-
const footer = options.footer ?? [];
|
|
269690
|
-
let frame = 0;
|
|
269691
|
-
let stopped = false;
|
|
269692
|
-
let drawnLines = 0;
|
|
269693
|
-
const musingSeed = Math.floor(Math.random() * MUSINGS.length);
|
|
269694
|
-
const statusLabel = () => {
|
|
269695
|
-
if (running.size === 0) {
|
|
269696
|
-
if (options.idleLabel)
|
|
269697
|
-
return `${options.idleLabel}…`;
|
|
269698
|
-
const index = (musingSeed + Math.floor(Date.now() / MUSING_ROTATE_MS)) % MUSINGS.length;
|
|
269699
|
-
return `${MUSINGS[index]}…`;
|
|
269700
|
-
}
|
|
269701
|
-
const newest = [...running.values()].at(-1);
|
|
269702
|
-
const elapsed = Math.round((Date.now() - newest.startedAt) / 1000);
|
|
269703
|
-
const others = running.size > 1 ? ` (+${running.size - 1} more)` : "";
|
|
269704
|
-
const what = newest.label || `${newest.alias}${newest.summary ? ` ${newest.summary}` : ""}`;
|
|
269705
|
-
return `${what}${others} · ${elapsed}s`;
|
|
269706
|
-
};
|
|
269707
|
-
const clearBlock = () => {
|
|
269708
|
-
if (!drawnLines)
|
|
269709
|
-
return;
|
|
269710
|
-
write("\r\x1B[2K");
|
|
269711
|
-
for (let i = 1;i < drawnLines; i++)
|
|
269712
|
-
write("\x1B[1A\r\x1B[2K");
|
|
269713
|
-
drawnLines = 0;
|
|
269714
|
-
};
|
|
269715
|
-
const drawBlock = () => {
|
|
269716
|
-
if (!interactive || stopped)
|
|
269717
|
-
return;
|
|
269718
|
-
const lines = [
|
|
269719
|
-
...footer.length ? ["", ...footer] : [],
|
|
269720
|
-
source_default.dim(`${FRAMES[frame]} ${statusLabel()}`)
|
|
269721
|
-
];
|
|
269722
|
-
write(lines.join(`
|
|
269723
|
-
`));
|
|
269724
|
-
drawnLines = lines.length;
|
|
269725
|
-
};
|
|
269726
|
-
const tick = () => {
|
|
269727
|
-
if (!interactive || stopped)
|
|
269728
|
-
return;
|
|
269729
|
-
frame = (frame + 1) % FRAMES.length;
|
|
269730
|
-
clearBlock();
|
|
269731
|
-
drawBlock();
|
|
269732
|
-
};
|
|
269733
|
-
const timer = interactive ? setInterval(tick, 120) : null;
|
|
269734
|
-
if (timer)
|
|
269735
|
-
timer.unref?.();
|
|
269736
|
-
return {
|
|
269737
|
-
onEvent(event) {
|
|
269738
|
-
if (event.kind === "tool_start") {
|
|
269739
|
-
running.set(event.id, {
|
|
269740
|
-
alias: toolAlias(event.name),
|
|
269741
|
-
label: event.label,
|
|
269742
|
-
summary: event.summary,
|
|
269743
|
-
startedAt: Date.now()
|
|
269744
|
-
});
|
|
269745
|
-
if (interactive) {
|
|
269746
|
-
clearBlock();
|
|
269747
|
-
drawBlock();
|
|
269748
|
-
}
|
|
269749
|
-
return;
|
|
269750
|
-
}
|
|
269751
|
-
let elapsedMs;
|
|
269752
|
-
if (event.kind === "tool_end") {
|
|
269753
|
-
const started = running.get(event.id)?.startedAt;
|
|
269754
|
-
if (started != null)
|
|
269755
|
-
elapsedMs = Date.now() - started;
|
|
269756
|
-
running.delete(event.id);
|
|
269757
|
-
}
|
|
269758
|
-
const line = eventLine(event, elapsedMs);
|
|
269759
|
-
if (line == null)
|
|
269760
|
-
return;
|
|
269761
|
-
if (interactive) {
|
|
269762
|
-
clearBlock();
|
|
269763
|
-
write(`${line}
|
|
269764
|
-
`);
|
|
269765
|
-
drawBlock();
|
|
269766
|
-
} else {
|
|
269767
|
-
write(`${line}
|
|
269768
|
-
`);
|
|
269769
|
-
}
|
|
269770
|
-
},
|
|
269771
|
-
stop() {
|
|
269772
|
-
if (interactive) {
|
|
269773
|
-
clearBlock();
|
|
269774
|
-
if (footer.length)
|
|
269775
|
-
write(`
|
|
269776
|
-
${footer.join(`
|
|
269777
|
-
`)}
|
|
269778
|
-
`);
|
|
269779
|
-
}
|
|
269780
|
-
stopped = true;
|
|
269781
|
-
if (timer)
|
|
269782
|
-
clearInterval(timer);
|
|
269783
|
-
}
|
|
269784
|
-
};
|
|
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);
|
|
269785
269643
|
}
|
|
269786
269644
|
|
|
269787
|
-
// src/
|
|
269788
|
-
|
|
269789
|
-
|
|
269790
|
-
|
|
269791
|
-
|
|
269792
|
-
|
|
269793
|
-
|
|
269794
|
-
|
|
269795
|
-
|
|
269796
|
-
|
|
269797
|
-
|
|
269798
|
-
|
|
269799
|
-
|
|
269800
|
-
|
|
269801
|
-
|
|
269802
|
-
status: object({
|
|
269803
|
-
state: string2().nullish(),
|
|
269804
|
-
message: string2().nullish(),
|
|
269805
|
-
error_source: string2().nullish()
|
|
269806
|
-
}).nullish(),
|
|
269807
|
-
conversation: object({
|
|
269808
|
-
messages: array(object({
|
|
269809
|
-
role: string2().nullish(),
|
|
269810
|
-
content: unknown().nullish()
|
|
269811
|
-
})).nullish()
|
|
269812
|
-
}).nullish()
|
|
269813
|
-
});
|
|
269814
|
-
var PreviewUrlSchema = object({
|
|
269815
|
-
preview_url: string2().min(1)
|
|
269816
|
-
});
|
|
269817
|
-
var ConversationMessageSchema = object({
|
|
269818
|
-
id: string2(),
|
|
269819
|
-
role: string2(),
|
|
269820
|
-
hidden: boolean2().nullish(),
|
|
269821
|
-
outcome: unknown().nullish(),
|
|
269822
|
-
content: unknown().nullish(),
|
|
269823
|
-
reasoning: object({ content: string2().nullish() }).nullish(),
|
|
269824
|
-
tool_calls: array(object({
|
|
269825
|
-
id: string2(),
|
|
269826
|
-
name: string2(),
|
|
269827
|
-
arguments_string: string2().nullish(),
|
|
269828
|
-
status: string2().nullish(),
|
|
269829
|
-
results: unknown().nullish()
|
|
269830
|
-
})).nullish()
|
|
269831
|
-
});
|
|
269832
|
-
var FullConversationSchema = object({
|
|
269833
|
-
messages: array(ConversationMessageSchema).default([])
|
|
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()
|
|
269834
269660
|
});
|
|
269835
|
-
|
|
269836
|
-
|
|
269837
|
-
const
|
|
269661
|
+
async function loadSSOConfigFile(filePath) {
|
|
269662
|
+
const resolved = resolve7(filePath);
|
|
269663
|
+
const raw = await readJsonFile(resolved);
|
|
269664
|
+
const result = SSOConfigFileSchema.safeParse(raw);
|
|
269838
269665
|
if (!result.success) {
|
|
269839
|
-
throw new SchemaValidationError(
|
|
269666
|
+
throw new SchemaValidationError("Invalid SSO config file", result.error, filePath);
|
|
269840
269667
|
}
|
|
269841
269668
|
return result.data;
|
|
269842
269669
|
}
|
|
269843
|
-
function
|
|
269844
|
-
return
|
|
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
|
+
};
|
|
269845
269687
|
}
|
|
269846
|
-
|
|
269847
|
-
|
|
269848
|
-
|
|
269849
|
-
|
|
269850
|
-
|
|
269851
|
-
|
|
269852
|
-
|
|
269853
|
-
|
|
269854
|
-
|
|
269855
|
-
|
|
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
|
+
]
|
|
269856
269724
|
});
|
|
269857
|
-
} catch (error) {
|
|
269858
|
-
throw await ApiError.fromHttpError(error, "creating app");
|
|
269859
269725
|
}
|
|
269860
|
-
return
|
|
269726
|
+
return provider;
|
|
269861
269727
|
}
|
|
269862
|
-
async function
|
|
269863
|
-
|
|
269864
|
-
|
|
269865
|
-
|
|
269866
|
-
|
|
269867
|
-
|
|
269868
|
-
|
|
269869
|
-
|
|
269870
|
-
|
|
269871
|
-
|
|
269872
|
-
|
|
269873
|
-
|
|
269874
|
-
|
|
269875
|
-
|
|
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
|
+
]
|
|
269876
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);
|
|
269877
269794
|
} catch (error) {
|
|
269878
|
-
|
|
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;
|
|
269879
269807
|
}
|
|
269880
|
-
|
|
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
|
+
};
|
|
269881
269816
|
}
|
|
269882
|
-
|
|
269883
|
-
|
|
269884
|
-
|
|
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" };
|
|
269885
269866
|
}
|
|
269886
|
-
function
|
|
269887
|
-
|
|
269888
|
-
|
|
269867
|
+
function getLogoutCommand() {
|
|
269868
|
+
return new Base44Command("logout", {
|
|
269869
|
+
requireAuth: false,
|
|
269870
|
+
requireAppContext: false
|
|
269871
|
+
}).description("Logout from current device").action(logout);
|
|
269889
269872
|
}
|
|
269890
|
-
|
|
269891
|
-
|
|
269892
|
-
|
|
269893
|
-
|
|
269894
|
-
|
|
269895
|
-
|
|
269896
|
-
|
|
269897
|
-
|
|
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
|
+
};
|
|
269898
269881
|
}
|
|
269899
|
-
|
|
269882
|
+
const auth = await readAuth();
|
|
269883
|
+
return { outroMessage: `Logged in as: ${theme.styles.bold(auth.email)}` };
|
|
269900
269884
|
}
|
|
269901
|
-
|
|
269902
|
-
|
|
269903
|
-
try {
|
|
269904
|
-
response = await getAppClient().post("chat/message", {
|
|
269905
|
-
timeout: false,
|
|
269906
|
-
searchParams: {
|
|
269907
|
-
conversation_messages: "current_turn",
|
|
269908
|
-
...branchScope(branchId)
|
|
269909
|
-
},
|
|
269910
|
-
json: { content }
|
|
269911
|
-
});
|
|
269912
|
-
} catch (error) {
|
|
269913
|
-
throw await ApiError.fromHttpError(error, "sending message");
|
|
269914
|
-
}
|
|
269915
|
-
return parseOrThrow(ChatTurnSchema, await response.json(), "chat turn");
|
|
269885
|
+
function getWhoamiCommand() {
|
|
269886
|
+
return new Base44Command("whoami", { requireAppContext: false }).description("Display current authenticated user").action(whoami);
|
|
269916
269887
|
}
|
|
269917
|
-
|
|
269918
|
-
|
|
269919
|
-
|
|
269920
|
-
|
|
269921
|
-
|
|
269922
|
-
|
|
269923
|
-
|
|
269924
|
-
|
|
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` };
|
|
269925
269909
|
}
|
|
269926
|
-
|
|
269927
|
-
|
|
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
|
+
|
|
269914
|
+
// src/core/model.ts
|
|
269915
|
+
var MODELS = [
|
|
269916
|
+
{
|
|
269917
|
+
name: "Automatic",
|
|
269918
|
+
id: null,
|
|
269919
|
+
note: "matched with the best model",
|
|
269920
|
+
aliases: ["default", "auto"]
|
|
269921
|
+
},
|
|
269922
|
+
{ name: "Opus 5", id: "claude_opus_5" },
|
|
269923
|
+
{ name: "Sonnet 5", id: "claude-sonnet-5" },
|
|
269924
|
+
{ name: "Fable 5", id: "claude_fable_5", note: "uses more credits" },
|
|
269925
|
+
{ name: "GPT-5.6 Sol", id: "gpt_5_6_sol" },
|
|
269926
|
+
{
|
|
269927
|
+
name: "Gemini 3.8 Flash",
|
|
269928
|
+
id: "gemini_3_8_flash",
|
|
269929
|
+
note: "fast responses for everyday tasks"
|
|
269930
|
+
},
|
|
269931
|
+
{ name: "Base 1", id: "base1" }
|
|
269932
|
+
];
|
|
269933
|
+
var fold = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
269934
|
+
function resolvePick(input) {
|
|
269935
|
+
const key = fold(input);
|
|
269936
|
+
const byExact = MODELS.find((m) => fold(m.name) === key || m.id && fold(m.id) === key || m.aliases?.some((a) => fold(a) === key));
|
|
269937
|
+
if (byExact)
|
|
269938
|
+
return byExact;
|
|
269939
|
+
const byContains = MODELS.filter((m) => fold(m.name).includes(key) || m.id && fold(m.id).includes(key));
|
|
269940
|
+
if (byContains.length === 1)
|
|
269941
|
+
return byContains[0];
|
|
269942
|
+
const names = MODELS.map((m) => m.name).join(", ");
|
|
269943
|
+
throw new InvalidInputError(byContains.length > 1 ? `"${input}" is ambiguous — matches ${byContains.map((m) => m.name).join(", ")}.` : `Unknown model "${input}". Choose one of: ${names}.`);
|
|
269944
|
+
}
|
|
269945
|
+
async function getMe() {
|
|
269928
269946
|
try {
|
|
269929
|
-
|
|
269930
|
-
timeout: 30000,
|
|
269931
|
-
searchParams: { limit: String(limit), ...branchScope(branchId) }
|
|
269932
|
-
});
|
|
269947
|
+
return await base44Client.get("api/auth/me").json();
|
|
269933
269948
|
} catch (error) {
|
|
269934
|
-
throw await ApiError.fromHttpError(error, "reading
|
|
269949
|
+
throw await ApiError.fromHttpError(error, "reading your account");
|
|
269935
269950
|
}
|
|
269936
|
-
return parseOrThrow(FullConversationSchema, await response.json(), "conversation").messages;
|
|
269937
|
-
}
|
|
269938
|
-
async function resolveActiveBranchId() {
|
|
269939
|
-
const branches = await listBranches();
|
|
269940
|
-
return branches.length === 1 ? branches[0].id : undefined;
|
|
269941
269951
|
}
|
|
269942
|
-
async function
|
|
269943
|
-
let response;
|
|
269952
|
+
async function saveBuilderModel(userId, modelId) {
|
|
269944
269953
|
try {
|
|
269945
|
-
|
|
269946
|
-
|
|
269954
|
+
await base44Client.post(`api/auth/${userId}/update-user`, {
|
|
269955
|
+
json: { builder_model: modelId }
|
|
269947
269956
|
});
|
|
269948
269957
|
} catch (error) {
|
|
269949
|
-
|
|
269958
|
+
if (error instanceof HTTPError && error.response.status === 400) {
|
|
269959
|
+
throw new InvalidInputError("This account can't pick a builder model yet — enable the PER_USER_BUILDER_MODEL_SELECTION flag for it in PostHog, or the model isn't available in this workspace.");
|
|
269960
|
+
}
|
|
269961
|
+
throw await ApiError.fromHttpError(error, "saving your model choice");
|
|
269950
269962
|
}
|
|
269951
|
-
const url = parseOrThrow(PreviewUrlSchema, await response.json(), "preview URL").preview_url;
|
|
269952
|
-
return /^https?:\/\//.test(url) ? url : `https://${url}`;
|
|
269953
269963
|
}
|
|
269964
|
+
var displayName = (id) => MODELS.find((m) => m.id === id)?.name ?? id ?? "Automatic";
|
|
269954
269965
|
|
|
269955
|
-
// src/cli/commands/
|
|
269956
|
-
|
|
269957
|
-
|
|
269958
|
-
|
|
269959
|
-
|
|
269960
|
-
|
|
269961
|
-
|
|
269962
|
-
|
|
269963
|
-
|
|
269964
|
-
|
|
269965
|
-
|
|
269966
|
-
|
|
269967
|
-
|
|
269968
|
-
|
|
269969
|
-
|
|
269970
|
-
|
|
269971
|
-
|
|
269972
|
-
}
|
|
269973
|
-
|
|
269974
|
-
|
|
269975
|
-
|
|
269966
|
+
// src/cli/commands/builder/model.ts
|
|
269967
|
+
async function modelAction({ log, jsonMode }, input) {
|
|
269968
|
+
const me = await getMe();
|
|
269969
|
+
const current = me.builder_model ?? null;
|
|
269970
|
+
if (!input) {
|
|
269971
|
+
if (jsonMode) {
|
|
269972
|
+
return {
|
|
269973
|
+
stdout: `${JSON.stringify({
|
|
269974
|
+
current,
|
|
269975
|
+
models: MODELS.map((m) => ({ name: m.name, id: m.id }))
|
|
269976
|
+
})}
|
|
269977
|
+
`
|
|
269978
|
+
};
|
|
269979
|
+
}
|
|
269980
|
+
for (const m of MODELS) {
|
|
269981
|
+
const active = (m.id ?? null) === current;
|
|
269982
|
+
const marker = active ? theme.styles.bold("●") : theme.styles.dim("○");
|
|
269983
|
+
const note = m.note ? theme.styles.dim(` (${m.note})`) : "";
|
|
269984
|
+
log.message(`${marker} ${active ? theme.styles.bold(m.name) : m.name}${note}`);
|
|
269985
|
+
}
|
|
269986
|
+
return {
|
|
269987
|
+
outroMessage: `Current: ${theme.styles.bold(displayName(current))}. Set with \`base44 builder model <name>\`.`
|
|
269988
|
+
};
|
|
269976
269989
|
}
|
|
269977
|
-
const
|
|
269978
|
-
|
|
269979
|
-
|
|
269980
|
-
if (await appConfigExists(targetDir)) {
|
|
269981
|
-
throw new InvalidInputError(`./${name} is already linked to a Base44 app. Pick another name.`);
|
|
269990
|
+
const pick = resolvePick(input);
|
|
269991
|
+
if ((pick.id ?? null) === current) {
|
|
269992
|
+
return { outroMessage: `Already on ${theme.styles.bold(pick.name)}.` };
|
|
269982
269993
|
}
|
|
269983
|
-
|
|
269984
|
-
|
|
269985
|
-
|
|
269986
|
-
|
|
269987
|
-
newRepoName: options.repoName,
|
|
269988
|
-
branch: options.fromBranch,
|
|
269989
|
-
prompt: options.prompt
|
|
269990
|
-
}) : await createApp({ appName: name, prompt: options.prompt });
|
|
269991
|
-
await writeAppConfig(targetDir, created.id);
|
|
269992
|
-
await mkdir3(join21(targetDir, "base44"), { recursive: true });
|
|
269993
|
-
try {
|
|
269994
|
-
await writeFile2(join21(targetDir, "base44", "config.jsonc"), `// Base44 project configuration.
|
|
269995
|
-
{
|
|
269996
|
-
"name": ${JSON.stringify(name)}
|
|
269997
|
-
}
|
|
269998
|
-
`, { flag: "wx" });
|
|
269999
|
-
} catch {}
|
|
270000
|
-
setAppContext({ id: created.id, projectRoot: targetDir });
|
|
269994
|
+
await saveBuilderModel(me.id, pick.id);
|
|
269995
|
+
if (jsonMode)
|
|
269996
|
+
return { stdout: `${JSON.stringify({ current: pick.id })}
|
|
269997
|
+
` };
|
|
270001
269998
|
return {
|
|
270002
|
-
id:
|
|
270003
|
-
editorUrl: `${getBase44ApiUrl()}/apps/${created.id}/editor/preview`,
|
|
270004
|
-
repoUrl: created.imported_repo_url ?? undefined,
|
|
270005
|
-
dirName: name,
|
|
270006
|
-
targetDir
|
|
269999
|
+
outroMessage: pick.id === null ? "Model reset — Base44 chooses per app again." : `Builder model set to ${theme.styles.bold(pick.name)} for every new turn.`
|
|
270007
270000
|
};
|
|
270008
270001
|
}
|
|
270009
|
-
function
|
|
270010
|
-
|
|
270011
|
-
|
|
270012
|
-
|
|
270013
|
-
source_default.dim(` cd ${app.dirName} && base44 code # keep building with the agent`),
|
|
270014
|
-
source_default.dim(` cd ${app.dirName} && base44 app send "…" # one non-interactive turn`)
|
|
270015
|
-
];
|
|
270016
|
-
}
|
|
270017
|
-
async function githubReauthLines(error) {
|
|
270018
|
-
if (!isGithubUserTokenError(error))
|
|
270019
|
-
return null;
|
|
270020
|
-
const link = await startGithubReauth().catch(() => null);
|
|
270021
|
-
return [
|
|
270022
|
-
"Your GitHub authorization expired. Reconnect, then run this again:",
|
|
270023
|
-
link ? terminalLink("Reconnect GitHub", link) : "Open Base44 → GitHub settings to reconnect your account."
|
|
270024
|
-
];
|
|
270025
|
-
}
|
|
270026
|
-
async function resolveBranchId(ctx) {
|
|
270027
|
-
return ctx.branchId ?? await resolveActiveBranchId().catch(() => {
|
|
270028
|
-
return;
|
|
270029
|
-
});
|
|
270002
|
+
function getModelCommand() {
|
|
270003
|
+
const command = new Base44Command("model", { requireAppContext: false });
|
|
270004
|
+
command.description("Pick the builder model for your turns (account-wide). No argument lists models and the current pick; `default` clears it").argument("[model]", 'Model name or id, e.g. "Opus 5" or default').action(modelAction);
|
|
270005
|
+
return command;
|
|
270030
270006
|
}
|
|
270031
270007
|
|
|
270032
|
-
// src/
|
|
270033
|
-
|
|
270034
|
-
|
|
270008
|
+
// src/cli/commands/builder/shared.ts
|
|
270009
|
+
import { mkdir as mkdir3, writeFile as writeFile2 } from "node:fs/promises";
|
|
270010
|
+
import { basename as basename6, join as join25, relative as relative6, resolve as resolve8 } from "node:path";
|
|
270011
|
+
|
|
270012
|
+
// src/cli/commands/code/render.ts
|
|
270013
|
+
var TOOL_ALIASES = {
|
|
270014
|
+
run_shell_command: "bash",
|
|
270015
|
+
read_repo_file: "read",
|
|
270016
|
+
write_repo_file: "write",
|
|
270017
|
+
edit_repo_file: "edit",
|
|
270018
|
+
set_secrets: "secrets",
|
|
270019
|
+
generate_development_secrets: "secrets",
|
|
270020
|
+
create_pull_request: "pr",
|
|
270021
|
+
merge_pull_request: "merge",
|
|
270022
|
+
list_pr_threads: "pr threads",
|
|
270023
|
+
reply_to_pr_thread: "pr reply",
|
|
270024
|
+
comment_on_pr: "pr comment",
|
|
270025
|
+
resolve_pr_thread: "pr resolve",
|
|
270026
|
+
reload_preview: "reload",
|
|
270027
|
+
preview_execute_code: "preview js",
|
|
270028
|
+
preview_screenshot: "screenshot",
|
|
270029
|
+
connect_github_account: "github"
|
|
270030
|
+
};
|
|
270031
|
+
var QUIET_OK_RESULTS = new Set(["read", "write", "edit", "reload"]);
|
|
270032
|
+
function toolAlias(name) {
|
|
270033
|
+
return TOOL_ALIASES[name] ?? name;
|
|
270035
270034
|
}
|
|
270036
|
-
var
|
|
270037
|
-
|
|
270038
|
-
|
|
270039
|
-
|
|
270040
|
-
return
|
|
270035
|
+
var ESC_CHAR = String.fromCharCode(27);
|
|
270036
|
+
var BEL = String.fromCharCode(7);
|
|
270037
|
+
var OSC8_CLOSE = `${ESC_CHAR}]8;;${BEL}`;
|
|
270038
|
+
function terminalLink(label, url) {
|
|
270039
|
+
return `${ESC_CHAR}]8;;${url}${BEL}${source_default.dim.underline(label)}${OSC8_CLOSE}`;
|
|
270041
270040
|
}
|
|
270042
|
-
|
|
270043
|
-
|
|
270044
|
-
|
|
270045
|
-
const
|
|
270046
|
-
|
|
270047
|
-
|
|
270048
|
-
|
|
270049
|
-
|
|
270041
|
+
function linkifyUrls(text) {
|
|
270042
|
+
let n = 0;
|
|
270043
|
+
return text.replace(/https?:\/\/[^\s]+/g, (raw) => {
|
|
270044
|
+
const trailing = raw.match(/[.,;:!?)\]}'"]+$/)?.[0] ?? "";
|
|
270045
|
+
const url = trailing ? raw.slice(0, -trailing.length) : raw;
|
|
270046
|
+
const id = `b44-${n++}`;
|
|
270047
|
+
return `${ESC_CHAR}]8;id=${id};${url}${BEL}${url}${OSC8_CLOSE}${trailing}`;
|
|
270048
|
+
});
|
|
270050
270049
|
}
|
|
270051
|
-
function
|
|
270052
|
-
const
|
|
270053
|
-
|
|
270054
|
-
|
|
270055
|
-
|
|
270056
|
-
|
|
270057
|
-
|
|
270050
|
+
function hardWrapAnsi(text, width) {
|
|
270051
|
+
const ESC = new RegExp(`^(?:${ESC_CHAR}\\[[0-9;]*m|${ESC_CHAR}\\]8;[^${BEL}]*${BEL})`);
|
|
270052
|
+
const RESET = `${ESC_CHAR}[0m`;
|
|
270053
|
+
const out = [];
|
|
270054
|
+
for (const logical of text.split(`
|
|
270055
|
+
`)) {
|
|
270056
|
+
let line = "";
|
|
270057
|
+
let visible = 0;
|
|
270058
|
+
let active = [];
|
|
270059
|
+
let link = "";
|
|
270060
|
+
let i = 0;
|
|
270061
|
+
while (i < logical.length) {
|
|
270062
|
+
const esc = ESC.exec(logical.slice(i));
|
|
270063
|
+
if (esc) {
|
|
270064
|
+
const seq = esc[0];
|
|
270065
|
+
line += seq;
|
|
270066
|
+
if (seq === RESET)
|
|
270067
|
+
active = [];
|
|
270068
|
+
else if (seq.endsWith("m"))
|
|
270069
|
+
active.push(seq);
|
|
270070
|
+
else if (seq === OSC8_CLOSE)
|
|
270071
|
+
link = "";
|
|
270072
|
+
else
|
|
270073
|
+
link = seq;
|
|
270074
|
+
i += seq.length;
|
|
270075
|
+
continue;
|
|
270076
|
+
}
|
|
270077
|
+
if (visible >= width) {
|
|
270078
|
+
out.push(`${line}${link ? OSC8_CLOSE : ""}${RESET}`);
|
|
270079
|
+
line = active.join("") + link;
|
|
270080
|
+
visible = 0;
|
|
270081
|
+
}
|
|
270082
|
+
line += logical[i];
|
|
270083
|
+
visible++;
|
|
270084
|
+
i++;
|
|
270058
270085
|
}
|
|
270059
|
-
|
|
270060
|
-
return {
|
|
270061
|
-
label: oneLine(salvageKey(raw, ["summary"]) ?? "", 90),
|
|
270062
|
-
summary: oneLine(salvageKey(raw, SALIENT_KEYS) ?? raw, 90)
|
|
270063
|
-
};
|
|
270086
|
+
out.push(line);
|
|
270064
270087
|
}
|
|
270065
|
-
|
|
270066
|
-
const salient = {
|
|
270067
|
-
run_shell_command: pick("command"),
|
|
270068
|
-
read_repo_file: pick("path") ?? pick("file_path"),
|
|
270069
|
-
write_repo_file: pick("path") ?? pick("file_path"),
|
|
270070
|
-
edit_repo_file: pick("path") ?? pick("file_path"),
|
|
270071
|
-
create_pull_request: pick("title"),
|
|
270072
|
-
reload_preview: ""
|
|
270073
|
-
};
|
|
270074
|
-
const summary = salient[name] ?? Object.entries(args).find(([key, v]) => key !== "summary" && typeof v === "string" && v.trim().length > 0)?.[1] ?? "";
|
|
270075
|
-
return {
|
|
270076
|
-
label: oneLine(pick("summary") ?? "", 90),
|
|
270077
|
-
summary: oneLine(summary, 90)
|
|
270078
|
-
};
|
|
270088
|
+
return out;
|
|
270079
270089
|
}
|
|
270080
|
-
function
|
|
270081
|
-
const
|
|
270082
|
-
if (
|
|
270083
|
-
return
|
|
270084
|
-
return
|
|
270090
|
+
function formatDuration2(ms) {
|
|
270091
|
+
const seconds = Math.round(ms / 1000);
|
|
270092
|
+
if (seconds < 90)
|
|
270093
|
+
return `${seconds}s`;
|
|
270094
|
+
return `${Math.floor(seconds / 60)}m ${String(seconds % 60).padStart(2, "0")}s`;
|
|
270085
270095
|
}
|
|
270086
|
-
function
|
|
270087
|
-
|
|
270088
|
-
|
|
270089
|
-
|
|
270090
|
-
|
|
270091
|
-
|
|
270092
|
-
|
|
270093
|
-
|
|
270094
|
-
|
|
270095
|
-
|
|
270096
|
-
|
|
270096
|
+
function eventLine(event, elapsedMs) {
|
|
270097
|
+
switch (event.kind) {
|
|
270098
|
+
case "thinking":
|
|
270099
|
+
return source_default.dim(`✻ ${event.text}`);
|
|
270100
|
+
case "text":
|
|
270101
|
+
return linkifyUrls(event.text);
|
|
270102
|
+
case "tool_start":
|
|
270103
|
+
return null;
|
|
270104
|
+
case "waiting": {
|
|
270105
|
+
const what = event.label || toolAlias(event.name);
|
|
270106
|
+
return source_default.yellow(`⏸ ${what} — needs your input (answer in the editor)`);
|
|
270107
|
+
}
|
|
270108
|
+
case "tool_end": {
|
|
270109
|
+
const alias = toolAlias(event.name);
|
|
270110
|
+
const mark = event.ok ? source_default.green("✓") : source_default.red("✗");
|
|
270111
|
+
const title = source_default.bold(event.label || alias);
|
|
270112
|
+
const took = elapsedMs != null && elapsedMs >= 3000 ? ` ${source_default.dim(`· ${formatDuration2(elapsedMs)}`)}` : "";
|
|
270113
|
+
const inlineDetail = !event.label && event.summary ? ` ${source_default.dim(event.summary)}` : "";
|
|
270114
|
+
const paramsLine = event.label && event.summary ? `
|
|
270115
|
+
${source_default.dim(`${alias}: ${event.summary}`)}` : "";
|
|
270116
|
+
const head = `${mark} ${title}${inlineDetail}${took}${paramsLine}`;
|
|
270117
|
+
if (event.ok && (QUIET_OK_RESULTS.has(alias) || !event.result)) {
|
|
270118
|
+
return head;
|
|
270119
|
+
}
|
|
270120
|
+
const result = event.ok ? source_default.dim(linkifyUrls(event.result)) : source_default.red(linkifyUrls(event.result));
|
|
270121
|
+
return `${head}${event.result ? `
|
|
270122
|
+
${result}` : ""}`;
|
|
270123
|
+
}
|
|
270097
270124
|
}
|
|
270098
|
-
return progress;
|
|
270099
270125
|
}
|
|
270100
|
-
|
|
270101
|
-
|
|
270102
|
-
|
|
270103
|
-
|
|
270104
|
-
|
|
270105
|
-
|
|
270106
|
-
|
|
270107
|
-
|
|
270108
|
-
|
|
270109
|
-
|
|
270110
|
-
|
|
270111
|
-
|
|
270112
|
-
|
|
270113
|
-
|
|
270114
|
-
|
|
270115
|
-
|
|
270116
|
-
|
|
270117
|
-
|
|
270126
|
+
var FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
270127
|
+
var MUSINGS = [
|
|
270128
|
+
"Shmoozing",
|
|
270129
|
+
"Shmoogling",
|
|
270130
|
+
"Percolating",
|
|
270131
|
+
"Noodling",
|
|
270132
|
+
"Marinating",
|
|
270133
|
+
"Brewing",
|
|
270134
|
+
"Simmering",
|
|
270135
|
+
"Conjuring",
|
|
270136
|
+
"Tinkering",
|
|
270137
|
+
"Scheming",
|
|
270138
|
+
"Pondering",
|
|
270139
|
+
"Mulling",
|
|
270140
|
+
"Whirring",
|
|
270141
|
+
"Crunching",
|
|
270142
|
+
"Weaving",
|
|
270143
|
+
"Sketching",
|
|
270144
|
+
"Hatching",
|
|
270145
|
+
"Riffing",
|
|
270146
|
+
"Cooking",
|
|
270147
|
+
"Composting ideas",
|
|
270148
|
+
"Rummaging",
|
|
270149
|
+
"Vibing responsibly",
|
|
270150
|
+
"Untangling",
|
|
270151
|
+
"Squinting at the repo"
|
|
270152
|
+
];
|
|
270153
|
+
var MUSING_ROTATE_MS = 6000;
|
|
270154
|
+
function idleMusing(seed) {
|
|
270155
|
+
return `${MUSINGS[(seed + Math.floor(Date.now() / MUSING_ROTATE_MS)) % MUSINGS.length]}…`;
|
|
270156
|
+
}
|
|
270157
|
+
function createTurnStream(interactive, write = (text) => process.stdout.write(text), options = {}) {
|
|
270158
|
+
const running = new Map;
|
|
270159
|
+
const footer = options.footer ?? [];
|
|
270160
|
+
let frame = 0;
|
|
270161
|
+
let stopped = false;
|
|
270162
|
+
let drawnLines = 0;
|
|
270163
|
+
const musingSeed = Math.floor(Math.random() * MUSINGS.length);
|
|
270164
|
+
const statusLabel = () => {
|
|
270165
|
+
if (running.size === 0) {
|
|
270166
|
+
if (options.idleLabel)
|
|
270167
|
+
return `${options.idleLabel}…`;
|
|
270168
|
+
const index = (musingSeed + Math.floor(Date.now() / MUSING_ROTATE_MS)) % MUSINGS.length;
|
|
270169
|
+
return `${MUSINGS[index]}…`;
|
|
270118
270170
|
}
|
|
270119
|
-
|
|
270120
|
-
|
|
270121
|
-
|
|
270122
|
-
|
|
270123
|
-
|
|
270124
|
-
|
|
270125
|
-
|
|
270126
|
-
|
|
270127
|
-
|
|
270128
|
-
|
|
270129
|
-
|
|
270130
|
-
|
|
270131
|
-
|
|
270132
|
-
|
|
270133
|
-
|
|
270134
|
-
|
|
270135
|
-
|
|
270136
|
-
|
|
270137
|
-
|
|
270138
|
-
|
|
270139
|
-
|
|
270171
|
+
const newest = [...running.values()].at(-1);
|
|
270172
|
+
const elapsed = Math.round((Date.now() - newest.startedAt) / 1000);
|
|
270173
|
+
const others = running.size > 1 ? ` (+${running.size - 1} more)` : "";
|
|
270174
|
+
const what = newest.label || `${newest.alias}${newest.summary ? ` ${newest.summary}` : ""}`;
|
|
270175
|
+
return `${what}${others} · ${elapsed}s`;
|
|
270176
|
+
};
|
|
270177
|
+
const clearBlock = () => {
|
|
270178
|
+
if (!drawnLines)
|
|
270179
|
+
return;
|
|
270180
|
+
write("\r\x1B[2K");
|
|
270181
|
+
for (let i = 1;i < drawnLines; i++)
|
|
270182
|
+
write("\x1B[1A\r\x1B[2K");
|
|
270183
|
+
drawnLines = 0;
|
|
270184
|
+
};
|
|
270185
|
+
const drawBlock = () => {
|
|
270186
|
+
if (!interactive || stopped)
|
|
270187
|
+
return;
|
|
270188
|
+
const lines = [
|
|
270189
|
+
...footer.length ? ["", ...footer] : [],
|
|
270190
|
+
source_default.dim(`${FRAMES[frame]} ${statusLabel()}`)
|
|
270191
|
+
];
|
|
270192
|
+
write(lines.join(`
|
|
270193
|
+
`));
|
|
270194
|
+
drawnLines = lines.length;
|
|
270195
|
+
};
|
|
270196
|
+
const tick = () => {
|
|
270197
|
+
if (!interactive || stopped)
|
|
270198
|
+
return;
|
|
270199
|
+
frame = (frame + 1) % FRAMES.length;
|
|
270200
|
+
clearBlock();
|
|
270201
|
+
drawBlock();
|
|
270202
|
+
};
|
|
270203
|
+
const timer = interactive ? setInterval(tick, 120) : null;
|
|
270204
|
+
if (timer)
|
|
270205
|
+
timer.unref?.();
|
|
270206
|
+
return {
|
|
270207
|
+
onEvent(event) {
|
|
270208
|
+
if (event.kind === "tool_start") {
|
|
270209
|
+
running.set(event.id, {
|
|
270210
|
+
alias: toolAlias(event.name),
|
|
270211
|
+
label: event.label,
|
|
270212
|
+
summary: event.summary,
|
|
270213
|
+
startedAt: Date.now()
|
|
270140
270214
|
});
|
|
270215
|
+
if (interactive) {
|
|
270216
|
+
clearBlock();
|
|
270217
|
+
drawBlock();
|
|
270218
|
+
}
|
|
270219
|
+
return;
|
|
270141
270220
|
}
|
|
270142
|
-
|
|
270143
|
-
|
|
270144
|
-
const
|
|
270145
|
-
|
|
270146
|
-
|
|
270147
|
-
|
|
270148
|
-
name: tool.name,
|
|
270149
|
-
label: labelTense(meta.label, "done"),
|
|
270150
|
-
summary: meta.summary,
|
|
270151
|
-
ok: status === "success",
|
|
270152
|
-
result: oneLine(tool.results, 110)
|
|
270153
|
-
});
|
|
270221
|
+
let elapsedMs;
|
|
270222
|
+
if (event.kind === "tool_end") {
|
|
270223
|
+
const started = running.get(event.id)?.startedAt;
|
|
270224
|
+
if (started != null)
|
|
270225
|
+
elapsedMs = Date.now() - started;
|
|
270226
|
+
running.delete(event.id);
|
|
270154
270227
|
}
|
|
270155
|
-
|
|
270156
|
-
|
|
270157
|
-
|
|
270158
|
-
|
|
270159
|
-
|
|
270160
|
-
|
|
270161
|
-
|
|
270162
|
-
|
|
270163
|
-
|
|
270164
|
-
|
|
270165
|
-
|
|
270166
|
-
|
|
270167
|
-
|
|
270168
|
-
|
|
270169
|
-
|
|
270170
|
-
|
|
270171
|
-
|
|
270172
|
-
|
|
270173
|
-
|
|
270174
|
-
|
|
270175
|
-
|
|
270176
|
-
}
|
|
270177
|
-
|
|
270178
|
-
|
|
270179
|
-
|
|
270180
|
-
return async (prime = false) => {
|
|
270181
|
-
try {
|
|
270182
|
-
const messages = await getFullConversation(30, options.branchId);
|
|
270183
|
-
const events = diffConversation(state, messages);
|
|
270184
|
-
if (!prime)
|
|
270185
|
-
for (const event of events)
|
|
270186
|
-
onEvent(event);
|
|
270187
|
-
return messages;
|
|
270188
|
-
} catch {
|
|
270189
|
-
return [];
|
|
270228
|
+
const line = eventLine(event, elapsedMs);
|
|
270229
|
+
if (line == null)
|
|
270230
|
+
return;
|
|
270231
|
+
if (interactive) {
|
|
270232
|
+
clearBlock();
|
|
270233
|
+
write(`${line}
|
|
270234
|
+
`);
|
|
270235
|
+
drawBlock();
|
|
270236
|
+
} else {
|
|
270237
|
+
write(`${line}
|
|
270238
|
+
`);
|
|
270239
|
+
}
|
|
270240
|
+
},
|
|
270241
|
+
stop() {
|
|
270242
|
+
if (interactive) {
|
|
270243
|
+
clearBlock();
|
|
270244
|
+
if (footer.length)
|
|
270245
|
+
write(`
|
|
270246
|
+
${footer.join(`
|
|
270247
|
+
`)}
|
|
270248
|
+
`);
|
|
270249
|
+
}
|
|
270250
|
+
stopped = true;
|
|
270251
|
+
if (timer)
|
|
270252
|
+
clearInterval(timer);
|
|
270190
270253
|
}
|
|
270191
270254
|
};
|
|
270192
270255
|
}
|
|
270193
|
-
|
|
270194
|
-
|
|
270195
|
-
|
|
270196
|
-
|
|
270197
|
-
|
|
270198
|
-
|
|
270199
|
-
|
|
270200
|
-
|
|
270201
|
-
|
|
270202
|
-
|
|
270203
|
-
|
|
270204
|
-
|
|
270205
|
-
|
|
270206
|
-
|
|
270207
|
-
|
|
270208
|
-
|
|
270256
|
+
|
|
270257
|
+
// src/core/resources/apps/api.ts
|
|
270258
|
+
var CreatedAppSchema = object({
|
|
270259
|
+
id: string2().min(1),
|
|
270260
|
+
name: string2().nullish(),
|
|
270261
|
+
imported_repo_url: string2().nullish()
|
|
270262
|
+
});
|
|
270263
|
+
var AppStateSchema = object({
|
|
270264
|
+
id: string2(),
|
|
270265
|
+
app_type: string2().nullish(),
|
|
270266
|
+
is_managed_source_code: boolean2().nullish(),
|
|
270267
|
+
imported_repo_url: string2().nullish(),
|
|
270268
|
+
status: object({
|
|
270269
|
+
state: string2().nullish(),
|
|
270270
|
+
message: string2().nullish()
|
|
270271
|
+
}).nullish()
|
|
270272
|
+
});
|
|
270273
|
+
var ChatTurnSchema = object({
|
|
270274
|
+
queued: boolean2().optional(),
|
|
270275
|
+
status: object({
|
|
270276
|
+
state: string2().nullish(),
|
|
270277
|
+
message: string2().nullish(),
|
|
270278
|
+
error_source: string2().nullish()
|
|
270279
|
+
}).nullish(),
|
|
270280
|
+
conversation: object({
|
|
270281
|
+
messages: array(object({
|
|
270282
|
+
role: string2().nullish(),
|
|
270283
|
+
content: unknown().nullish()
|
|
270284
|
+
})).nullish()
|
|
270285
|
+
}).nullish()
|
|
270286
|
+
});
|
|
270287
|
+
var PreviewUrlSchema = object({
|
|
270288
|
+
preview_url: string2().min(1)
|
|
270289
|
+
});
|
|
270290
|
+
var ConversationMessageSchema = object({
|
|
270291
|
+
id: string2(),
|
|
270292
|
+
role: string2(),
|
|
270293
|
+
hidden: boolean2().nullish(),
|
|
270294
|
+
outcome: unknown().nullish(),
|
|
270295
|
+
content: unknown().nullish(),
|
|
270296
|
+
reasoning: object({ content: string2().nullish() }).nullish(),
|
|
270297
|
+
tool_calls: array(object({
|
|
270298
|
+
id: string2(),
|
|
270299
|
+
name: string2(),
|
|
270300
|
+
arguments_string: string2().nullish(),
|
|
270301
|
+
status: string2().nullish(),
|
|
270302
|
+
results: unknown().nullish()
|
|
270303
|
+
})).nullish()
|
|
270304
|
+
});
|
|
270305
|
+
var FullConversationSchema = object({
|
|
270306
|
+
messages: array(ConversationMessageSchema).default([])
|
|
270307
|
+
});
|
|
270308
|
+
var OAuthInitiateSchema = object({ authorization_url: string2().min(1) });
|
|
270309
|
+
function parseOrThrow(schema, payload, what) {
|
|
270310
|
+
const result = schema.safeParse(payload);
|
|
270311
|
+
if (!result.success) {
|
|
270312
|
+
throw new SchemaValidationError(`Invalid ${what} response from server`, result.error);
|
|
270209
270313
|
}
|
|
270210
|
-
|
|
270211
|
-
return work;
|
|
270314
|
+
return result.data;
|
|
270212
270315
|
}
|
|
270213
|
-
|
|
270214
|
-
|
|
270215
|
-
const deadline = Date.now() + (options.timeoutMs ?? 20 * 60000);
|
|
270216
|
-
const poll = makePoller(onEvent, options);
|
|
270217
|
-
while (Date.now() < deadline) {
|
|
270218
|
-
const messages = await poll();
|
|
270219
|
-
if (messages.length > 0 && turnSettled(messages))
|
|
270220
|
-
return "settled";
|
|
270221
|
-
await sleep3(intervalMs);
|
|
270222
|
-
}
|
|
270223
|
-
return "timeout";
|
|
270316
|
+
function branchScope(branchId) {
|
|
270317
|
+
return branchId ? { branch_id: branchId } : {};
|
|
270224
270318
|
}
|
|
270225
|
-
|
|
270226
|
-
|
|
270227
|
-
|
|
270228
|
-
|
|
270229
|
-
|
|
270230
|
-
|
|
270231
|
-
|
|
270232
|
-
|
|
270233
|
-
|
|
270234
|
-
|
|
270235
|
-
|
|
270236
|
-
|
|
270237
|
-
throw
|
|
270238
|
-
}
|
|
270239
|
-
let app;
|
|
270240
|
-
try {
|
|
270241
|
-
app = await runTask(options.import ? "Importing the repository" : "Creating your app", () => createAndLinkApp({
|
|
270242
|
-
prompt,
|
|
270243
|
-
name: options.name,
|
|
270244
|
-
importRepo: options.import,
|
|
270245
|
-
mode: options.mode,
|
|
270246
|
-
repoName: options.repoName,
|
|
270247
|
-
fromBranch: options.fromBranch
|
|
270248
|
-
}));
|
|
270249
|
-
} catch (error) {
|
|
270250
|
-
for (const line of await githubReauthLines(error) ?? [])
|
|
270251
|
-
log.message(line);
|
|
270252
|
-
throw error;
|
|
270253
|
-
}
|
|
270254
|
-
if (!jsonMode) {
|
|
270255
|
-
if (app.repoUrl)
|
|
270256
|
-
log.message(source_default.dim(`repo ${app.repoUrl}`));
|
|
270257
|
-
log.message(source_default.dim(`editor ${app.editorUrl}`));
|
|
270258
|
-
log.message(source_default.dim(`linked ./${app.dirName}`));
|
|
270259
|
-
}
|
|
270260
|
-
let finalState;
|
|
270261
|
-
let previewUrl;
|
|
270262
|
-
const startedAt = Date.now();
|
|
270263
|
-
if (prompt) {
|
|
270264
|
-
const branchId = await resolveActiveBranchId().catch(() => {
|
|
270265
|
-
return;
|
|
270266
|
-
});
|
|
270267
|
-
const stream = createTurnStream(process.stdout.isTTY === true && !jsonMode, undefined, { idleLabel: "provisioning the sandbox and starting the build" });
|
|
270268
|
-
try {
|
|
270269
|
-
const settled = await streamConversationUntilSettled((event) => {
|
|
270270
|
-
if (!jsonMode)
|
|
270271
|
-
stream.onEvent(event);
|
|
270272
|
-
}, { branchId, timeoutMs: POLL_TIMEOUT_MS });
|
|
270273
|
-
finalState = settled === "timeout" ? "processing" : (await getAppState(app.id)).status?.state ?? "ready";
|
|
270274
|
-
if (finalState === "ready") {
|
|
270275
|
-
previewUrl = await getPreviewUrl().catch(() => {
|
|
270276
|
-
return;
|
|
270277
|
-
});
|
|
270278
|
-
}
|
|
270279
|
-
} finally {
|
|
270280
|
-
stream.stop();
|
|
270281
|
-
}
|
|
270282
|
-
}
|
|
270283
|
-
if (jsonMode) {
|
|
270284
|
-
return {
|
|
270285
|
-
stdout: `${JSON.stringify({
|
|
270286
|
-
id: app.id,
|
|
270287
|
-
repo_url: app.repoUrl ?? null,
|
|
270288
|
-
editor_url: app.editorUrl,
|
|
270289
|
-
preview_url: previewUrl ?? null,
|
|
270290
|
-
dir: app.dirName,
|
|
270291
|
-
path: app.targetDir,
|
|
270292
|
-
status: finalState ?? "created"
|
|
270293
|
-
})}
|
|
270294
|
-
`
|
|
270295
|
-
};
|
|
270296
|
-
}
|
|
270297
|
-
if (previewUrl)
|
|
270298
|
-
log.message(`preview ${previewUrl}`);
|
|
270299
|
-
for (const line of nextStepsLines(app))
|
|
270300
|
-
log.message(line);
|
|
270301
|
-
if (finalState === "error") {
|
|
270302
|
-
return {
|
|
270303
|
-
outroMessage: `The first build reported an error — open the editor for details.`
|
|
270304
|
-
};
|
|
270305
|
-
}
|
|
270306
|
-
if (finalState === "processing") {
|
|
270307
|
-
return {
|
|
270308
|
-
outroMessage: `Still building — follow it with \`base44 app status\`.`
|
|
270309
|
-
};
|
|
270319
|
+
async function createApp(options) {
|
|
270320
|
+
let response;
|
|
270321
|
+
try {
|
|
270322
|
+
response = await base44Client.post("api/apps", {
|
|
270323
|
+
timeout: false,
|
|
270324
|
+
json: {
|
|
270325
|
+
...options.appName ? { name: options.appName } : {},
|
|
270326
|
+
...options.organizationId ? { organization_id: options.organizationId } : {},
|
|
270327
|
+
...options.prompt ? { initial_message: { content: options.prompt } } : {}
|
|
270328
|
+
}
|
|
270329
|
+
});
|
|
270330
|
+
} catch (error) {
|
|
270331
|
+
throw await ApiError.fromHttpError(error, "creating app");
|
|
270310
270332
|
}
|
|
270311
|
-
return
|
|
270312
|
-
outroMessage: prompt ? `First build finished · ${formatDuration2(Date.now() - startedAt)}.` : "App created."
|
|
270313
|
-
};
|
|
270333
|
+
return parseOrThrow(CreatedAppSchema, await response.json(), "app");
|
|
270314
270334
|
}
|
|
270315
|
-
function
|
|
270316
|
-
|
|
270317
|
-
|
|
270318
|
-
|
|
270335
|
+
async function createImportedApp(options) {
|
|
270336
|
+
let response;
|
|
270337
|
+
try {
|
|
270338
|
+
response = await base44Client.post("api/apps", {
|
|
270339
|
+
timeout: false,
|
|
270340
|
+
json: {
|
|
270341
|
+
app_type: "imported_app",
|
|
270342
|
+
name: options.appName,
|
|
270343
|
+
imported_source_mode: options.sourceMode,
|
|
270344
|
+
imported_repo_url: options.repoUrl,
|
|
270345
|
+
...options.newRepoName ? { imported_new_repo_name: options.newRepoName } : {},
|
|
270346
|
+
...options.branch ? { imported_branch: options.branch } : {},
|
|
270347
|
+
...options.prompt ? { initial_message: { content: options.prompt } } : {}
|
|
270348
|
+
}
|
|
270349
|
+
});
|
|
270350
|
+
} catch (error) {
|
|
270351
|
+
throw await ApiError.fromHttpError(error, "importing repository");
|
|
270352
|
+
}
|
|
270353
|
+
return parseOrThrow(CreatedAppSchema, await response.json(), "app");
|
|
270319
270354
|
}
|
|
270320
|
-
|
|
270321
|
-
|
|
270322
|
-
|
|
270323
|
-
const url = await ctx.runTask("Resolving preview URL (boots the sandbox if needed)", () => getPreviewUrl());
|
|
270324
|
-
if (ctx.jsonMode)
|
|
270325
|
-
return { stdout: `${JSON.stringify({ preview_url: url })}
|
|
270326
|
-
` };
|
|
270327
|
-
ctx.log.message(url);
|
|
270328
|
-
return { outroMessage: "Preview is live." };
|
|
270355
|
+
async function startGithubReauth() {
|
|
270356
|
+
const response = await base44Client.post("api/github/oauth/initiate?skip_installation=true");
|
|
270357
|
+
return parseOrThrow(OAuthInitiateSchema, await response.json(), "github oauth initiate").authorization_url;
|
|
270329
270358
|
}
|
|
270330
|
-
function
|
|
270331
|
-
const
|
|
270332
|
-
|
|
270333
|
-
return command;
|
|
270359
|
+
function isGithubUserTokenError(error) {
|
|
270360
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
270361
|
+
return /api\.github\.com/i.test(message) && /\b401\b|unauthorized/i.test(message);
|
|
270334
270362
|
}
|
|
270335
|
-
|
|
270336
|
-
|
|
270337
|
-
|
|
270338
|
-
|
|
270339
|
-
|
|
270340
|
-
|
|
270341
|
-
|
|
270342
|
-
|
|
270343
|
-
|
|
270363
|
+
async function getAppState(appId) {
|
|
270364
|
+
let response;
|
|
270365
|
+
try {
|
|
270366
|
+
response = await base44Client.get(`api/apps/${appId}`, {
|
|
270367
|
+
searchParams: {
|
|
270368
|
+
fields: "id,status,app_type,is_managed_source_code,imported_repo_url"
|
|
270369
|
+
}
|
|
270370
|
+
});
|
|
270371
|
+
} catch (error) {
|
|
270372
|
+
throw await ApiError.fromHttpError(error, "reading app status");
|
|
270344
270373
|
}
|
|
270345
|
-
return;
|
|
270374
|
+
return parseOrThrow(AppStateSchema, await response.json(), "app status");
|
|
270346
270375
|
}
|
|
270347
|
-
async function
|
|
270348
|
-
|
|
270349
|
-
if (ctx.jsonMode) {
|
|
270350
|
-
const turn = await ctx.runTask("Agent working (a turn can take minutes)", () => sendTurn(message, branchId));
|
|
270351
|
-
if (turn.queued)
|
|
270352
|
-
return { stdout: `${JSON.stringify({ queued: true })}
|
|
270353
|
-
` };
|
|
270354
|
-
return {
|
|
270355
|
-
stdout: `${JSON.stringify({
|
|
270356
|
-
status: turn.status?.state ?? "ready",
|
|
270357
|
-
error_source: turn.status?.error_source ?? null,
|
|
270358
|
-
reply: lastAssistantReply(turn) ?? null
|
|
270359
|
-
})}
|
|
270360
|
-
`
|
|
270361
|
-
};
|
|
270362
|
-
}
|
|
270363
|
-
const stream = createTurnStream(process.stdout.isTTY === true);
|
|
270364
|
-
let turn;
|
|
270376
|
+
async function sendTurn(content, branchId) {
|
|
270377
|
+
let response;
|
|
270365
270378
|
try {
|
|
270366
|
-
|
|
270367
|
-
|
|
270368
|
-
|
|
270379
|
+
response = await getAppClient().post("chat/message", {
|
|
270380
|
+
timeout: false,
|
|
270381
|
+
searchParams: {
|
|
270382
|
+
conversation_messages: "current_turn",
|
|
270383
|
+
...branchScope(branchId)
|
|
270384
|
+
},
|
|
270385
|
+
json: { content }
|
|
270386
|
+
});
|
|
270387
|
+
} catch (error) {
|
|
270388
|
+
throw await ApiError.fromHttpError(error, "sending message");
|
|
270369
270389
|
}
|
|
270370
|
-
|
|
270371
|
-
|
|
270372
|
-
|
|
270373
|
-
|
|
270390
|
+
return parseOrThrow(ChatTurnSchema, await response.json(), "chat turn");
|
|
270391
|
+
}
|
|
270392
|
+
async function stopTurn(branchId) {
|
|
270393
|
+
try {
|
|
270394
|
+
await getAppClient().post("chat/stop", {
|
|
270395
|
+
searchParams: branchScope(branchId)
|
|
270396
|
+
});
|
|
270397
|
+
} catch (error) {
|
|
270398
|
+
throw await ApiError.fromHttpError(error, "stopping the turn");
|
|
270374
270399
|
}
|
|
270375
|
-
|
|
270376
|
-
|
|
270377
|
-
|
|
270378
|
-
|
|
270400
|
+
}
|
|
270401
|
+
async function getFullConversation(limit, branchId) {
|
|
270402
|
+
let response;
|
|
270403
|
+
try {
|
|
270404
|
+
response = await getAppClient().get("chat/full-conversation", {
|
|
270405
|
+
timeout: 30000,
|
|
270406
|
+
searchParams: { limit: String(limit), ...branchScope(branchId) }
|
|
270407
|
+
});
|
|
270408
|
+
} catch (error) {
|
|
270409
|
+
throw await ApiError.fromHttpError(error, "reading the conversation");
|
|
270379
270410
|
}
|
|
270380
|
-
return
|
|
270411
|
+
return parseOrThrow(FullConversationSchema, await response.json(), "conversation").messages;
|
|
270381
270412
|
}
|
|
270382
|
-
function
|
|
270383
|
-
const
|
|
270384
|
-
|
|
270385
|
-
return command;
|
|
270413
|
+
async function resolveActiveBranchId() {
|
|
270414
|
+
const branches = await listBranches();
|
|
270415
|
+
return branches.length === 1 ? branches[0].id : undefined;
|
|
270386
270416
|
}
|
|
270387
|
-
|
|
270388
|
-
|
|
270389
|
-
|
|
270390
|
-
|
|
270391
|
-
|
|
270392
|
-
|
|
270393
|
-
|
|
270394
|
-
|
|
270395
|
-
stdout: `${JSON.stringify({ id: app.id, state, message: app.status?.message ?? null })}
|
|
270396
|
-
`
|
|
270397
|
-
};
|
|
270417
|
+
async function getPreviewUrl() {
|
|
270418
|
+
let response;
|
|
270419
|
+
try {
|
|
270420
|
+
response = await getAppClient().get("sandbox/preview-url", {
|
|
270421
|
+
timeout: false
|
|
270422
|
+
});
|
|
270423
|
+
} catch (error) {
|
|
270424
|
+
throw await ApiError.fromHttpError(error, "fetching preview URL");
|
|
270398
270425
|
}
|
|
270399
|
-
|
|
270400
|
-
|
|
270401
|
-
ctx.log.message(`Note: ${app.status.message}`);
|
|
270402
|
-
return { outroMessage: "Status read." };
|
|
270403
|
-
}
|
|
270404
|
-
function getStatusCommand() {
|
|
270405
|
-
const command = new Base44Command("status");
|
|
270406
|
-
command.description("Show whether the app is building, ready, or errored").action(statusAction);
|
|
270407
|
-
return command;
|
|
270426
|
+
const url = parseOrThrow(PreviewUrlSchema, await response.json(), "preview URL").preview_url;
|
|
270427
|
+
return /^https?:\/\//.test(url) ? url : `https://${url}`;
|
|
270408
270428
|
}
|
|
270409
270429
|
|
|
270410
|
-
// src/cli/commands/
|
|
270411
|
-
|
|
270412
|
-
|
|
270413
|
-
|
|
270414
|
-
|
|
270415
|
-
|
|
270416
|
-
|
|
270417
|
-
|
|
270418
|
-
|
|
270419
|
-
function
|
|
270420
|
-
const
|
|
270421
|
-
|
|
270422
|
-
|
|
270430
|
+
// src/cli/commands/builder/shared.ts
|
|
270431
|
+
var APP_NAME_RE = /^[A-Za-z0-9._-]+$/;
|
|
270432
|
+
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(" "));
|
|
270433
|
+
var FALLBACK_WORDS = [
|
|
270434
|
+
"swift-otter",
|
|
270435
|
+
"sunny-comet",
|
|
270436
|
+
"tidy-maple",
|
|
270437
|
+
"brisk-panda"
|
|
270438
|
+
];
|
|
270439
|
+
function inventAppName(prompt) {
|
|
270440
|
+
const suffix = Math.random().toString(36).slice(2, 5);
|
|
270441
|
+
const words = (prompt ?? "").toLowerCase().match(/[a-z0-9]+/g)?.filter((w) => w.length > 2 && !NAME_STOPWORDS.has(w)).slice(0, 3) ?? [];
|
|
270442
|
+
const core = words.length ? words.join("-") : FALLBACK_WORDS[Math.floor(Math.random() * FALLBACK_WORDS.length)];
|
|
270443
|
+
return `base44-${core}-${suffix}`.slice(0, 60);
|
|
270423
270444
|
}
|
|
270424
|
-
|
|
270425
|
-
|
|
270426
|
-
function getAppCommand() {
|
|
270427
|
-
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());
|
|
270445
|
+
function repoBasename(repoUrl) {
|
|
270446
|
+
return repoUrl.replace(/\/+$/, "").replace(/\.git$/, "").split("/").pop() ?? "app";
|
|
270428
270447
|
}
|
|
270429
|
-
|
|
270430
|
-
|
|
270431
|
-
|
|
270432
|
-
async function passwordLoginAction({ log, runTask }, action) {
|
|
270433
|
-
const shouldEnable = action === "enable";
|
|
270434
|
-
const { project } = await readProjectConfig();
|
|
270435
|
-
const configDir = dirname15(project.configPath);
|
|
270436
|
-
const authDir = join22(configDir, project.authDir);
|
|
270437
|
-
const updated = await runTask("Updating local auth config", async () => {
|
|
270438
|
-
const current = await readAuthConfig(authDir) ?? DEFAULT_AUTH_CONFIG;
|
|
270439
|
-
const merged = { ...current, enableUsernamePassword: shouldEnable };
|
|
270440
|
-
await writeAuthConfig(authDir, merged);
|
|
270441
|
-
return merged;
|
|
270442
|
-
});
|
|
270443
|
-
if (!shouldEnable && !hasAnyLoginMethod(updated)) {
|
|
270444
|
-
log.warn("Disabling password auth will leave no login methods enabled. Users will be locked out.");
|
|
270448
|
+
async function createAndLinkApp(options) {
|
|
270449
|
+
if (options.name && !APP_NAME_RE.test(options.name)) {
|
|
270450
|
+
throw new InvalidInputError("The name becomes a directory — letters, digits, dots, dashes and underscores only.");
|
|
270445
270451
|
}
|
|
270446
|
-
const
|
|
270452
|
+
const cwd = process.cwd();
|
|
270453
|
+
const fallbackName = () => options.importRepo ? repoBasename(options.importRepo) : inventAppName(options.prompt);
|
|
270454
|
+
const chosenDir = options.path ? resolve8(cwd, options.path) : await isDirEmpty(cwd) ? cwd : undefined;
|
|
270455
|
+
const dirBase = chosenDir ? basename6(chosenDir) : undefined;
|
|
270456
|
+
const name = options.name ?? (dirBase && APP_NAME_RE.test(dirBase) ? dirBase : fallbackName());
|
|
270457
|
+
const targetDir = chosenDir ?? join25(cwd, name);
|
|
270458
|
+
const here = targetDir === cwd;
|
|
270459
|
+
const dirName = here ? "." : relative6(cwd, targetDir) || name;
|
|
270460
|
+
await mkdir3(targetDir, { recursive: true });
|
|
270461
|
+
if (await appConfigExists(targetDir)) {
|
|
270462
|
+
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.`);
|
|
270463
|
+
}
|
|
270464
|
+
const created = options.importRepo ? await createImportedApp({
|
|
270465
|
+
appName: name,
|
|
270466
|
+
repoUrl: options.importRepo,
|
|
270467
|
+
sourceMode: options.mode ?? "direct",
|
|
270468
|
+
newRepoName: options.repoName,
|
|
270469
|
+
branch: options.fromBranch,
|
|
270470
|
+
prompt: options.prompt
|
|
270471
|
+
}) : await createApp({ appName: name, prompt: options.prompt });
|
|
270472
|
+
await writeAppConfig(targetDir, created.id);
|
|
270473
|
+
await mkdir3(join25(targetDir, "base44"), { recursive: true });
|
|
270474
|
+
try {
|
|
270475
|
+
await writeFile2(join25(targetDir, "base44", "config.jsonc"), `// Base44 project configuration.
|
|
270476
|
+
{
|
|
270477
|
+
"name": ${JSON.stringify(name)}
|
|
270478
|
+
}
|
|
270479
|
+
`, { flag: "wx" });
|
|
270480
|
+
} catch {}
|
|
270481
|
+
setAppContext({ id: created.id, projectRoot: targetDir });
|
|
270447
270482
|
return {
|
|
270448
|
-
|
|
270449
|
-
|
|
270483
|
+
id: created.id,
|
|
270484
|
+
editorUrl: `${getBase44ApiUrl()}/apps/${created.id}/editor/preview`,
|
|
270485
|
+
repoUrl: created.imported_repo_url ?? undefined,
|
|
270486
|
+
dirName,
|
|
270487
|
+
targetDir,
|
|
270488
|
+
here
|
|
270489
|
+
};
|
|
270490
|
+
}
|
|
270491
|
+
function repoLabel(url) {
|
|
270492
|
+
return url.replace(/^https?:\/\//, "").replace(/\.git$/, "").replace(/\/$/, "");
|
|
270493
|
+
}
|
|
270494
|
+
function appTypeChip(state) {
|
|
270495
|
+
switch (state.app_type ?? "user_app") {
|
|
270496
|
+
case "imported_app":
|
|
270497
|
+
return state.imported_repo_url ? repoLabel(state.imported_repo_url) : "repository app";
|
|
270498
|
+
case "user_game":
|
|
270499
|
+
return "game";
|
|
270500
|
+
case "mobile_app":
|
|
270501
|
+
return "mobile app";
|
|
270502
|
+
case "slide":
|
|
270503
|
+
return "slides";
|
|
270504
|
+
case "user_agent":
|
|
270505
|
+
return "superagent";
|
|
270506
|
+
default:
|
|
270507
|
+
return "web app";
|
|
270508
|
+
}
|
|
270509
|
+
}
|
|
270510
|
+
async function assertBuilderApp(appId) {
|
|
270511
|
+
const state = await getAppState(appId);
|
|
270512
|
+
if (state.is_managed_source_code === false) {
|
|
270513
|
+
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.");
|
|
270514
|
+
}
|
|
270515
|
+
if (state.app_type === "user_agent") {
|
|
270516
|
+
throw new InvalidInputError("This is a Superagent, not an app the builder works on. Superagent has no CLI surface yet.");
|
|
270517
|
+
}
|
|
270518
|
+
return state;
|
|
270519
|
+
}
|
|
270520
|
+
function nextStepsLines(app) {
|
|
270521
|
+
const cd = app.here ? "" : `cd ${app.dirName} && `;
|
|
270522
|
+
return [
|
|
270523
|
+
"",
|
|
270524
|
+
`${source_default.bold("Your app lives in")} ${app.here ? "./ (this directory)" : `./${app.dirName}`}`,
|
|
270525
|
+
source_default.dim(` ${cd}base44 code # keep building with the agent`),
|
|
270526
|
+
source_default.dim(` ${cd}base44 builder send "…" # one non-interactive turn`),
|
|
270527
|
+
source_default.dim(` files live remotely — ${cd}base44 sandbox ls to look, base44 eject for a copy`)
|
|
270528
|
+
];
|
|
270529
|
+
}
|
|
270530
|
+
async function githubReauthLines(error) {
|
|
270531
|
+
if (!isGithubUserTokenError(error))
|
|
270532
|
+
return null;
|
|
270533
|
+
const link = await startGithubReauth().catch(() => null);
|
|
270534
|
+
return [
|
|
270535
|
+
"Your GitHub authorization expired. Reconnect, then run this again:",
|
|
270536
|
+
link ? terminalLink("Reconnect GitHub", link) : "Open Base44 → GitHub settings to reconnect your account."
|
|
270537
|
+
];
|
|
270450
270538
|
}
|
|
270451
|
-
function
|
|
270452
|
-
return
|
|
270539
|
+
async function resolveBranchId(ctx) {
|
|
270540
|
+
return ctx.branchId ?? await resolveActiveBranchId().catch(() => {
|
|
270541
|
+
return;
|
|
270542
|
+
});
|
|
270453
270543
|
}
|
|
270454
270544
|
|
|
270455
|
-
// src/
|
|
270456
|
-
|
|
270457
|
-
|
|
270458
|
-
log,
|
|
270459
|
-
runTask
|
|
270460
|
-
}) {
|
|
270461
|
-
const { project } = await readProjectConfig();
|
|
270462
|
-
const configDir = dirname16(project.configPath);
|
|
270463
|
-
const authDir = join23(configDir, project.authDir);
|
|
270464
|
-
const remoteConfig = await runTask("Fetching auth config from Base44", async () => {
|
|
270465
|
-
return await pullAuthConfig();
|
|
270466
|
-
}, {
|
|
270467
|
-
successMessage: "Auth config fetched successfully",
|
|
270468
|
-
errorMessage: "Failed to fetch auth config"
|
|
270469
|
-
});
|
|
270470
|
-
const { written } = await runTask("Syncing auth config file", async () => {
|
|
270471
|
-
return await writeAuthConfig(authDir, remoteConfig);
|
|
270472
|
-
}, {
|
|
270473
|
-
successMessage: "Auth config file synced successfully",
|
|
270474
|
-
errorMessage: "Failed to sync auth config file"
|
|
270475
|
-
});
|
|
270476
|
-
if (written) {
|
|
270477
|
-
log.success("Auth config written to local file");
|
|
270478
|
-
} else {
|
|
270479
|
-
log.info("Auth config is already up to date");
|
|
270480
|
-
}
|
|
270481
|
-
return {
|
|
270482
|
-
outroMessage: `Pulled auth config to ${authDir} (overwrites local file)`
|
|
270483
|
-
};
|
|
270545
|
+
// src/core/resources/apps/stream.ts
|
|
270546
|
+
function newStreamState() {
|
|
270547
|
+
return { perMessage: new Map };
|
|
270484
270548
|
}
|
|
270485
|
-
|
|
270486
|
-
|
|
270549
|
+
var TOOL_SETTLED = new Set(["success", "error", "stopped"]);
|
|
270550
|
+
function oneLine(value, max) {
|
|
270551
|
+
const text = typeof value === "string" ? value : value == null ? "" : JSON.stringify(value);
|
|
270552
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
270553
|
+
return flat.length > max ? `${flat.slice(0, max)}…` : flat;
|
|
270487
270554
|
}
|
|
270488
|
-
|
|
270489
|
-
|
|
270490
|
-
|
|
270491
|
-
|
|
270492
|
-
|
|
270493
|
-
|
|
270494
|
-
return {
|
|
270495
|
-
outroMessage: "No auth config to push. Run `base44 auth pull` to fetch the remote config first."
|
|
270496
|
-
};
|
|
270497
|
-
}
|
|
270498
|
-
if (!hasAnyLoginMethod(authConfig[0])) {
|
|
270499
|
-
log.warn("This config has no login methods enabled. Pushing it will lock out all users.");
|
|
270555
|
+
var SALIENT_KEYS = ["command", "path", "file_path", "title"];
|
|
270556
|
+
function salvageKey(raw, keys) {
|
|
270557
|
+
for (const key of keys) {
|
|
270558
|
+
const match = raw.match(new RegExp(`"${key}"\\s*:\\s*"((?:[^"\\\\]|\\\\.)*)"`));
|
|
270559
|
+
if (match?.[1])
|
|
270560
|
+
return match[1].replace(/\\(.)/g, "$1");
|
|
270500
270561
|
}
|
|
270501
|
-
|
|
270502
|
-
|
|
270503
|
-
|
|
270504
|
-
|
|
270505
|
-
|
|
270506
|
-
|
|
270507
|
-
|
|
270508
|
-
if (
|
|
270509
|
-
|
|
270562
|
+
return;
|
|
270563
|
+
}
|
|
270564
|
+
function toolMeta(name, argumentsString) {
|
|
270565
|
+
const raw = argumentsString ?? "";
|
|
270566
|
+
let args = {};
|
|
270567
|
+
try {
|
|
270568
|
+
const parsed = JSON.parse(raw);
|
|
270569
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
270570
|
+
args = parsed;
|
|
270510
270571
|
}
|
|
270572
|
+
} catch {
|
|
270573
|
+
return {
|
|
270574
|
+
label: oneLine(salvageKey(raw, ["summary"]) ?? "", 90),
|
|
270575
|
+
summary: oneLine(salvageKey(raw, SALIENT_KEYS) ?? raw, 90)
|
|
270576
|
+
};
|
|
270511
270577
|
}
|
|
270512
|
-
|
|
270513
|
-
|
|
270514
|
-
|
|
270515
|
-
|
|
270516
|
-
|
|
270517
|
-
|
|
270578
|
+
const pick = (key) => typeof args[key] === "string" && args[key].trim() ? args[key] : undefined;
|
|
270579
|
+
const salient = {
|
|
270580
|
+
run_shell_command: pick("command"),
|
|
270581
|
+
read_repo_file: pick("path") ?? pick("file_path"),
|
|
270582
|
+
write_repo_file: pick("path") ?? pick("file_path"),
|
|
270583
|
+
edit_repo_file: pick("path") ?? pick("file_path"),
|
|
270584
|
+
create_pull_request: pick("title"),
|
|
270585
|
+
reload_preview: ""
|
|
270586
|
+
};
|
|
270587
|
+
const summary = salient[name] ?? Object.entries(args).find(([key, v]) => key !== "summary" && typeof v === "string" && v.trim().length > 0)?.[1] ?? "";
|
|
270518
270588
|
return {
|
|
270519
|
-
|
|
270589
|
+
label: oneLine(pick("summary") ?? "", 90),
|
|
270590
|
+
summary: oneLine(summary, 90)
|
|
270520
270591
|
};
|
|
270521
270592
|
}
|
|
270522
|
-
function
|
|
270523
|
-
|
|
270593
|
+
function labelTense(label, tense) {
|
|
270594
|
+
const parts = label.split(/\s*\|\s*/);
|
|
270595
|
+
if (parts.length < 2)
|
|
270596
|
+
return label;
|
|
270597
|
+
return tense === "running" ? parts[0] : parts[1];
|
|
270524
270598
|
}
|
|
270525
|
-
|
|
270526
|
-
|
|
270527
|
-
|
|
270528
|
-
|
|
270529
|
-
|
|
270530
|
-
|
|
270531
|
-
|
|
270532
|
-
|
|
270533
|
-
|
|
270534
|
-
|
|
270535
|
-
|
|
270536
|
-
google: {
|
|
270537
|
-
envVar: "google_oauth_client_secret",
|
|
270538
|
-
promptMessage: "Enter Google OAuth client secret"
|
|
270599
|
+
function progressFor(state, id) {
|
|
270600
|
+
let progress = state.perMessage.get(id);
|
|
270601
|
+
if (!progress) {
|
|
270602
|
+
progress = {
|
|
270603
|
+
contentLength: 0,
|
|
270604
|
+
reasoningLength: 0,
|
|
270605
|
+
announcedTools: new Map,
|
|
270606
|
+
settledTools: new Set,
|
|
270607
|
+
waitingNotified: new Set
|
|
270608
|
+
};
|
|
270609
|
+
state.perMessage.set(id, progress);
|
|
270539
270610
|
}
|
|
270540
|
-
|
|
270541
|
-
function hasSecretOptions(options) {
|
|
270542
|
-
return Boolean(options.clientSecret || options.clientSecretStdin || options.envFile);
|
|
270543
|
-
}
|
|
270544
|
-
function hasCustomOAuthOptions(options) {
|
|
270545
|
-
return Boolean(options.clientId || hasSecretOptions(options));
|
|
270611
|
+
return progress;
|
|
270546
270612
|
}
|
|
270547
|
-
|
|
270548
|
-
const
|
|
270549
|
-
const
|
|
270550
|
-
|
|
270551
|
-
|
|
270552
|
-
|
|
270553
|
-
|
|
270554
|
-
|
|
270555
|
-
|
|
270556
|
-
|
|
270557
|
-
|
|
270558
|
-
|
|
270559
|
-
|
|
270560
|
-
|
|
270561
|
-
|
|
270562
|
-
|
|
270563
|
-
|
|
270564
|
-
|
|
270565
|
-
|
|
270566
|
-
|
|
270567
|
-
|
|
270568
|
-
|
|
270569
|
-
|
|
270570
|
-
|
|
270571
|
-
|
|
270572
|
-
|
|
270573
|
-
|
|
270574
|
-
|
|
270575
|
-
|
|
270576
|
-
|
|
270577
|
-
|
|
270578
|
-
|
|
270579
|
-
|
|
270580
|
-
|
|
270581
|
-
|
|
270582
|
-
|
|
270583
|
-
|
|
270584
|
-
|
|
270585
|
-
|
|
270586
|
-
|
|
270587
|
-
|
|
270588
|
-
|
|
270589
|
-
|
|
270590
|
-
|
|
270591
|
-
|
|
270592
|
-
|
|
270593
|
-
|
|
270613
|
+
function diffConversation(state, messages) {
|
|
270614
|
+
const events = [];
|
|
270615
|
+
for (const message of messages) {
|
|
270616
|
+
if (message.role !== "assistant" || message.hidden)
|
|
270617
|
+
continue;
|
|
270618
|
+
const progress = progressFor(state, message.id);
|
|
270619
|
+
const reasoning = message.reasoning?.content ?? "";
|
|
270620
|
+
if (reasoning.length > progress.reasoningLength) {
|
|
270621
|
+
const delta = reasoning.slice(progress.reasoningLength).trim();
|
|
270622
|
+
if (delta)
|
|
270623
|
+
events.push({ kind: "thinking", text: oneLine(delta, 300) });
|
|
270624
|
+
progress.reasoningLength = reasoning.length;
|
|
270625
|
+
}
|
|
270626
|
+
if (typeof message.content === "string" && message.content.length > progress.contentLength) {
|
|
270627
|
+
const delta = message.content.slice(progress.contentLength).trim();
|
|
270628
|
+
if (delta)
|
|
270629
|
+
events.push({ kind: "text", text: delta });
|
|
270630
|
+
progress.contentLength = message.content.length;
|
|
270631
|
+
}
|
|
270632
|
+
for (const tool of message.tool_calls ?? []) {
|
|
270633
|
+
if (!progress.announcedTools.has(tool.id)) {
|
|
270634
|
+
progress.announcedTools.set(tool.id, toolMeta(tool.name, tool.arguments_string));
|
|
270635
|
+
const meta = progress.announcedTools.get(tool.id);
|
|
270636
|
+
events.push({
|
|
270637
|
+
kind: "tool_start",
|
|
270638
|
+
id: tool.id,
|
|
270639
|
+
name: tool.name,
|
|
270640
|
+
label: labelTense(meta.label, "running"),
|
|
270641
|
+
summary: meta.summary
|
|
270642
|
+
});
|
|
270643
|
+
}
|
|
270644
|
+
const status = tool.status ?? "running";
|
|
270645
|
+
if (status === "waiting_for_user_input" && !progress.waitingNotified.has(tool.id)) {
|
|
270646
|
+
progress.waitingNotified.add(tool.id);
|
|
270647
|
+
const meta = progress.announcedTools.get(tool.id);
|
|
270648
|
+
events.push({
|
|
270649
|
+
kind: "waiting",
|
|
270650
|
+
id: tool.id,
|
|
270651
|
+
name: tool.name,
|
|
270652
|
+
label: labelTense(meta.label, "running")
|
|
270653
|
+
});
|
|
270654
|
+
}
|
|
270655
|
+
if (TOOL_SETTLED.has(status) && !progress.settledTools.has(tool.id)) {
|
|
270656
|
+
progress.settledTools.add(tool.id);
|
|
270657
|
+
const meta = progress.announcedTools.get(tool.id);
|
|
270658
|
+
events.push({
|
|
270659
|
+
kind: "tool_end",
|
|
270660
|
+
id: tool.id,
|
|
270661
|
+
name: tool.name,
|
|
270662
|
+
label: labelTense(meta.label, "done"),
|
|
270663
|
+
summary: meta.summary,
|
|
270664
|
+
ok: status === "success",
|
|
270665
|
+
result: oneLine(tool.results, 110)
|
|
270666
|
+
});
|
|
270667
|
+
}
|
|
270594
270668
|
}
|
|
270595
270669
|
}
|
|
270596
|
-
|
|
270597
|
-
const configDir = dirname17(project.configPath);
|
|
270598
|
-
const authDir = join24(configDir, project.authDir);
|
|
270599
|
-
const { config: updated } = await runTask("Updating local auth config", async () => updateSocialLoginConfig(authDir, provider, shouldEnable, useCustomOAuth && options.clientId ? { clientId: options.clientId } : undefined));
|
|
270600
|
-
if (clientSecret) {
|
|
270601
|
-
await runTask("Saving client secret", async () => pushCustomOAuthSecret(provider, clientSecret));
|
|
270602
|
-
}
|
|
270603
|
-
if (!shouldEnable && !hasAnyLoginMethod(updated)) {
|
|
270604
|
-
log.warn(`Disabling ${label} login will leave no login methods enabled. Users will be locked out.`);
|
|
270605
|
-
}
|
|
270606
|
-
const newStatus = shouldEnable ? "enabled" : "disabled";
|
|
270607
|
-
const oauthNote = useCustomOAuth ? " with custom OAuth" : "";
|
|
270608
|
-
let outroMessage = `${label} login ${newStatus}${oauthNote} in local config. Run \`base44 auth push\` or \`base44 deploy\` to apply.`;
|
|
270609
|
-
if (useCustomOAuth && !clientSecret) {
|
|
270610
|
-
outroMessage += `
|
|
270611
|
-
Remember to push the client secret separately: base44 secrets set --env-file <path>`;
|
|
270612
|
-
}
|
|
270613
|
-
return { outroMessage };
|
|
270670
|
+
return events;
|
|
270614
270671
|
}
|
|
270615
|
-
function
|
|
270616
|
-
return
|
|
270617
|
-
"enable",
|
|
270618
|
-
"disable"
|
|
270619
|
-
])).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);
|
|
270672
|
+
function turnSettled(messages) {
|
|
270673
|
+
return newestUserTurn(messages)?.settled ?? false;
|
|
270620
270674
|
}
|
|
270621
|
-
|
|
270622
|
-
|
|
270623
|
-
|
|
270624
|
-
|
|
270625
|
-
|
|
270626
|
-
|
|
270627
|
-
|
|
270628
|
-
|
|
270629
|
-
|
|
270630
|
-
|
|
270631
|
-
|
|
270632
|
-
|
|
270633
|
-
tokenEndpoint: string2().optional(),
|
|
270634
|
-
userinfoEndpoint: string2().optional(),
|
|
270635
|
-
jwksUri: string2().optional(),
|
|
270636
|
-
ssoName: string2().optional()
|
|
270637
|
-
});
|
|
270638
|
-
async function loadSSOConfigFile(filePath) {
|
|
270639
|
-
const resolved = resolve7(filePath);
|
|
270640
|
-
const raw = await readJsonFile(resolved);
|
|
270641
|
-
const result = SSOConfigFileSchema.safeParse(raw);
|
|
270642
|
-
if (!result.success) {
|
|
270643
|
-
throw new SchemaValidationError("Invalid SSO config file", result.error, filePath);
|
|
270675
|
+
function newestUserTurn(messages) {
|
|
270676
|
+
for (let i = messages.length - 1;i >= 0; i--) {
|
|
270677
|
+
const message = messages[i];
|
|
270678
|
+
if (message.role === "user" && !message.hidden) {
|
|
270679
|
+
const outcome = message.outcome;
|
|
270680
|
+
const backendStatus = outcome && typeof outcome === "object" ? outcome.backend_status : undefined;
|
|
270681
|
+
return {
|
|
270682
|
+
id: message.id,
|
|
270683
|
+
settled: outcome != null && backendStatus !== "pending",
|
|
270684
|
+
backendStatus
|
|
270685
|
+
};
|
|
270686
|
+
}
|
|
270644
270687
|
}
|
|
270645
|
-
return
|
|
270688
|
+
return null;
|
|
270646
270689
|
}
|
|
270647
|
-
|
|
270648
|
-
|
|
270649
|
-
|
|
270650
|
-
|
|
270651
|
-
|
|
270652
|
-
|
|
270653
|
-
|
|
270654
|
-
|
|
270655
|
-
|
|
270656
|
-
|
|
270657
|
-
|
|
270658
|
-
|
|
270659
|
-
|
|
270660
|
-
|
|
270661
|
-
jwksUri: options.jwksUri ?? fileConfig.jwksUri,
|
|
270662
|
-
ssoName: options.ssoName ?? fileConfig.ssoName
|
|
270690
|
+
var sleep3 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
270691
|
+
function makePoller(onEvent, options) {
|
|
270692
|
+
const state = newStreamState();
|
|
270693
|
+
return async (prime = false) => {
|
|
270694
|
+
try {
|
|
270695
|
+
const messages = await getFullConversation(30, options.branchId);
|
|
270696
|
+
const events = diffConversation(state, messages);
|
|
270697
|
+
if (!prime)
|
|
270698
|
+
for (const event of events)
|
|
270699
|
+
onEvent(event);
|
|
270700
|
+
return messages;
|
|
270701
|
+
} catch {
|
|
270702
|
+
return [];
|
|
270703
|
+
}
|
|
270663
270704
|
};
|
|
270664
270705
|
}
|
|
270665
|
-
|
|
270666
|
-
|
|
270667
|
-
|
|
270668
|
-
|
|
270669
|
-
|
|
270670
|
-
|
|
270671
|
-
|
|
270672
|
-
|
|
270673
|
-
|
|
270674
|
-
|
|
270675
|
-
|
|
270676
|
-
|
|
270677
|
-
|
|
270678
|
-
|
|
270679
|
-
|
|
270680
|
-
|
|
270681
|
-
}
|
|
270682
|
-
function exampleCommand(provider) {
|
|
270683
|
-
let cmd = `base44 auth sso enable --provider ${provider} --client-id <id> --client-secret <secret>`;
|
|
270684
|
-
if (provider === KNOWN_SSO_PROVIDERS.microsoft)
|
|
270685
|
-
cmd += " --tenant-id <id>";
|
|
270686
|
-
if (provider === KNOWN_SSO_PROVIDERS.okta)
|
|
270687
|
-
cmd += " --okta-domain <domain>";
|
|
270688
|
-
if (provider === KNOWN_SSO_PROVIDERS.custom)
|
|
270689
|
-
cmd += " --sso-name <name> --auth-endpoint <url> --token-endpoint <url> --userinfo-endpoint <url> --jwks-uri <url>";
|
|
270690
|
-
return cmd;
|
|
270691
|
-
}
|
|
270692
|
-
function validateProvider(provider) {
|
|
270693
|
-
if (!provider) {
|
|
270694
|
-
throw new InvalidInputError("Missing --provider.", {
|
|
270695
|
-
hints: [
|
|
270696
|
-
{
|
|
270697
|
-
message: `Valid providers: ${providerNames.join(", ")}`,
|
|
270698
|
-
command: "base44 auth sso enable --provider <provider> --client-id <id> --client-secret <secret>"
|
|
270699
|
-
}
|
|
270700
|
-
]
|
|
270701
|
-
});
|
|
270706
|
+
async function streamConversationDuring(start, onEvent, options = {}) {
|
|
270707
|
+
const intervalMs = options.intervalMs ?? 1000;
|
|
270708
|
+
const poll = makePoller(onEvent, options);
|
|
270709
|
+
await poll(true);
|
|
270710
|
+
const work = start();
|
|
270711
|
+
let pending = true;
|
|
270712
|
+
const settled = work.then(() => {
|
|
270713
|
+
pending = false;
|
|
270714
|
+
}, () => {
|
|
270715
|
+
pending = false;
|
|
270716
|
+
});
|
|
270717
|
+
while (pending) {
|
|
270718
|
+
await Promise.race([sleep3(intervalMs), settled]);
|
|
270719
|
+
if (!pending)
|
|
270720
|
+
break;
|
|
270721
|
+
await poll();
|
|
270702
270722
|
}
|
|
270703
|
-
|
|
270723
|
+
await poll();
|
|
270724
|
+
return work;
|
|
270704
270725
|
}
|
|
270705
|
-
async function
|
|
270706
|
-
|
|
270707
|
-
|
|
270726
|
+
async function streamConversationUntilSettled(onEvent, options = {}) {
|
|
270727
|
+
const intervalMs = options.intervalMs ?? 1000;
|
|
270728
|
+
const deadline = Date.now() + (options.timeoutMs ?? 20 * 60000);
|
|
270729
|
+
const poll = makePoller(onEvent, options);
|
|
270730
|
+
while (Date.now() < deadline) {
|
|
270731
|
+
const messages = await poll();
|
|
270732
|
+
if (messages.length > 0 && turnSettled(messages))
|
|
270733
|
+
return "settled";
|
|
270734
|
+
await sleep3(intervalMs);
|
|
270708
270735
|
}
|
|
270709
|
-
|
|
270710
|
-
|
|
270711
|
-
|
|
270712
|
-
|
|
270736
|
+
return "timeout";
|
|
270737
|
+
}
|
|
270738
|
+
|
|
270739
|
+
// src/cli/commands/builder/new.ts
|
|
270740
|
+
var POLL_TIMEOUT_MS = 20 * 60000;
|
|
270741
|
+
var MODES = ["direct", "fork", "copy"];
|
|
270742
|
+
async function newAction({ log, runTask, jsonMode }, prompt, options) {
|
|
270743
|
+
if (options.mode && !MODES.includes(options.mode)) {
|
|
270744
|
+
throw new InvalidInputError("--mode must be direct, fork, or copy.");
|
|
270713
270745
|
}
|
|
270714
|
-
|
|
270715
|
-
|
|
270716
|
-
throw new InvalidInputError("Missing --client-id.", {
|
|
270717
|
-
hints: [
|
|
270718
|
-
{
|
|
270719
|
-
message: `Example: base44 auth sso enable --provider ${provider} --client-id <id> --client-secret <secret>`,
|
|
270720
|
-
command: `base44 auth sso enable --provider ${provider} --client-id <id> --client-secret <secret>`
|
|
270721
|
-
}
|
|
270722
|
-
]
|
|
270723
|
-
});
|
|
270746
|
+
if ((options.mode || options.repoName || options.fromBranch) && !options.import) {
|
|
270747
|
+
throw new InvalidInputError("--mode, --repo-name and --from-branch apply only with --import <repo>.");
|
|
270724
270748
|
}
|
|
270725
|
-
|
|
270726
|
-
|
|
270727
|
-
const secrets = await parseEnvFile(resolve7(merged.envFile));
|
|
270728
|
-
const value = secrets.sso_client_secret;
|
|
270729
|
-
if (!value) {
|
|
270730
|
-
throw new InvalidInputError(`Key "sso_client_secret" not found in ${merged.envFile}.`);
|
|
270731
|
-
}
|
|
270732
|
-
clientSecret = value;
|
|
270733
|
-
} else {
|
|
270734
|
-
clientSecret = await resolveSecret({
|
|
270735
|
-
flagValue: merged.clientSecret,
|
|
270736
|
-
fromStdin: merged.clientSecretStdin,
|
|
270737
|
-
envVar: "sso_client_secret",
|
|
270738
|
-
promptMessage: "Enter SSO client secret",
|
|
270739
|
-
isNonInteractive,
|
|
270740
|
-
name: "client secret",
|
|
270741
|
-
hints: [
|
|
270742
|
-
{
|
|
270743
|
-
message: `Provide via flag: base44 auth sso enable --provider ${provider} --client-id <id> --client-secret <secret>`,
|
|
270744
|
-
command: `base44 auth sso enable --provider ${provider} --client-id <id> --client-secret <secret>`
|
|
270745
|
-
},
|
|
270746
|
-
{
|
|
270747
|
-
message: `Provide via stdin: echo <secret> | base44 auth sso enable --provider ${provider} --client-id <id> --client-secret-stdin`
|
|
270748
|
-
},
|
|
270749
|
-
{
|
|
270750
|
-
message: `Provide via env: sso_client_secret=<secret> base44 auth sso enable --provider ${provider} --client-id <id>`
|
|
270751
|
-
}
|
|
270752
|
-
]
|
|
270753
|
-
});
|
|
270749
|
+
if (!prompt && !options.import) {
|
|
270750
|
+
throw new InvalidInputError('Describe the app ("<prompt>") or pass --import <repo>.');
|
|
270754
270751
|
}
|
|
270755
|
-
|
|
270756
|
-
clientId: merged.clientId,
|
|
270757
|
-
clientSecret,
|
|
270758
|
-
scope: merged.scope,
|
|
270759
|
-
discoveryUrl: merged.discoveryUrl,
|
|
270760
|
-
tenantId: merged.tenantId,
|
|
270761
|
-
oktaDomain: merged.oktaDomain,
|
|
270762
|
-
authEndpoint: merged.authEndpoint,
|
|
270763
|
-
tokenEndpoint: merged.tokenEndpoint,
|
|
270764
|
-
userinfoEndpoint: merged.userinfoEndpoint,
|
|
270765
|
-
jwksUri: merged.jwksUri,
|
|
270766
|
-
ssoName: merged.ssoName
|
|
270767
|
-
};
|
|
270768
|
-
let secrets;
|
|
270752
|
+
let app;
|
|
270769
270753
|
try {
|
|
270770
|
-
|
|
270754
|
+
app = await runTask(options.import ? "Importing the repository" : "Creating your app", () => createAndLinkApp({
|
|
270755
|
+
prompt,
|
|
270756
|
+
name: options.name,
|
|
270757
|
+
importRepo: options.import,
|
|
270758
|
+
mode: options.mode,
|
|
270759
|
+
repoName: options.repoName,
|
|
270760
|
+
fromBranch: options.fromBranch,
|
|
270761
|
+
path: options.path
|
|
270762
|
+
}));
|
|
270771
270763
|
} catch (error) {
|
|
270772
|
-
|
|
270773
|
-
|
|
270774
|
-
|
|
270775
|
-
|
|
270776
|
-
|
|
270777
|
-
|
|
270778
|
-
|
|
270779
|
-
|
|
270780
|
-
|
|
270781
|
-
|
|
270764
|
+
for (const line of await githubReauthLines(error) ?? [])
|
|
270765
|
+
log.message(line);
|
|
270766
|
+
throw error;
|
|
270767
|
+
}
|
|
270768
|
+
if (!jsonMode) {
|
|
270769
|
+
if (app.repoUrl)
|
|
270770
|
+
log.message(source_default.dim(`repo ${app.repoUrl}`));
|
|
270771
|
+
log.message(source_default.dim(`editor ${app.editorUrl}`));
|
|
270772
|
+
log.message(source_default.dim(`linked ${app.here ? "./ (this directory)" : `./${app.dirName}`}`));
|
|
270773
|
+
}
|
|
270774
|
+
let finalState;
|
|
270775
|
+
let previewUrl;
|
|
270776
|
+
const startedAt = Date.now();
|
|
270777
|
+
if (prompt) {
|
|
270778
|
+
const branchId = await resolveActiveBranchId().catch(() => {
|
|
270779
|
+
return;
|
|
270780
|
+
});
|
|
270781
|
+
const stream = createTurnStream(process.stdout.isTTY === true && !jsonMode, undefined, { idleLabel: "provisioning the sandbox and starting the build" });
|
|
270782
|
+
try {
|
|
270783
|
+
const settled = await streamConversationUntilSettled((event) => {
|
|
270784
|
+
if (!jsonMode)
|
|
270785
|
+
stream.onEvent(event);
|
|
270786
|
+
}, { branchId, timeoutMs: POLL_TIMEOUT_MS });
|
|
270787
|
+
finalState = settled === "timeout" ? "processing" : (await getAppState(app.id)).status?.state ?? "ready";
|
|
270788
|
+
if (finalState === "ready") {
|
|
270789
|
+
previewUrl = await getPreviewUrl().catch(() => {
|
|
270790
|
+
return;
|
|
270791
|
+
});
|
|
270792
|
+
}
|
|
270793
|
+
} finally {
|
|
270794
|
+
stream.stop();
|
|
270782
270795
|
}
|
|
270783
|
-
throw error;
|
|
270784
270796
|
}
|
|
270785
|
-
|
|
270786
|
-
|
|
270787
|
-
|
|
270788
|
-
|
|
270789
|
-
|
|
270790
|
-
|
|
270791
|
-
|
|
270792
|
-
|
|
270793
|
-
|
|
270794
|
-
|
|
270795
|
-
|
|
270796
|
-
|
|
270797
|
-
|
|
270798
|
-
if (hasEnableOnlyOptions(options)) {
|
|
270799
|
-
throw new InvalidInputError("Configuration options cannot be used with disable. To disable SSO: base44 auth sso disable");
|
|
270797
|
+
if (jsonMode) {
|
|
270798
|
+
return {
|
|
270799
|
+
stdout: `${JSON.stringify({
|
|
270800
|
+
id: app.id,
|
|
270801
|
+
repo_url: app.repoUrl ?? null,
|
|
270802
|
+
editor_url: app.editorUrl,
|
|
270803
|
+
preview_url: previewUrl ?? null,
|
|
270804
|
+
dir: app.dirName,
|
|
270805
|
+
path: app.targetDir,
|
|
270806
|
+
status: finalState ?? "created"
|
|
270807
|
+
})}
|
|
270808
|
+
`
|
|
270809
|
+
};
|
|
270800
270810
|
}
|
|
270801
|
-
|
|
270802
|
-
|
|
270803
|
-
const
|
|
270804
|
-
|
|
270805
|
-
|
|
270806
|
-
|
|
270807
|
-
|
|
270811
|
+
if (previewUrl)
|
|
270812
|
+
log.message(`preview ${previewUrl}`);
|
|
270813
|
+
for (const line of nextStepsLines(app))
|
|
270814
|
+
log.message(line);
|
|
270815
|
+
if (finalState === "error") {
|
|
270816
|
+
return {
|
|
270817
|
+
outroMessage: `The first build reported an error — open the editor for details.`
|
|
270818
|
+
};
|
|
270819
|
+
}
|
|
270820
|
+
if (finalState === "processing") {
|
|
270821
|
+
return {
|
|
270822
|
+
outroMessage: `Still building — follow it with \`base44 builder status\`.`
|
|
270823
|
+
};
|
|
270808
270824
|
}
|
|
270809
270825
|
return {
|
|
270810
|
-
outroMessage:
|
|
270826
|
+
outroMessage: prompt ? `First build finished · ${formatDuration2(Date.now() - startedAt)}.` : "App created."
|
|
270811
270827
|
};
|
|
270812
270828
|
}
|
|
270813
|
-
|
|
270814
|
-
|
|
270815
|
-
|
|
270816
|
-
|
|
270817
|
-
return ssoEnableAction(context, options);
|
|
270818
|
-
}
|
|
270819
|
-
function getSSOCommand() {
|
|
270820
|
-
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([
|
|
270821
|
-
"enable",
|
|
270822
|
-
"disable"
|
|
270823
|
-
])).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);
|
|
270824
|
-
}
|
|
270825
|
-
|
|
270826
|
-
// src/cli/commands/auth/index.ts
|
|
270827
|
-
function getAuthCommand() {
|
|
270828
|
-
return new Command2("auth").description("Manage app authentication settings").addCommand(getPasswordLoginCommand()).addCommand(getSocialLoginCommand()).addCommand(getSSOCommand()).addCommand(getAuthPullCommand()).addCommand(getAuthPushCommand());
|
|
270829
|
+
function getNewCommand() {
|
|
270830
|
+
const command = new Base44Command("new", { requireAppContext: false });
|
|
270831
|
+
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").action(newAction);
|
|
270832
|
+
return command;
|
|
270829
270833
|
}
|
|
270830
270834
|
|
|
270831
|
-
// src/cli/commands/
|
|
270832
|
-
function
|
|
270833
|
-
|
|
270834
|
-
|
|
270835
|
-
|
|
270836
|
-
|
|
270835
|
+
// src/cli/commands/builder/send.ts
|
|
270836
|
+
function lastAssistantReply(turn) {
|
|
270837
|
+
const messages = turn.conversation?.messages ?? [];
|
|
270838
|
+
for (let i = messages.length - 1;i >= 0; i--) {
|
|
270839
|
+
const { role, content } = messages[i];
|
|
270840
|
+
if (role === "assistant" && typeof content === "string" && content.trim()) {
|
|
270841
|
+
return content.trim();
|
|
270842
|
+
}
|
|
270843
|
+
}
|
|
270844
|
+
return;
|
|
270837
270845
|
}
|
|
270838
|
-
|
|
270839
|
-
|
|
270840
|
-
|
|
270841
|
-
await
|
|
270842
|
-
|
|
270846
|
+
async function sendAction(ctx, message) {
|
|
270847
|
+
if (ctx.app)
|
|
270848
|
+
await assertBuilderApp(ctx.app.id);
|
|
270849
|
+
const branchId = await resolveBranchId(ctx);
|
|
270850
|
+
if (ctx.jsonMode) {
|
|
270851
|
+
const turn = await ctx.runTask("Agent working (a turn can take minutes)", () => sendTurn(message, branchId));
|
|
270852
|
+
if (turn.queued)
|
|
270853
|
+
return { stdout: `${JSON.stringify({ queued: true })}
|
|
270854
|
+
` };
|
|
270855
|
+
return {
|
|
270856
|
+
stdout: `${JSON.stringify({
|
|
270857
|
+
status: turn.status?.state ?? "ready",
|
|
270858
|
+
error_source: turn.status?.error_source ?? null,
|
|
270859
|
+
reply: lastAssistantReply(turn) ?? null
|
|
270860
|
+
})}
|
|
270861
|
+
`
|
|
270862
|
+
};
|
|
270863
|
+
}
|
|
270864
|
+
const stream = createTurnStream(process.stdout.isTTY === true);
|
|
270865
|
+
let turn;
|
|
270866
|
+
try {
|
|
270867
|
+
turn = await streamConversationDuring(() => sendTurn(message, branchId), stream.onEvent, { branchId });
|
|
270868
|
+
} finally {
|
|
270869
|
+
stream.stop();
|
|
270870
|
+
}
|
|
270871
|
+
if (turn.queued) {
|
|
270872
|
+
return {
|
|
270873
|
+
outroMessage: "The agent is busy with an earlier message — yours was queued and runs next."
|
|
270874
|
+
};
|
|
270875
|
+
}
|
|
270876
|
+
if (turn.status?.state === "error") {
|
|
270877
|
+
return {
|
|
270878
|
+
outroMessage: `Turn failed (${turn.status.error_source ?? "unknown"}) — see the editor for details.`
|
|
270879
|
+
};
|
|
270880
|
+
}
|
|
270881
|
+
return { outroMessage: "Turn finished." };
|
|
270843
270882
|
}
|
|
270844
|
-
function
|
|
270845
|
-
|
|
270846
|
-
|
|
270847
|
-
|
|
270848
|
-
}).description("Logout from current device").action(logout);
|
|
270883
|
+
function getSendCommand() {
|
|
270884
|
+
const command = new Base44Command("send", { supportsBranch: true });
|
|
270885
|
+
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);
|
|
270886
|
+
return command;
|
|
270849
270887
|
}
|
|
270850
270888
|
|
|
270851
|
-
// src/cli/commands/
|
|
270852
|
-
async function
|
|
270853
|
-
const
|
|
270854
|
-
|
|
270889
|
+
// src/cli/commands/builder/status.ts
|
|
270890
|
+
async function statusAction(ctx) {
|
|
270891
|
+
const id = ctx.app?.id;
|
|
270892
|
+
const app = await ctx.runTask("Reading app status", () => getAppState(id));
|
|
270893
|
+
const state = app.status?.state ?? "ready";
|
|
270894
|
+
if (ctx.jsonMode) {
|
|
270855
270895
|
return {
|
|
270856
|
-
|
|
270896
|
+
stdout: `${JSON.stringify({ id: app.id, state, message: app.status?.message ?? null })}
|
|
270897
|
+
`
|
|
270857
270898
|
};
|
|
270858
270899
|
}
|
|
270859
|
-
|
|
270860
|
-
|
|
270900
|
+
ctx.log.message(`State: ${state}`);
|
|
270901
|
+
if (app.status?.message)
|
|
270902
|
+
ctx.log.message(`Note: ${app.status.message}`);
|
|
270903
|
+
return { outroMessage: "Status read." };
|
|
270861
270904
|
}
|
|
270862
|
-
function
|
|
270863
|
-
|
|
270905
|
+
function getStatusCommand() {
|
|
270906
|
+
const command = new Base44Command("status");
|
|
270907
|
+
command.description("Show whether the app is building, ready, or errored").action(statusAction);
|
|
270908
|
+
return command;
|
|
270864
270909
|
}
|
|
270865
270910
|
|
|
270866
|
-
// src/cli/commands/
|
|
270867
|
-
async function
|
|
270868
|
-
|
|
270869
|
-
runTask,
|
|
270870
|
-
jsonMode
|
|
270871
|
-
})
|
|
270872
|
-
const remote = await runTask("Fetching branches", () => listBranches());
|
|
270873
|
-
const branches = [
|
|
270874
|
-
{ name: "main", status: "active" },
|
|
270875
|
-
...remote.map((branch) => ({
|
|
270876
|
-
name: branch.branch_name,
|
|
270877
|
-
status: branch.status
|
|
270878
|
-
}))
|
|
270879
|
-
];
|
|
270880
|
-
if (jsonMode)
|
|
270881
|
-
return { stdout: `${JSON.stringify({ branches })}
|
|
270911
|
+
// src/cli/commands/builder/stop.ts
|
|
270912
|
+
async function stopAction(ctx) {
|
|
270913
|
+
const branchId = await resolveBranchId(ctx);
|
|
270914
|
+
await ctx.runTask("Stopping the running turn", () => stopTurn(branchId));
|
|
270915
|
+
if (ctx.jsonMode)
|
|
270916
|
+
return { stdout: `${JSON.stringify({ stopped: true })}
|
|
270882
270917
|
` };
|
|
270883
|
-
|
|
270884
|
-
log.message(`${branch.name} (${branch.status})`);
|
|
270885
|
-
return { outroMessage: `${branches.length} branches` };
|
|
270918
|
+
return { outroMessage: "Stopped." };
|
|
270886
270919
|
}
|
|
270887
|
-
function
|
|
270888
|
-
|
|
270920
|
+
function getStopCommand() {
|
|
270921
|
+
const command = new Base44Command("stop", { supportsBranch: true });
|
|
270922
|
+
command.description("Stop the agent's running turn").action(stopAction);
|
|
270923
|
+
return command;
|
|
270924
|
+
}
|
|
270925
|
+
|
|
270926
|
+
// src/cli/commands/builder/index.ts
|
|
270927
|
+
function getBuilderCommand() {
|
|
270928
|
+
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());
|
|
270889
270929
|
}
|
|
270890
270930
|
|
|
270891
270931
|
// ../../node_modules/ink/build/render.js
|
|
@@ -275851,6 +275891,119 @@ var build_default = TextInput;
|
|
|
275851
275891
|
// src/cli/commands/code/session.tsx
|
|
275852
275892
|
var import_react23 = __toESM(require_react(), 1);
|
|
275853
275893
|
|
|
275894
|
+
// src/cli/commands/code/logo.ts
|
|
275895
|
+
var ROWS = 6;
|
|
275896
|
+
var ASPECT = 0.44;
|
|
275897
|
+
var SAMPLES = 4;
|
|
275898
|
+
var FILL = 0.6;
|
|
275899
|
+
var GAP_ROW = 3.5 + 0.5;
|
|
275900
|
+
var GAP_HEIGHT_ROWS = 0.8;
|
|
275901
|
+
var LOGO_COLS = Math.max(2, Math.floor(ROWS / ASPECT + 0.5));
|
|
275902
|
+
var OCTANT = {
|
|
275903
|
+
rx: 2,
|
|
275904
|
+
ry: 4,
|
|
275905
|
+
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█")
|
|
275906
|
+
};
|
|
275907
|
+
var QUAD = {
|
|
275908
|
+
rx: 2,
|
|
275909
|
+
ry: 2,
|
|
275910
|
+
table: Array.from(" ▘▝▀▖▌▞▛▗▚▐▜▄▙▟█")
|
|
275911
|
+
};
|
|
275912
|
+
function detectTier(env = process.env) {
|
|
275913
|
+
const term = env.TERM ?? "";
|
|
275914
|
+
const prog = env.TERM_PROGRAM ?? "";
|
|
275915
|
+
if (prog === "ghostty" || term.includes("ghostty"))
|
|
275916
|
+
return "octant";
|
|
275917
|
+
if (env.KITTY_WINDOW_ID || term.includes("kitty"))
|
|
275918
|
+
return "octant";
|
|
275919
|
+
if (env.WEZTERM_PANE || env.WEZTERM_EXECUTABLE)
|
|
275920
|
+
return "octant";
|
|
275921
|
+
if (term.startsWith("foot") || prog === "contour")
|
|
275922
|
+
return "octant";
|
|
275923
|
+
return "quad";
|
|
275924
|
+
}
|
|
275925
|
+
function coverageGrid(rows, cols, aspect, pixelH) {
|
|
275926
|
+
const worldH = rows * pixelH;
|
|
275927
|
+
const worldW = cols * aspect;
|
|
275928
|
+
const radius = Math.min(worldH, worldW) / 2;
|
|
275929
|
+
const cx = worldW / 2;
|
|
275930
|
+
const cy = worldH / 2;
|
|
275931
|
+
const step = 1 / SAMPLES;
|
|
275932
|
+
const grid = [];
|
|
275933
|
+
for (let py = 0;py < rows; py++) {
|
|
275934
|
+
const y0 = py * pixelH;
|
|
275935
|
+
const y1 = y0 + pixelH;
|
|
275936
|
+
const dyLo = y0 <= cy && cy <= y1 ? 0 : Math.min(Math.abs(y0 - cy), Math.abs(y1 - cy));
|
|
275937
|
+
const dyHi = Math.max(Math.abs(y0 - cy), Math.abs(y1 - cy));
|
|
275938
|
+
const line = [];
|
|
275939
|
+
for (let px = 0;px < cols; px++) {
|
|
275940
|
+
const x0 = px * aspect;
|
|
275941
|
+
const x1 = x0 + aspect;
|
|
275942
|
+
const dxLo = x0 <= cx && cx <= x1 ? 0 : Math.min(Math.abs(x0 - cx), Math.abs(x1 - cx));
|
|
275943
|
+
const dxHi = Math.max(Math.abs(x0 - cx), Math.abs(x1 - cx));
|
|
275944
|
+
if (Math.hypot(dxLo, dyLo) >= radius) {
|
|
275945
|
+
line.push(0);
|
|
275946
|
+
continue;
|
|
275947
|
+
}
|
|
275948
|
+
if (Math.hypot(dxHi, dyHi) <= radius) {
|
|
275949
|
+
line.push(1);
|
|
275950
|
+
continue;
|
|
275951
|
+
}
|
|
275952
|
+
let hits = 0;
|
|
275953
|
+
for (let j = 0;j < SAMPLES; j++) {
|
|
275954
|
+
const dy = y0 + (j + 0.5) * step * pixelH - cy;
|
|
275955
|
+
for (let i = 0;i < SAMPLES; i++) {
|
|
275956
|
+
const dx = x0 + (i + 0.5) * step * aspect - cx;
|
|
275957
|
+
if (Math.hypot(dx, dy) <= radius)
|
|
275958
|
+
hits++;
|
|
275959
|
+
}
|
|
275960
|
+
}
|
|
275961
|
+
line.push(hits / (SAMPLES * SAMPLES));
|
|
275962
|
+
}
|
|
275963
|
+
grid.push(line);
|
|
275964
|
+
}
|
|
275965
|
+
return grid;
|
|
275966
|
+
}
|
|
275967
|
+
function carveGap(grid, ry) {
|
|
275968
|
+
const n = grid.length;
|
|
275969
|
+
const thick = Math.max(1, Math.round(GAP_HEIGHT_ROWS * ry));
|
|
275970
|
+
const centre = GAP_ROW >= 0 ? GAP_ROW * ry : n + GAP_ROW * ry;
|
|
275971
|
+
const start = Math.max(0, Math.min(n - thick, centre - thick / 2));
|
|
275972
|
+
for (let i = Math.trunc(start);i < Math.trunc(start + thick); i++) {
|
|
275973
|
+
grid[i].fill(0);
|
|
275974
|
+
}
|
|
275975
|
+
}
|
|
275976
|
+
function logoRows(color, tier = detectTier()) {
|
|
275977
|
+
const { rx, ry, table } = tier === "octant" ? OCTANT : QUAD;
|
|
275978
|
+
const grid = coverageGrid(ROWS * ry, LOGO_COLS * rx, ASPECT / rx, 1 / ry);
|
|
275979
|
+
carveGap(grid, ry);
|
|
275980
|
+
const fg = color ? source_default.hex(color) : (s) => s;
|
|
275981
|
+
const bg = color ? source_default.bgHex(color) : (s) => s;
|
|
275982
|
+
const full = (1 << rx * ry) - 1;
|
|
275983
|
+
const out = [];
|
|
275984
|
+
for (let r = 0;r < ROWS; r++) {
|
|
275985
|
+
let row = "";
|
|
275986
|
+
for (let c = 0;c < LOGO_COLS; c++) {
|
|
275987
|
+
let mask = 0;
|
|
275988
|
+
for (let sr = 0;sr < ry; sr++) {
|
|
275989
|
+
for (let sc = 0;sc < rx; sc++) {
|
|
275990
|
+
if (grid[r * ry + sr][c * rx + sc] >= FILL)
|
|
275991
|
+
mask |= 1 << sr * rx + sc;
|
|
275992
|
+
}
|
|
275993
|
+
}
|
|
275994
|
+
const glyph = table[mask];
|
|
275995
|
+
if (glyph === " ")
|
|
275996
|
+
row += " ";
|
|
275997
|
+
else if (mask === full && color)
|
|
275998
|
+
row += bg(" ");
|
|
275999
|
+
else
|
|
276000
|
+
row += fg(glyph);
|
|
276001
|
+
}
|
|
276002
|
+
out.push(row);
|
|
276003
|
+
}
|
|
276004
|
+
return out;
|
|
276005
|
+
}
|
|
276006
|
+
|
|
275854
276007
|
// src/cli/commands/code/paste.ts
|
|
275855
276008
|
import { PassThrough as PassThrough3 } from "node:stream";
|
|
275856
276009
|
var START = "\x1B[200~";
|
|
@@ -276255,7 +276408,7 @@ function SessionView({ engine, footer, subscribe }) {
|
|
|
276255
276408
|
});
|
|
276256
276409
|
const columns = process.stdout.columns || 80;
|
|
276257
276410
|
const rows = process.stdout.rows || 24;
|
|
276258
|
-
const width =
|
|
276411
|
+
const width = columns;
|
|
276259
276412
|
const innerWidth = Math.max(10, width - 4);
|
|
276260
276413
|
const inputRows = Math.max(1, Math.ceil((input.length + 3) / innerWidth));
|
|
276261
276414
|
const pickerOpen = pickerIndex !== null;
|
|
@@ -276350,43 +276503,11 @@ function SessionView({ engine, footer, subscribe }) {
|
|
|
276350
276503
|
]
|
|
276351
276504
|
}, undefined, true, undefined, this);
|
|
276352
276505
|
}
|
|
276353
|
-
var LOGO_ROWS = 6;
|
|
276354
|
-
var LOGO_GAP_SUBROW = 8;
|
|
276355
|
-
var QUAD = " ▘▝▀▖▌▞▛▗▚▐▜▄▙▟█";
|
|
276356
|
-
function buildLogoRows() {
|
|
276357
|
-
const sy = 2 * LOGO_ROWS;
|
|
276358
|
-
const sx = 2 * sy;
|
|
276359
|
-
const cx = (sx - 1) / 2;
|
|
276360
|
-
const cy = (sy - 1) / 2;
|
|
276361
|
-
const rad = sy / 2 - 0.5;
|
|
276362
|
-
const on = (px, py) => {
|
|
276363
|
-
if (py === LOGO_GAP_SUBROW)
|
|
276364
|
-
return false;
|
|
276365
|
-
const dx = (px - cx) * 0.5;
|
|
276366
|
-
const dy = py - cy;
|
|
276367
|
-
return dx * dx + dy * dy <= rad * rad;
|
|
276368
|
-
};
|
|
276369
|
-
const rows = [];
|
|
276370
|
-
for (let ty = 0;ty < sy; ty += 2) {
|
|
276371
|
-
let row = "";
|
|
276372
|
-
for (let tx = 0;tx < sx; tx += 2) {
|
|
276373
|
-
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);
|
|
276374
|
-
row += QUAD[bits];
|
|
276375
|
-
}
|
|
276376
|
-
rows.push(row.trim());
|
|
276377
|
-
}
|
|
276378
|
-
return rows.map((row) => row.trim());
|
|
276379
|
-
}
|
|
276380
276506
|
function renderHeader(who, mode) {
|
|
276381
276507
|
const orange = source_default.hex(BRAND_ORANGE);
|
|
276382
276508
|
const cwd = process.cwd().replace(process.env.HOME ?? "", "~");
|
|
276383
|
-
const logo =
|
|
276384
|
-
const logoW =
|
|
276385
|
-
const center = (s) => {
|
|
276386
|
-
const total = Math.max(0, logoW - s.length);
|
|
276387
|
-
const left = Math.floor(total / 2);
|
|
276388
|
-
return " ".repeat(left) + s + " ".repeat(total - left);
|
|
276389
|
-
};
|
|
276509
|
+
const logo = logoRows(BRAND_ORANGE);
|
|
276510
|
+
const logoW = LOGO_COLS;
|
|
276390
276511
|
const text = [
|
|
276391
276512
|
`${orange.bold("Base44 Code")} ${source_default.dim(`v${package_default.version}`)}`,
|
|
276392
276513
|
source_default.bold(who ? `Welcome back, ${who}!` : "Welcome!"),
|
|
@@ -276398,7 +276519,7 @@ function renderHeader(who, mode) {
|
|
|
276398
276519
|
const textTop = Math.max(0, Math.floor((logo.length - text.length) / 2));
|
|
276399
276520
|
const out = [];
|
|
276400
276521
|
for (let i = 0;i < height; i++) {
|
|
276401
|
-
const left = i < logo.length ?
|
|
276522
|
+
const left = i < logo.length ? logo[i] : " ".repeat(logoW);
|
|
276402
276523
|
const right = text[i - textTop] ?? "";
|
|
276403
276524
|
out.push(` ${left} ${right}`.trimEnd());
|
|
276404
276525
|
}
|
|
@@ -276589,21 +276710,21 @@ async function runGenesisSession(options) {
|
|
|
276589
276710
|
|
|
276590
276711
|
// src/cli/commands/code/index.ts
|
|
276591
276712
|
var BRAND_ORANGE2 = "#E86B3C";
|
|
276592
|
-
async function bootstrapApp(prompt, footer, emit, onCreated, importRepo) {
|
|
276713
|
+
async function bootstrapApp(prompt, footer, emit, onCreated, importRepo, path) {
|
|
276593
276714
|
let app;
|
|
276594
276715
|
try {
|
|
276595
|
-
app = await createAndLinkApp({ prompt, importRepo });
|
|
276716
|
+
app = await createAndLinkApp({ prompt, importRepo, path });
|
|
276596
276717
|
} catch (error) {
|
|
276597
276718
|
for (const line of await githubReauthLines(error) ?? [])
|
|
276598
276719
|
emit(line);
|
|
276599
276720
|
throw error;
|
|
276600
276721
|
}
|
|
276601
276722
|
onCreated(app);
|
|
276602
|
-
footer.push(source_default.dim(`dir
|
|
276723
|
+
footer.push(source_default.dim(`dir ${app.here ? "./" : `./${app.dirName}`}`));
|
|
276603
276724
|
if (app.repoUrl)
|
|
276604
276725
|
footer.push(terminalLink("repo", app.repoUrl));
|
|
276605
276726
|
footer.push(terminalLink("editor", app.editorUrl));
|
|
276606
|
-
emit(source_default.dim(`linked ./${app.dirName} (cd ${app.dirName} after the session)`));
|
|
276727
|
+
emit(source_default.dim(app.here ? "linked ./ (this directory)" : `linked ./${app.dirName} (cd ${app.dirName} after the session)`));
|
|
276607
276728
|
const branchId = await resolveActiveBranchId().catch(() => {
|
|
276608
276729
|
return;
|
|
276609
276730
|
});
|
|
@@ -276627,46 +276748,52 @@ async function bootstrapApp(prompt, footer, emit, onCreated, importRepo) {
|
|
|
276627
276748
|
}
|
|
276628
276749
|
};
|
|
276629
276750
|
}
|
|
276630
|
-
async function codeAction({ log }, options) {
|
|
276751
|
+
async function codeAction({ log }, options, appId) {
|
|
276752
|
+
const orange = source_default.hex(BRAND_ORANGE2);
|
|
276753
|
+
const chip = (label) => source_default.dim(`${orange("●")} ${label}`);
|
|
276631
276754
|
if (process.stdout.isTTY !== true) {
|
|
276632
276755
|
throw new InvalidInputError("base44 code is an interactive session and needs a terminal.");
|
|
276633
276756
|
}
|
|
276634
276757
|
let linked = false;
|
|
276635
276758
|
try {
|
|
276636
|
-
await initAppContext();
|
|
276759
|
+
await initAppContext(appId ? { appId } : {});
|
|
276637
276760
|
linked = true;
|
|
276638
276761
|
} catch {}
|
|
276639
276762
|
if (linked) {
|
|
276640
|
-
if (options.import) {
|
|
276641
|
-
throw new InvalidInputError("--import
|
|
276763
|
+
if (options.import || options.path) {
|
|
276764
|
+
throw new InvalidInputError("--import and --path create a new app; run them outside a linked project, without --app-id.");
|
|
276642
276765
|
}
|
|
276766
|
+
const { id, projectRoot } = getAppContext();
|
|
276767
|
+
const state = await assertBuilderApp(id);
|
|
276643
276768
|
const branchId = await resolveActiveBranchId().catch(() => {
|
|
276644
276769
|
return;
|
|
276645
276770
|
});
|
|
276646
276771
|
await runInteractiveSession({
|
|
276647
276772
|
branchId,
|
|
276648
|
-
footer: [],
|
|
276773
|
+
footer: [chip(appTypeChip(state))],
|
|
276649
276774
|
primeFirstPoll: true,
|
|
276650
276775
|
idleHint: "what should the agent do next?"
|
|
276651
276776
|
});
|
|
276652
|
-
|
|
276777
|
+
if (projectRoot) {
|
|
276778
|
+
log.message(source_default.dim(`app dir ${projectRoot}`));
|
|
276779
|
+
return {
|
|
276780
|
+
outroMessage: "Session closed. Run `base44 code` here to resume."
|
|
276781
|
+
};
|
|
276782
|
+
}
|
|
276653
276783
|
return {
|
|
276654
|
-
outroMessage:
|
|
276784
|
+
outroMessage: `Session closed. Resume with \`base44 code --app-id ${id}\`.`
|
|
276655
276785
|
};
|
|
276656
276786
|
}
|
|
276657
276787
|
let created;
|
|
276658
|
-
const
|
|
276659
|
-
const footer = [
|
|
276660
|
-
source_default.dim(`${orange("●")} ${options.import ? "import" : "builder"}`)
|
|
276661
|
-
];
|
|
276788
|
+
const footer = [chip(options.import ? repoLabel(options.import) : "web app")];
|
|
276662
276789
|
await runGenesisSession({
|
|
276663
|
-
idleHint: options.import ? "describe what to build over the
|
|
276790
|
+
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",
|
|
276664
276791
|
creatingLabel: options.import ? "importing the repository" : "creating your app",
|
|
276665
|
-
modeLabel: options.import ? `
|
|
276792
|
+
modeLabel: options.import ? `Repository — ${repoLabel(options.import)}` : "Web app — Base44 template + builder agent",
|
|
276666
276793
|
footer,
|
|
276667
276794
|
createApp: (prompt, emit) => bootstrapApp(prompt, footer, emit, (app) => {
|
|
276668
276795
|
created = app;
|
|
276669
|
-
}, options.import)
|
|
276796
|
+
}, options.import, options.path)
|
|
276670
276797
|
});
|
|
276671
276798
|
if (!created) {
|
|
276672
276799
|
return { outroMessage: "Session closed. No app was created." };
|
|
@@ -276678,7 +276805,7 @@ async function codeAction({ log }, options) {
|
|
|
276678
276805
|
}
|
|
276679
276806
|
function getCodeCommand() {
|
|
276680
276807
|
const command = new Base44Command("code", { requireAppContext: false });
|
|
276681
|
-
command.description("Open Base44 Code
|
|
276808
|
+
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>)").action((ctx, options) => codeAction(ctx, options, command.optsWithGlobals().appId));
|
|
276682
276809
|
return command;
|
|
276683
276810
|
}
|
|
276684
276811
|
|
|
@@ -277476,10 +277603,10 @@ function getConnectorsListAvailableCommand() {
|
|
|
277476
277603
|
}
|
|
277477
277604
|
|
|
277478
277605
|
// src/cli/commands/connectors/pull.ts
|
|
277479
|
-
import { dirname as dirname19, join as join27, resolve as
|
|
277606
|
+
import { dirname as dirname19, join as join27, resolve as resolve9 } from "node:path";
|
|
277480
277607
|
async function resolveConnectorsDir(options) {
|
|
277481
277608
|
if (!getAppContext().projectRoot) {
|
|
277482
|
-
return
|
|
277609
|
+
return resolve9(options.dir ?? "connectors");
|
|
277483
277610
|
}
|
|
277484
277611
|
const { project } = await readProjectConfig();
|
|
277485
277612
|
return join27(dirname19(project.configPath), project.connectorsDir);
|
|
@@ -277523,10 +277650,10 @@ function getConnectorsPullCommand() {
|
|
|
277523
277650
|
}
|
|
277524
277651
|
|
|
277525
277652
|
// src/cli/commands/connectors/push.ts
|
|
277526
|
-
import { resolve as
|
|
277653
|
+
import { resolve as resolve10 } from "node:path";
|
|
277527
277654
|
async function readConnectorsToPush(options) {
|
|
277528
277655
|
if (!getAppContext().projectRoot) {
|
|
277529
|
-
return readAllConnectors(
|
|
277656
|
+
return readAllConnectors(resolve10(options.dir ?? "connectors"));
|
|
277530
277657
|
}
|
|
277531
277658
|
const { connectors } = await readProjectConfig();
|
|
277532
277659
|
return connectors;
|
|
@@ -277995,7 +278122,7 @@ function getBuildCommand() {
|
|
|
277995
278122
|
}
|
|
277996
278123
|
|
|
277997
278124
|
// src/cli/commands/project/create.ts
|
|
277998
|
-
import { basename as
|
|
278125
|
+
import { basename as basename7, resolve as resolve11 } from "node:path";
|
|
277999
278126
|
var import_kebabCase2 = __toESM(require_kebabCase(), 1);
|
|
278000
278127
|
|
|
278001
278128
|
// src/cli/commands/project/scaffold-shared.ts
|
|
@@ -278160,8 +278287,8 @@ async function createInteractive(options, ctx) {
|
|
|
278160
278287
|
name: () => {
|
|
278161
278288
|
return options.name ? Promise.resolve(options.name) : Ze({
|
|
278162
278289
|
message: "What is the name of your project?",
|
|
278163
|
-
placeholder:
|
|
278164
|
-
initialValue:
|
|
278290
|
+
placeholder: basename7(process.cwd()),
|
|
278291
|
+
initialValue: basename7(process.cwd()),
|
|
278165
278292
|
validate: (value) => {
|
|
278166
278293
|
if (!value || value.trim().length === 0) {
|
|
278167
278294
|
return "Every project deserves a name";
|
|
@@ -278191,7 +278318,7 @@ async function createInteractive(options, ctx) {
|
|
|
278191
278318
|
}, ctx);
|
|
278192
278319
|
}
|
|
278193
278320
|
async function createNonInteractive(options, ctx) {
|
|
278194
|
-
ctx.log.info(`Creating a new project at ${
|
|
278321
|
+
ctx.log.info(`Creating a new project at ${resolve11(options.path)}`);
|
|
278195
278322
|
const template = await getTemplateById(options.template ?? DEFAULT_TEMPLATE_ID);
|
|
278196
278323
|
return await executeCreate({
|
|
278197
278324
|
template,
|
|
@@ -278215,7 +278342,7 @@ async function executeCreate({
|
|
|
278215
278342
|
}, ctx) {
|
|
278216
278343
|
const { log, runTask } = ctx;
|
|
278217
278344
|
const name = rawName.trim();
|
|
278218
|
-
const resolvedPath =
|
|
278345
|
+
const resolvedPath = resolve11(projectPath);
|
|
278219
278346
|
const organizationId = await resolveWorkspaceId(ctx, flagWorkspaceId, isInteractive);
|
|
278220
278347
|
const { projectId } = await runTask("Setting up your project...", async () => {
|
|
278221
278348
|
return await createProjectFiles({
|
|
@@ -278856,7 +278983,7 @@ function getLogsCommand() {
|
|
|
278856
278983
|
}
|
|
278857
278984
|
|
|
278858
278985
|
// src/cli/commands/project/scaffold.ts
|
|
278859
|
-
import { basename as
|
|
278986
|
+
import { basename as basename8, resolve as resolve12 } from "node:path";
|
|
278860
278987
|
function resolveAppId(options) {
|
|
278861
278988
|
const appId = options.appId;
|
|
278862
278989
|
if (!appId) {
|
|
@@ -278872,8 +278999,8 @@ function resolveAppId(options) {
|
|
|
278872
278999
|
async function scaffoldAction(ctx, name, options, command) {
|
|
278873
279000
|
const { log, runTask } = ctx;
|
|
278874
279001
|
const appId = resolveAppId(command.optsWithGlobals());
|
|
278875
|
-
const resolvedPath =
|
|
278876
|
-
const projectName = (name ??
|
|
279002
|
+
const resolvedPath = resolve12("./");
|
|
279003
|
+
const projectName = (name ?? basename8(resolvedPath)).trim();
|
|
278877
279004
|
const template = await getTemplateById("backend-only");
|
|
278878
279005
|
log.info(`Scaffolding project at ${resolvedPath}`);
|
|
278879
279006
|
const { projectId } = await runTask("Setting up your project...", async () => {
|
|
@@ -279190,6 +279317,21 @@ function getSandboxListDirectoryCommand() {
|
|
|
279190
279317
|
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);
|
|
279191
279318
|
}
|
|
279192
279319
|
|
|
279320
|
+
// src/cli/commands/sandbox/preview.ts
|
|
279321
|
+
async function previewAction(ctx) {
|
|
279322
|
+
const url = await ctx.runTask("Resolving preview URL (boots the sandbox if needed)", () => getPreviewUrl());
|
|
279323
|
+
if (ctx.jsonMode)
|
|
279324
|
+
return { stdout: `${JSON.stringify({ preview_url: url })}
|
|
279325
|
+
` };
|
|
279326
|
+
ctx.log.message(url);
|
|
279327
|
+
return { outroMessage: "Preview is live." };
|
|
279328
|
+
}
|
|
279329
|
+
function getSandboxPreviewCommand() {
|
|
279330
|
+
const command = new Base44Command("preview");
|
|
279331
|
+
command.description("Print the app's live preview URL").action(previewAction);
|
|
279332
|
+
return command;
|
|
279333
|
+
}
|
|
279334
|
+
|
|
279193
279335
|
// src/cli/commands/sandbox/read-file.ts
|
|
279194
279336
|
async function readFileAction({ runTask, branchId }, paths, options) {
|
|
279195
279337
|
const { id: appId } = getAppContext();
|
|
@@ -279243,7 +279385,7 @@ Examples:
|
|
|
279243
279385
|
|
|
279244
279386
|
// src/cli/commands/sandbox/index.ts
|
|
279245
279387
|
function getSandboxCommand() {
|
|
279246
|
-
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());
|
|
279388
|
+
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());
|
|
279247
279389
|
}
|
|
279248
279390
|
|
|
279249
279391
|
// src/cli/commands/secrets/delete.ts
|
|
@@ -279289,7 +279431,7 @@ function getSecretsListCommand() {
|
|
|
279289
279431
|
}
|
|
279290
279432
|
|
|
279291
279433
|
// src/cli/commands/secrets/set.ts
|
|
279292
|
-
import { resolve as
|
|
279434
|
+
import { resolve as resolve13 } from "node:path";
|
|
279293
279435
|
function parseEntries(entries) {
|
|
279294
279436
|
const secrets = {};
|
|
279295
279437
|
for (const entry of entries) {
|
|
@@ -279320,7 +279462,7 @@ async function setSecretsAction({ log, runTask }, entries, options) {
|
|
|
279320
279462
|
validateInput(entries, options);
|
|
279321
279463
|
let secrets;
|
|
279322
279464
|
if (options.envFile) {
|
|
279323
|
-
secrets = await parseEnvFile(
|
|
279465
|
+
secrets = await parseEnvFile(resolve13(options.envFile));
|
|
279324
279466
|
if (Object.keys(secrets).length === 0) {
|
|
279325
279467
|
throw new InvalidInputError("The env file contains no valid KEY=VALUE entries.");
|
|
279326
279468
|
}
|
|
@@ -279349,7 +279491,7 @@ function getSecretsCommand() {
|
|
|
279349
279491
|
}
|
|
279350
279492
|
|
|
279351
279493
|
// src/cli/commands/site/deploy.ts
|
|
279352
|
-
import { resolve as
|
|
279494
|
+
import { resolve as resolve14 } from "node:path";
|
|
279353
279495
|
async function deployAction2(ctx, options) {
|
|
279354
279496
|
const { isNonInteractive } = ctx;
|
|
279355
279497
|
if (isNonInteractive && !options.yes) {
|
|
@@ -279427,7 +279569,7 @@ async function deployTarball({ runTask }, project) {
|
|
|
279427
279569
|
}
|
|
279428
279570
|
function siteOutputDir(project) {
|
|
279429
279571
|
const outputDirectory = project.site?.outputDirectory;
|
|
279430
|
-
return outputDirectory ?
|
|
279572
|
+
return outputDirectory ? resolve14(project.root, outputDirectory) : null;
|
|
279431
279573
|
}
|
|
279432
279574
|
function getSiteDeployCommand() {
|
|
279433
279575
|
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)");
|
|
@@ -281929,7 +282071,7 @@ function createCustomIntegrationRoutes(remoteProxy, logger) {
|
|
|
281929
282071
|
|
|
281930
282072
|
// src/cli/dev/dev-server/watcher.ts
|
|
281931
282073
|
import { EventEmitter as EventEmitter6 } from "node:events";
|
|
281932
|
-
import { relative as
|
|
282074
|
+
import { relative as relative10 } from "node:path";
|
|
281933
282075
|
|
|
281934
282076
|
// ../../node_modules/chokidar/index.js
|
|
281935
282077
|
import { EventEmitter as EventEmitter5 } from "node:events";
|
|
@@ -283614,7 +283756,7 @@ class WatchBase44 extends EventEmitter6 {
|
|
|
283614
283756
|
ignoreInitial: true
|
|
283615
283757
|
});
|
|
283616
283758
|
watcher.on("all", import_debounce4.default(async (_event, path) => {
|
|
283617
|
-
this.emit("change", name,
|
|
283759
|
+
this.emit("change", name, relative10(targetPath, path));
|
|
283618
283760
|
}, WATCH_DEBOUNCE_MS));
|
|
283619
283761
|
watcher.on("error", (err) => {
|
|
283620
283762
|
this.logger.error(`Watch handler failed for ${targetPath}`, err);
|
|
@@ -284124,7 +284266,7 @@ Examples:
|
|
|
284124
284266
|
}
|
|
284125
284267
|
|
|
284126
284268
|
// src/cli/commands/project/eject.ts
|
|
284127
|
-
import { resolve as
|
|
284269
|
+
import { resolve as resolve18 } from "node:path";
|
|
284128
284270
|
var import_kebabCase3 = __toESM(require_kebabCase(), 1);
|
|
284129
284271
|
async function eject(ctx, options, command) {
|
|
284130
284272
|
const { log, runTask, isNonInteractive } = ctx;
|
|
@@ -284188,7 +284330,7 @@ async function eject(ctx, options, command) {
|
|
|
284188
284330
|
Ne("Operation cancelled.");
|
|
284189
284331
|
throw new CLIExitError(0);
|
|
284190
284332
|
}
|
|
284191
|
-
const resolvedPath =
|
|
284333
|
+
const resolvedPath = resolve18(selectedPath);
|
|
284192
284334
|
await runTask("Downloading your project's code...", async (updateMessage) => {
|
|
284193
284335
|
await createProjectFilesForExistingProject({
|
|
284194
284336
|
projectId,
|
|
@@ -284270,7 +284412,7 @@ function createProgram(context) {
|
|
|
284270
284412
|
program.addCommand(getSecretsCommand());
|
|
284271
284413
|
program.addCommand(getSandboxCommand());
|
|
284272
284414
|
program.addCommand(getBranchesCommand());
|
|
284273
|
-
program.addCommand(
|
|
284415
|
+
program.addCommand(getBuilderCommand());
|
|
284274
284416
|
program.addCommand(getCodeCommand());
|
|
284275
284417
|
program.addCommand(getAuthCommand());
|
|
284276
284418
|
program.addCommand(getSiteCommand());
|
|
@@ -288325,4 +288467,4 @@ export {
|
|
|
288325
288467
|
runCLI
|
|
288326
288468
|
};
|
|
288327
288469
|
|
|
288328
|
-
//# debugId=
|
|
288470
|
+
//# debugId=E73C629AD07CDDC164756E2164756E21
|