diff --git a/.github/workflows/pr-description.yml b/.github/workflows/pr-description.yml
index 024bc168..0a0113dd 100644
--- a/.github/workflows/pr-description.yml
+++ b/.github/workflows/pr-description.yml
@@ -36,48 +36,49 @@ jobs:
USE THIS EXACT TEMPLATE FORMAT (fill in the placeholders and check appropriate boxes):
- ## Description
-
- [2-4 sentence summary of what this PR does and why]
-
- ## Related Issue
-
- [Link any related issues, e.g., "Fixes #123" or "Closes #456", or "None" if not applicable]
-
- ## Type of Change
-
- - [ ] Bug fix (non-breaking change which fixes an issue)
- - [ ] New feature (non-breaking change which adds functionality)
- - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- - [ ] Documentation update
- - [ ] Refactoring (no functional changes)
- - [ ] Other (please describe):
-
- ## Changes Made
-
- [List the main changes as bullet points]
-
- ## Testing
-
- - [ ] I have tested these changes locally
- - [ ] I have added/updated tests as needed
- - [ ] All tests pass (`npm test`)
-
- ## Checklist
-
- - [ ] My code follows the project's style guidelines
- - [ ] I have performed a self-review of my own code
- - [ ] I have commented my code, particularly in hard-to-understand areas
- - [ ] I have made corresponding changes to the documentation (if applicable)
- - [ ] My changes generate no new warnings
- - [ ] I have updated AGENTS.md if I made architectural changes
-
- ## Additional Notes
-
- [Any additional context, or "None"]
-
- ---
- 🤖 Generated by Claude | [current UTC timestamp in format: YYYY-MM-DD HH:MM UTC]
+ > [!NOTE]
+ > ## Description
+ >
+ > [2-4 sentence summary of what this PR does and why]
+ >
+ > ## Related Issue
+ >
+ > [Link any related issues, e.g., "Fixes #123" or "Closes #456", or "None" if not applicable]
+ >
+ > ## Type of Change
+ >
+ > - [ ] Bug fix (non-breaking change which fixes an issue)
+ > - [ ] New feature (non-breaking change which adds functionality)
+ > - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
+ > - [ ] Documentation update
+ > - [ ] Refactoring (no functional changes)
+ > - [ ] Other (please describe):
+ >
+ > ## Changes Made
+ >
+ > [List the main changes as bullet points]
+ >
+ > ## Testing
+ >
+ > - [ ] I have tested these changes locally
+ > - [ ] I have added/updated tests as needed
+ > - [ ] All tests pass (`npm test`)
+ >
+ > ## Checklist
+ >
+ > - [ ] My code follows the project's style guidelines
+ > - [ ] I have performed a self-review of my own code
+ > - [ ] I have commented my code, particularly in hard-to-understand areas
+ > - [ ] I have made corresponding changes to the documentation (if applicable)
+ > - [ ] My changes generate no new warnings
+ > - [ ] I have updated AGENTS.md if I made architectural changes
+ >
+ > ## Additional Notes
+ >
+ > [Any additional context, or "None"]
+ >
+ > ---
+ > 🤖 Generated by Claude | [current UTC timestamp in format: YYYY-MM-DD HH:MM UTC]
GUIDELINES FOR FILLING THE TEMPLATE:
- Check [x] the appropriate "Type of Change" based on what the code does
diff --git a/src/cli/commands/site/browse.ts b/src/cli/commands/site/browse.ts
new file mode 100644
index 00000000..077646ec
--- /dev/null
+++ b/src/cli/commands/site/browse.ts
@@ -0,0 +1,23 @@
+import { Command } from "commander";
+import open from "open";
+import type { CLIContext } from "@/cli/types.js";
+import { runCommand, getSiteUrl } from "@/cli/utils/index.js";
+import type { RunCommandResult } from "@/cli/utils/runCommand.js";
+
+async function browseAction(): Promise {
+ const siteUrl = await getSiteUrl();
+
+ if (!process.env.CI) {
+ await open(siteUrl);
+ }
+
+ return { outroMessage: `Site opened at ${siteUrl}` };
+}
+
+export function getSiteBrowseCommand(context: CLIContext): Command {
+ return new Command("browse")
+ .description("Open the published site in your browser")
+ .action(async () => {
+ await runCommand(browseAction, { requireAuth: true }, context);
+ });
+}
diff --git a/src/cli/commands/site/deploy.ts b/src/cli/commands/site/deploy.ts
index b33efa6b..5c64337b 100644
--- a/src/cli/commands/site/deploy.ts
+++ b/src/cli/commands/site/deploy.ts
@@ -53,14 +53,10 @@ async function deployAction(options: DeployOptions): Promise {
}
export function getSiteDeployCommand(context: CLIContext): Command {
- return new Command("site")
- .description("Manage site deployments")
- .addCommand(
- new Command("deploy")
- .description("Deploy built site files to Base44 hosting")
- .option("-y, --yes", "Skip confirmation prompt")
- .action(async (options: DeployOptions) => {
- await runCommand(() => deployAction(options), { requireAuth: true }, context);
- })
- );
+ return new Command("deploy")
+ .description("Deploy built site files to Base44 hosting")
+ .option("-y, --yes", "Skip confirmation prompt")
+ .action(async (options: DeployOptions) => {
+ await runCommand(() => deployAction(options), { requireAuth: true }, context);
+ });
}
diff --git a/src/cli/commands/site/index.ts b/src/cli/commands/site/index.ts
new file mode 100644
index 00000000..909fdf04
--- /dev/null
+++ b/src/cli/commands/site/index.ts
@@ -0,0 +1,11 @@
+import { Command } from "commander";
+import type { CLIContext } from "@/cli/types.js";
+import { getSiteDeployCommand } from "./deploy.js";
+import { getSiteBrowseCommand } from "./browse.js";
+
+export function getSiteCommand(context: CLIContext): Command {
+ return new Command("site")
+ .description("Manage site")
+ .addCommand(getSiteDeployCommand(context))
+ .addCommand(getSiteBrowseCommand(context));
+}
diff --git a/src/cli/program.ts b/src/cli/program.ts
index 77474222..6477c10b 100644
--- a/src/cli/program.ts
+++ b/src/cli/program.ts
@@ -10,7 +10,7 @@ import { getCreateCommand } from "@/cli/commands/project/create.js";
import { getDashboardCommand } from "@/cli/commands/project/dashboard.js";
import { getDeployCommand } from "@/cli/commands/project/deploy.js";
import { getLinkCommand } from "@/cli/commands/project/link.js";
-import { getSiteDeployCommand } from "@/cli/commands/site/deploy.js";
+import { getSiteCommand } from "@/cli/commands/site/index.js";
import packageJson from "../../package.json";
export function createProgram(context: CLIContext): Command {
@@ -48,7 +48,7 @@ export function createProgram(context: CLIContext): Command {
program.addCommand(getFunctionsDeployCommand(context));
// Register site commands
- program.addCommand(getSiteDeployCommand(context));
+ program.addCommand(getSiteCommand(context));
return program;
}
diff --git a/src/cli/utils/urls.ts b/src/cli/utils/urls.ts
index c86fd43a..c7113677 100644
--- a/src/cli/utils/urls.ts
+++ b/src/cli/utils/urls.ts
@@ -1,5 +1,11 @@
+import { z } from "zod";
import { getBase44ApiUrl } from "@/core/config.js";
import { getAppConfig } from "@/core/project/index.js";
+import { base44Client } from "@/core/clients/index.js";
+
+const PublishedUrlResponseSchema = z.object({
+ url: z.string(),
+});
/**
* Gets the dashboard URL for a project.
@@ -12,3 +18,17 @@ export function getDashboardUrl(projectId?: string): string {
const id = projectId ?? getAppConfig().id;
return `${getBase44ApiUrl()}/apps/${id}/editor/workspace/overview`;
}
+
+/**
+ * Gets the published site URL for a project by calling the API.
+ *
+ * @param projectId - Optional project ID. If not provided, uses cached appId from getAppConfig().
+ * @returns The published site URL (e.g., https://myapp.base44.app)
+ * @throws Error if no projectId provided and app config is not initialized, or if app has no slug
+ */
+export async function getSiteUrl(projectId?: string): Promise {
+ const id = projectId ?? getAppConfig().id;
+ const response = await base44Client.get(`api/apps/platform/${id}/published-url`);
+ const data = PublishedUrlResponseSchema.parse(await response.json());
+ return data.url;
+}
diff --git a/src/core/resources/index.ts b/src/core/resources/index.ts
index 4f4aef06..a4da42c7 100644
--- a/src/core/resources/index.ts
+++ b/src/core/resources/index.ts
@@ -1,4 +1,4 @@
export type { Resource } from "./types.js";
export * from "./entity/index.js";
export * from "./function/index.js";
-export * from "./agent/index.js";
+export * from "./agent/index.js";
\ No newline at end of file
diff --git a/tests/cli/dashboard.spec.ts b/tests/cli/dashboard.spec.ts
index 13f83c8a..aaf88e8c 100644
--- a/tests/cli/dashboard.spec.ts
+++ b/tests/cli/dashboard.spec.ts
@@ -7,7 +7,7 @@ describe("dashboard command", () => {
it("opens dashboard URL when in a project", async () => {
await t.givenLoggedInWithProject(fixture("basic"));
- const result = await t.run("dashboard");
+ const result = await t.run("dashboard", "browse");
t.expectResult(result).toSucceed();
t.expectResult(result).toContain("Dashboard opened");
@@ -17,7 +17,7 @@ describe("dashboard command", () => {
it("fails when not in a project directory", async () => {
await t.givenLoggedIn({ email: "test@example.com", name: "Test User" });
- const result = await t.run("dashboard");
+ const result = await t.run("dashboard", "browse");
t.expectResult(result).toFail();
t.expectResult(result).toContain("No Base44 project found");