diff --git a/enterprise/frontend/src/embedding-sdk/README.md b/enterprise/frontend/src/embedding-sdk/README.md
index f64f9aab89415cb9f319368cd464fc632b304d8a..cd9205fc42942e34b4bb8743ca20df92e213298f 100644
--- a/enterprise/frontend/src/embedding-sdk/README.md
+++ b/enterprise/frontend/src/embedding-sdk/README.md
@@ -46,6 +46,17 @@ Features not yet supported:
 
 # Getting started
 
+## Quickstart
+
+Run `npx @metabase/embedding-sdk-react start` in your React project to get started.
+
+This will start a local Metabase instance with Docker and generate sample dashboard components from your data. First run of this command will take 1 - 2 minutes to install the dependencies.
+
+Prerequisites:
+
+- [Node.js 18.x LTS](https://nodejs.org/en) or higher
+- [Docker](https://docker.com)
+
 ## Start Metabase
 
 Currently, the SDK only works with Metabase version 50.
diff --git a/enterprise/frontend/src/embedding-sdk/cli/actions/start.ts b/enterprise/frontend/src/embedding-sdk/cli/actions/start.ts
index 79d7e84c57a9fc8b04685f2205d904c76e23bc9a..bce8ffbf5ebe11504a13f90024255fb4e9ee50ca 100644
--- a/enterprise/frontend/src/embedding-sdk/cli/actions/start.ts
+++ b/enterprise/frontend/src/embedding-sdk/cli/actions/start.ts
@@ -2,6 +2,9 @@ import { runCli } from "../run";
 import { printError } from "../utils/print";
 
 export async function start() {
+  // When the user runs the CLI with npx, there will be some deprecation warnings that we should clear.
+  console.clear();
+
   try {
     await runCli();
   } catch (error) {
diff --git a/enterprise/frontend/src/embedding-sdk/cli/constants/code-sample.ts b/enterprise/frontend/src/embedding-sdk/cli/constants/code-sample.ts
deleted file mode 100644
index f1e9710f7c46ffca7e87e3bb40e8c027d24f4431..0000000000000000000000000000000000000000
--- a/enterprise/frontend/src/embedding-sdk/cli/constants/code-sample.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { SDK_PACKAGE_NAME } from "embedding-sdk/cli/constants/config";
-
-export const getCodeSample = (url: string, apiKey: string) => `
-import {
-  MetabaseProvider,
-  InteractiveDashboard,
-} from '${SDK_PACKAGE_NAME}'
-
-/** @type {import('${SDK_PACKAGE_NAME}').SDKConfig} */
-const config = {
-  metabaseInstanceUrl: \`${url}\`,
-  apiKey: '${apiKey}'
-}
-
-export const Analytics = () => (
-  <MetabaseProvider config={config}>
-    <InteractiveDashboard dashboardId={1} />
-  </MetabaseProvider>
-)
-`;
diff --git a/enterprise/frontend/src/embedding-sdk/cli/constants/messages.ts b/enterprise/frontend/src/embedding-sdk/cli/constants/messages.ts
index 6e8dcb4a5fb68dfcf9788a8ef245389f0993c3b5..8e17b384fa7243d6e6f257eb4222cd7400380021 100644
--- a/enterprise/frontend/src/embedding-sdk/cli/constants/messages.ts
+++ b/enterprise/frontend/src/embedding-sdk/cli/constants/messages.ts
@@ -27,13 +27,19 @@ export const EMBEDDING_FAILED_MESSAGE = `
   ${DELETE_CONTAINER_MESSAGE}
 `;
 
-// eslint-disable-next-line no-unconditional-metabase-links-render -- link for the CLI message
-export const METABASE_INSTANCE_SETUP_COMPLETE_MESSAGE = `
+export const PREMIUM_TOKEN_REQUIRED_MESSAGE =
+  "  Don't forget to add your premium token to your Metabase instance in the admin settings! The embedding demo will not work without a license.";
+
+export const getMetabaseInstanceSetupCompleteMessage = (instanceUrl: string) =>
+  // eslint-disable-next-line no-unconditional-metabase-links-render -- link for the CLI message
+  `
   Metabase instance is ready for embedding.
+  Go to ${instanceUrl} to start using Metabase.
+
   You can find your login credentials at METABASE_LOGIN.json
   Don't forget to put this file in your .gitignore.
 
-  Metabase will phone home some data collected via Google Analytics and Snowplow.
+  Metabase will phone home some data collected via Snowplow.
   We don’t collect any usernames, emails, server IPs, database details of any kind, or
   any personally identifiable information (PII).
 
diff --git a/enterprise/frontend/src/embedding-sdk/cli/run.ts b/enterprise/frontend/src/embedding-sdk/cli/run.ts
index 49c40cf86ffb67f00c02b5dca634c7a712cf9eed..dfc7cbf4557420c9b85958a057011a865cf234f1 100644
--- a/enterprise/frontend/src/embedding-sdk/cli/run.ts
+++ b/enterprise/frontend/src/embedding-sdk/cli/run.ts
@@ -1,10 +1,14 @@
-import { METABASE_INSTANCE_SETUP_COMPLETE_MESSAGE } from "./constants/messages";
+import chalk from "chalk";
+
+import {
+  PREMIUM_TOKEN_REQUIRED_MESSAGE,
+  getMetabaseInstanceSetupCompleteMessage,
+} from "./constants/messages";
 import {
   addEmbeddingToken,
   checkIsDockerRunning,
   createApiKey,
   generateCredentials,
-  generateCodeSample,
   pollMetabaseInstance,
   setupMetabaseInstance,
   showMetabaseCliTitle,
@@ -14,6 +18,7 @@ import {
   addDatabaseConnectionStep,
   pickDatabaseTables,
   createModelsAndXrays,
+  generateReactComponentFiles,
 } from "./steps";
 import type { CliState } from "./types/cli";
 import { printEmptyLines, printInfo } from "./utils/print";
@@ -35,7 +40,10 @@ export const CLI_STEPS = [
   { id: "addDatabaseConnection", executeStep: addDatabaseConnectionStep },
   { id: "pickDatabaseTables", executeStep: pickDatabaseTables },
   { id: "createModelsAndXrays", executeStep: createModelsAndXrays },
-  { id: "generateCodeSample", executeStep: generateCodeSample },
+  {
+    id: "generateReactComponentFiles",
+    executeStep: generateReactComponentFiles,
+  },
 ] as const;
 
 export async function runCli() {
@@ -57,12 +65,10 @@ export async function runCli() {
     state = nextState;
   }
 
-  console.log(METABASE_INSTANCE_SETUP_COMPLETE_MESSAGE);
+  console.log(getMetabaseInstanceSetupCompleteMessage(state.instanceUrl ?? ""));
 
   if (!state.token) {
-    console.log(
-      "  Don't forget to add your premium token to your Metabase instance in the admin settings!",
-    );
+    console.log(chalk.bold(PREMIUM_TOKEN_REQUIRED_MESSAGE));
   }
 
   printEmptyLines(1);
diff --git a/enterprise/frontend/src/embedding-sdk/cli/snippets/analytics-css-snippet.ts b/enterprise/frontend/src/embedding-sdk/cli/snippets/analytics-css-snippet.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d984f90438318a194a0b21952b04248cf2aa0c97
--- /dev/null
+++ b/enterprise/frontend/src/embedding-sdk/cli/snippets/analytics-css-snippet.ts
@@ -0,0 +1,49 @@
+export const ANALYTICS_CSS_SNIPPET = `
+.theme-switcher {
+  width: 28px;
+  height: 28px;
+  cursor: pointer;
+}
+
+.analytics-container {
+  width: 100%;
+  max-width: 1000px;
+  margin: 0 auto;
+  min-height: 100vh;
+  padding: 30px 0;
+}
+
+.analytics-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 30px 0;
+  column-gap: 15px;
+}
+
+.analytics-header-right {
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  padding: 30px 0;
+  column-gap: 15px;
+}
+
+.analytics-header-right > a {
+  color: #509EE3;
+}
+
+.dashboard-select {
+  background: transparent;
+  color: #509EE3;
+  border: none;
+  font-family: inherit;
+  font-size: 14px;
+  cursor: pointer;
+}
+
+.dashboard-select:focus {
+  outline: 1px solid #509EE3;
+  border-radius: 2px;
+}
+`.trim();
diff --git a/enterprise/frontend/src/embedding-sdk/cli/snippets/analytics-dashboard-snippet.ts b/enterprise/frontend/src/embedding-sdk/cli/snippets/analytics-dashboard-snippet.ts
new file mode 100644
index 0000000000000000000000000000000000000000..083d7550021384574467e422fc9eae87df663052
--- /dev/null
+++ b/enterprise/frontend/src/embedding-sdk/cli/snippets/analytics-dashboard-snippet.ts
@@ -0,0 +1,50 @@
+import { SDK_PACKAGE_NAME } from "../constants/config";
+import type { DashboardInfo } from "../types/dashboard";
+
+export const getAnalyticsDashboardSnippet = (
+  instanceUrl: string,
+  dashboards: DashboardInfo[],
+) => `
+import { useState } from 'react'
+import { InteractiveDashboard } from '${SDK_PACKAGE_NAME}'
+
+import { ThemeSwitcher } from './theme-switcher'
+
+export const AnalyticsDashboard = () => {
+  const [dashboardId, setDashboardId] = useState(DASHBOARDS[0].id)
+
+  const editLink = \`${instanceUrl}/dashboard/\${dashboardId}\`
+
+  return (
+    <div className="analytics-container">
+      <div className="analytics-header">
+        <div>
+          <select
+            className="dashboard-select"
+            onChange={(e) => setDashboardId(e.target.value)}
+          >
+            {DASHBOARDS.map((dashboard) => (
+              <option key={dashboard.id} value={dashboard.id}>
+                {dashboard.name}
+              </option>
+            ))}
+          </select>
+        </div>
+
+        <div className="analytics-header-right">
+          {/** TODO: Remove. This is just a link to edit the dashboard in Metabase for your convenience. */}
+          <a href={editLink} target="_blank">
+            Edit this dashboard
+          </a>
+
+          <ThemeSwitcher />
+        </div>
+      </div>
+
+      <InteractiveDashboard dashboardId={dashboardId} withTitle withDownloads />
+    </div>
+  )
+}
+
+const DASHBOARDS = ${JSON.stringify(dashboards, null, 2)}
+`;
diff --git a/enterprise/frontend/src/embedding-sdk/cli/snippets/analytics-page-snippet.ts b/enterprise/frontend/src/embedding-sdk/cli/snippets/analytics-page-snippet.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b4753a32cf59586fbd3d3841f8bbf60e862645f1
--- /dev/null
+++ b/enterprise/frontend/src/embedding-sdk/cli/snippets/analytics-page-snippet.ts
@@ -0,0 +1,18 @@
+/**
+ * A minimal setup that brings together SDK provider,
+ * theme switcher, and a sample dashboard.
+ */
+export const ANALYTICS_PAGE_SNIPPET = `
+import { AnalyticsDashboard } from './analytics-dashboard'
+import { MetabaseEmbedProvider, SampleThemeProvider } from './metabase-provider'
+
+import './analytics.css'
+
+export const AnalyticsPage = () => (
+  <SampleThemeProvider>
+    <MetabaseEmbedProvider>
+      <AnalyticsDashboard />
+    </MetabaseEmbedProvider>
+  </SampleThemeProvider>
+)
+`;
diff --git a/enterprise/frontend/src/embedding-sdk/cli/snippets/index.ts b/enterprise/frontend/src/embedding-sdk/cli/snippets/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..65b4b0e141c9a268308e663ce2b00bc9fbc6b28b
--- /dev/null
+++ b/enterprise/frontend/src/embedding-sdk/cli/snippets/index.ts
@@ -0,0 +1,4 @@
+export * from "./analytics-dashboard-snippet";
+export * from "./metabase-provider-snippet";
+export * from "./analytics-page-snippet";
+export * from "./theme-switcher-snippet";
diff --git a/enterprise/frontend/src/embedding-sdk/cli/snippets/metabase-provider-snippet.ts b/enterprise/frontend/src/embedding-sdk/cli/snippets/metabase-provider-snippet.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3f587e181cd48afd585666faac5f7e6f53bff854
--- /dev/null
+++ b/enterprise/frontend/src/embedding-sdk/cli/snippets/metabase-provider-snippet.ts
@@ -0,0 +1,75 @@
+import { SDK_PACKAGE_NAME } from "embedding-sdk/cli/constants/config";
+
+export const getMetabaseProviderSnippet = (url: string, apiKey: string) => `
+import { createContext, useContext, useMemo, useState } from 'react'
+import { MetabaseProvider } from '${SDK_PACKAGE_NAME}'
+
+/** @type {import('${SDK_PACKAGE_NAME}').SDKConfig} */
+const config = {
+  metabaseInstanceUrl: \`${url}\`,
+  apiKey: '${apiKey}'
+}
+
+// Used for the example theme switcher component
+export const SampleThemeContext = createContext({ themeKey: 'light' })
+
+export const MetabaseEmbedProvider = ({ children }) => {
+  const { themeKey } = useContext(SampleThemeContext)
+  const theme = useMemo(() => THEMES[themeKey], [themeKey])
+
+  return (
+    <MetabaseProvider config={config} theme={theme}>
+      {children}
+    </MetabaseProvider>
+  )
+}
+
+export const SampleThemeProvider = ({ children }) => {
+  const [themeKey, setThemeKey] = useState('light')
+
+  return (
+    <SampleThemeContext.Provider value={{themeKey, setThemeKey}}>
+      {children}
+    </SampleThemeContext.Provider>
+  )
+}
+
+/**
+  * Sample themes for Metabase components.
+  *
+  * @type {Record<string, import('${SDK_PACKAGE_NAME}').MetabaseTheme>}
+  */
+const THEMES = {
+  // Light theme
+  "light": {
+    colors: {
+      brand: "#509EE3",
+      filter: "#7172AD",
+      "text-primary": "#4C5773",
+      "text-secondary": "#696E7B",
+      "text-tertiary": "#949AAB",
+      border: "#EEECEC",
+      background: "#F9FBFC",
+      "background-hover": "#F9FBFC",
+      positive: "#84BB4C",
+      negative: "#ED6E6E"
+    }
+  },
+
+  // Dark theme
+  "dark": {
+    colors: {
+      brand: "#509EE3",
+      filter: "#7172AD",
+      "text-primary": "#FFFFFF",
+      "text-secondary": "#FFFFFF",
+      "text-tertiary": "#FFFFFF",
+      border: "#5A5F6B",
+      background: "#2D353A",
+      "background-hover": "#2D353A",
+      positive: "#84BB4C",
+      negative: "#ED6E6E"
+    }
+  }
+}
+`;
diff --git a/enterprise/frontend/src/embedding-sdk/cli/snippets/theme-switcher-snippet.ts b/enterprise/frontend/src/embedding-sdk/cli/snippets/theme-switcher-snippet.ts
new file mode 100644
index 0000000000000000000000000000000000000000..56678a358f553faf9e8ed7540b1ae66c28390fdf
--- /dev/null
+++ b/enterprise/frontend/src/embedding-sdk/cli/snippets/theme-switcher-snippet.ts
@@ -0,0 +1,47 @@
+export const THEME_SWITCHER_SNIPPET = `
+import { useContext } from 'react'
+
+import { SampleThemeContext } from './metabase-provider'
+
+export const ThemeSwitcher = () => {
+  const { themeKey, setThemeKey } = useContext(SampleThemeContext)
+
+  const ThemeIcon = ICONS[themeKey]
+
+  return (
+    <div
+      className="theme-switcher"
+      onClick={() => setThemeKey(themeKey === 'light' ? 'dark' : 'light')}
+    >
+      <ThemeIcon />
+    </div>
+  )
+}
+
+const ICONS = {
+  light: () => (
+    <svg viewBox="0 0 24 24">
+      <path
+        fill="none"
+        stroke="currentColor"
+        strokeLinecap="round"
+        strokeLinejoin="round"
+        strokeWidth="2"
+        d="M8 12a4 4 0 1 0 8 0a4 4 0 1 0-8 0m-5 0h1m8-9v1m8 8h1m-9 8v1M5.6 5.6l.7.7m12.1-.7l-.7.7m0 11.4l.7.7m-12.1-.7l-.7.7"
+      />
+    </svg>
+  ),
+  dark: () => (
+    <svg viewBox="0 0 24 24">
+      <path
+        fill="none"
+        stroke="currentColor"
+        strokeLinecap="round"
+        strokeLinejoin="round"
+        strokeWidth="2"
+        d="M12 3h.393a7.5 7.5 0 0 0 7.92 12.446A9 9 0 1 1 12 2.992z"
+      />
+    </svg>
+  )
+}
+`;
diff --git a/enterprise/frontend/src/embedding-sdk/cli/steps/create-models-and-xrays.ts b/enterprise/frontend/src/embedding-sdk/cli/steps/create-models-and-xrays.ts
index 50e12164a88f6a6f1a31f7be0f6a847968a84fbf..731ebf9b0f356dc940a23e48af6b4e8aa5306337 100644
--- a/enterprise/frontend/src/embedding-sdk/cli/steps/create-models-and-xrays.ts
+++ b/enterprise/frontend/src/embedding-sdk/cli/steps/create-models-and-xrays.ts
@@ -1,10 +1,9 @@
 import ora from "ora";
 
-import { createXrayDashboardFromModel } from "embedding-sdk/cli/utils/xray-models";
-import type { DashboardId } from "metabase-types/api";
-
 import type { CliStepMethod } from "../types/cli";
+import type { DashboardInfo } from "../types/dashboard";
 import { createModelFromTable } from "../utils/create-model-from-table";
+import { createXrayDashboardFromModel } from "../utils/xray-models";
 
 export const createModelsAndXrays: CliStepMethod = async state => {
   const { instanceUrl = "", databaseId, cookie = "", tables = [] } = state;
@@ -17,7 +16,7 @@ export const createModelsAndXrays: CliStepMethod = async state => {
 
   try {
     // Create a model for each table
-    const modelIds = await Promise.all(
+    const models = await Promise.all(
       tables.map(table =>
         createModelFromTable({
           table,
@@ -30,23 +29,23 @@ export const createModelsAndXrays: CliStepMethod = async state => {
 
     spinner.start("X-raying your data to create dashboards...");
 
-    const dashboardIds: DashboardId[] = [];
+    const dashboards: DashboardInfo[] = [];
 
     // We create dashboard sequentially to prevent multiple
     // "Automatically Generated Dashboards" collection from being created.
-    for (const modelId of modelIds) {
+    for (const model of models) {
       const dashboardId = await createXrayDashboardFromModel({
-        modelId,
+        modelId: model.modelId,
         instanceUrl,
         cookie,
       });
 
-      dashboardIds.push(dashboardId);
+      dashboards.push({ id: dashboardId, name: model.modelName });
     }
 
     spinner.succeed();
 
-    return [{ type: "done" }, { ...state, dashboardIds }];
+    return [{ type: "done" }, { ...state, dashboards }];
   } catch (error) {
     spinner.fail();
 
diff --git a/enterprise/frontend/src/embedding-sdk/cli/steps/generate-component-files.ts b/enterprise/frontend/src/embedding-sdk/cli/steps/generate-component-files.ts
new file mode 100644
index 0000000000000000000000000000000000000000..475edf60f8ae073da316e5bca8d93b01e454403f
--- /dev/null
+++ b/enterprise/frontend/src/embedding-sdk/cli/steps/generate-component-files.ts
@@ -0,0 +1,67 @@
+import fs from "fs/promises";
+
+import { input } from "@inquirer/prompts";
+
+import { ANALYTICS_CSS_SNIPPET } from "../snippets/analytics-css-snippet";
+import type { CliStepMethod } from "../types/cli";
+import { getComponentSnippets } from "../utils/get-component-snippets";
+import { printError, printSuccess } from "../utils/print";
+
+export const generateReactComponentFiles: CliStepMethod = async state => {
+  const { instanceUrl, apiKey, dashboards = [] } = state;
+
+  if (!instanceUrl || !apiKey) {
+    return [
+      { type: "error", message: "Missing instance URL or API key." },
+      state,
+    ];
+  }
+
+  let path: string;
+
+  // eslint-disable-next-line no-constant-condition -- ask until user provides a valid path
+  while (true) {
+    path = await input({
+      message: "Where do you want to save the example React components?",
+      default: "./components/metabase",
+    });
+
+    // Create a directory if it doesn't already exist.
+    try {
+      await fs.mkdir(path, { recursive: true });
+      break;
+    } catch (error) {
+      printError(
+        `The current path is not writeable. Please pick a different path.`,
+      );
+    }
+  }
+
+  const sampleComponents = getComponentSnippets({
+    instanceUrl,
+    apiKey,
+    dashboards,
+  });
+
+  // Generate sample components files in the specified directory.
+  for (const { name, content } of sampleComponents) {
+    await fs.writeFile(`${path}/${name}.jsx`, content);
+  }
+
+  // Generate analytics.css sample styles.
+  await fs.writeFile(`${path}/analytics.css`, ANALYTICS_CSS_SNIPPET);
+
+  // Generate index.js file with all the component exports.
+  const exportIndexContent = sampleComponents
+    .map(file => `export * from "./${file.name}";`)
+    .join("\n")
+    .trim();
+
+  await fs.writeFile(`${path}/index.js`, exportIndexContent);
+
+  printSuccess(
+    `Generated example React components files in "${path}". You can import the <AnalyticsPage /> component in your React app from this path.`,
+  );
+
+  return [{ type: "done" }, state];
+};
diff --git a/enterprise/frontend/src/embedding-sdk/cli/steps/generate-credentials.ts b/enterprise/frontend/src/embedding-sdk/cli/steps/generate-credentials.ts
index 59cd4d9e00a100ad9da1753ad2fef4edd6539648..73b5bb044f8f7e851037f931abd2202fff96cc95 100644
--- a/enterprise/frontend/src/embedding-sdk/cli/steps/generate-credentials.ts
+++ b/enterprise/frontend/src/embedding-sdk/cli/steps/generate-credentials.ts
@@ -2,11 +2,13 @@ import fs from "fs/promises";
 
 import { input } from "@inquirer/prompts";
 
-import type { CliStepMethod } from "embedding-sdk/cli/types/cli";
-import { generateRandomDemoPassword } from "embedding-sdk/cli/utils/generate-password";
-import { OUTPUT_STYLES, printEmptyLines } from "embedding-sdk/cli/utils/print";
 import { isEmail } from "metabase/lib/email";
 
+import type { CliStepMethod } from "../types/cli";
+import { addFileToGitIgnore } from "../utils/add-file-to-git-ignore";
+import { generateRandomDemoPassword } from "../utils/generate-password";
+import { OUTPUT_STYLES, printEmptyLines } from "../utils/print";
+
 export const generateCredentials: CliStepMethod = async state => {
   printEmptyLines();
   const email = await input({
@@ -18,9 +20,13 @@ export const generateCredentials: CliStepMethod = async state => {
 
   const password = generateRandomDemoPassword();
 
+  const credentialFile = "METABASE_LOGIN.json";
+
+  await addFileToGitIgnore(credentialFile);
+
   // Store the login credentials to a file.
   await fs.writeFile(
-    "./METABASE_LOGIN.json",
+    `./${credentialFile}`,
     JSON.stringify({ email, password }, null, 2),
   );
 
diff --git a/enterprise/frontend/src/embedding-sdk/cli/steps/get-code-sample.ts b/enterprise/frontend/src/embedding-sdk/cli/steps/get-code-sample.ts
deleted file mode 100644
index be3fb11329f89a7c85a50980545d8f1d6031d224..0000000000000000000000000000000000000000
--- a/enterprise/frontend/src/embedding-sdk/cli/steps/get-code-sample.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import clipboard from "clipboardy";
-import toggle from "inquirer-toggle";
-
-import { getCodeSample } from "embedding-sdk/cli/constants/code-sample";
-import type { CliStepMethod } from "embedding-sdk/cli/types/cli";
-import {
-  printEmptyLines,
-  printInfo,
-  printSuccess,
-} from "embedding-sdk/cli/utils/print";
-
-export const generateCodeSample: CliStepMethod = async state => {
-  if (!state.instanceUrl || !state.apiKey) {
-    return [
-      {
-        type: "error",
-        message: "Missing instance URL or API key",
-      },
-      state,
-    ];
-  }
-
-  printEmptyLines(1);
-  printSuccess(
-    "API key generated successfully. Here's a code sample to embed the Metabase SDK in your React application:",
-  );
-
-  const codeSample = getCodeSample(state.instanceUrl, state.apiKey);
-  printEmptyLines();
-  printInfo(codeSample.trim());
-  printEmptyLines();
-
-  const shouldCopyToClipboard = await toggle({
-    message: "Would you like to copy the code to your clipboard?",
-    default: true,
-  });
-
-  if (shouldCopyToClipboard) {
-    await clipboard.write(codeSample);
-    printSuccess(
-      "Code copied to clipboard. Paste it into your React application.",
-    );
-  } else {
-    printSuccess(
-      "Paste the code above into your React application to embed Metabase.",
-    );
-    printInfo(
-      "Then, put <Analytics /> in your component to view the dashboard!",
-    );
-  }
-  return [
-    {
-      type: "done",
-    },
-    state,
-  ];
-};
diff --git a/enterprise/frontend/src/embedding-sdk/cli/steps/index.ts b/enterprise/frontend/src/embedding-sdk/cli/steps/index.ts
index 33aca04ce5720bda3db03c5056f347e5a686fe2c..0d6dd3021fc3bc14cbbaa6c1174605c45257aa11 100644
--- a/enterprise/frontend/src/embedding-sdk/cli/steps/index.ts
+++ b/enterprise/frontend/src/embedding-sdk/cli/steps/index.ts
@@ -2,7 +2,6 @@ export * from "./add-embedding-token";
 export * from "./check-docker-running";
 export * from "./create-api-key";
 export * from "./generate-credentials";
-export * from "./get-code-sample";
 export * from "./poll-metabase-instance";
 export * from "./setup-metabase-instance";
 export * from "./show-metabase-cli-title";
@@ -12,3 +11,4 @@ export * from "./check-sdk-available";
 export * from "./add-database-connection";
 export * from "./pick-database-tables";
 export * from "./create-models-and-xrays";
+export * from "./generate-component-files";
diff --git a/enterprise/frontend/src/embedding-sdk/cli/steps/setup-metabase-instance.ts b/enterprise/frontend/src/embedding-sdk/cli/steps/setup-metabase-instance.ts
index 5d3931c810b31beaa780dc117afa04ee030e0a13..4558406e3a764b95db2f70ad4555c3fbc1b1bcfb 100644
--- a/enterprise/frontend/src/embedding-sdk/cli/steps/setup-metabase-instance.ts
+++ b/enterprise/frontend/src/embedding-sdk/cli/steps/setup-metabase-instance.ts
@@ -94,6 +94,7 @@ export const setupMetabaseInstance: CliStepMethod = async state => {
 
     if (!res.ok) {
       const errorMessage = await res.text();
+      spinner.fail();
 
       // Error message: The /api/setup route can only be used to create the first user, however a user currently exists.
       if (errorMessage.includes("a user currently exists")) {
@@ -162,6 +163,7 @@ export const setupMetabaseInstance: CliStepMethod = async state => {
 
     if (!res.ok) {
       const errorMessage = await res.text();
+      spinner.fail();
 
       if (errorMessage.includes("Unauthenticated")) {
         return onInstanceConfigured();
diff --git a/enterprise/frontend/src/embedding-sdk/cli/types/cli.ts b/enterprise/frontend/src/embedding-sdk/cli/types/cli.ts
index cf9b36a8271b27295da68daf294e4fb25e7a07f0..bee2148e92d4472487d840792d072ac416721b61 100644
--- a/enterprise/frontend/src/embedding-sdk/cli/types/cli.ts
+++ b/enterprise/frontend/src/embedding-sdk/cli/types/cli.ts
@@ -1,5 +1,7 @@
 import type { CLI_STEPS } from "embedding-sdk/cli/run";
-import type { DashboardId, Settings, Table } from "metabase-types/api";
+import type { Settings, Table } from "metabase-types/api";
+
+import type { DashboardInfo } from "../types/dashboard";
 
 export type CliState = Partial<{
   port: number;
@@ -17,8 +19,8 @@ export type CliState = Partial<{
   /** Database tables selected by the user */
   tables: Table[];
 
-  /** IDs of auto-generated dashboards */
-  dashboardIds: DashboardId[];
+  /** IDs and names of auto-generated dashboards */
+  dashboards: DashboardInfo[];
 }>;
 
 export type CliError = {
diff --git a/enterprise/frontend/src/embedding-sdk/cli/types/dashboard.ts b/enterprise/frontend/src/embedding-sdk/cli/types/dashboard.ts
new file mode 100644
index 0000000000000000000000000000000000000000..27e710a8f124874ea7148242b3ec619cab533652
--- /dev/null
+++ b/enterprise/frontend/src/embedding-sdk/cli/types/dashboard.ts
@@ -0,0 +1,3 @@
+import type { DashboardId } from "metabase-types/api";
+
+export type DashboardInfo = { id: DashboardId; name: string };
diff --git a/enterprise/frontend/src/embedding-sdk/cli/utils/add-file-to-git-ignore.ts b/enterprise/frontend/src/embedding-sdk/cli/utils/add-file-to-git-ignore.ts
new file mode 100644
index 0000000000000000000000000000000000000000..22245a05e50dafc3fa803d4a69a638f162af122c
--- /dev/null
+++ b/enterprise/frontend/src/embedding-sdk/cli/utils/add-file-to-git-ignore.ts
@@ -0,0 +1,23 @@
+import fs from "fs/promises";
+
+const GITIGNORE_PATH = ".gitignore";
+
+/**
+ * Adds the credential file to .gitignore if exists.
+ */
+export async function addFileToGitIgnore(fileName: string) {
+  try {
+    // Check if .gitignore exists
+    await fs.access(GITIGNORE_PATH);
+
+    let gitignoreContent = await fs.readFile(GITIGNORE_PATH, "utf-8");
+
+    // If the credential file is not in .gitignore, add it.
+    if (!gitignoreContent.includes(fileName)) {
+      gitignoreContent += `\n${fileName}`;
+      await fs.writeFile(GITIGNORE_PATH, gitignoreContent);
+    }
+  } catch (error) {
+    // skip if .gitignore does not exist
+  }
+}
diff --git a/enterprise/frontend/src/embedding-sdk/cli/utils/create-model-from-table.ts b/enterprise/frontend/src/embedding-sdk/cli/utils/create-model-from-table.ts
index fe95dc5eb9af814261ca227206f4500b64d65c48..19c4eb1c934b1ce28d37180b6ffcf199d090533c 100644
--- a/enterprise/frontend/src/embedding-sdk/cli/utils/create-model-from-table.ts
+++ b/enterprise/frontend/src/embedding-sdk/cli/utils/create-model-from-table.ts
@@ -55,5 +55,5 @@ export async function createModelFromTable(options: Options) {
 
   const { id: modelId } = (await res.json()) as { id: number };
 
-  return modelId;
+  return { modelId, modelName: table.display_name };
 }
diff --git a/enterprise/frontend/src/embedding-sdk/cli/utils/get-component-snippets.ts b/enterprise/frontend/src/embedding-sdk/cli/utils/get-component-snippets.ts
new file mode 100644
index 0000000000000000000000000000000000000000..38e009295981e534f22ebbbec9a0f0c17112b509
--- /dev/null
+++ b/enterprise/frontend/src/embedding-sdk/cli/utils/get-component-snippets.ts
@@ -0,0 +1,46 @@
+import {
+  getAnalyticsDashboardSnippet,
+  getMetabaseProviderSnippet,
+  THEME_SWITCHER_SNIPPET,
+  ANALYTICS_PAGE_SNIPPET,
+} from "../snippets";
+import type { DashboardInfo } from "../types/dashboard";
+
+interface Options {
+  instanceUrl: string;
+  apiKey: string;
+  dashboards: DashboardInfo[];
+}
+
+export function getComponentSnippets(options: Options) {
+  const { instanceUrl, apiKey, dashboards } = options;
+
+  const analyticsDashboardSnippet = getAnalyticsDashboardSnippet(
+    instanceUrl,
+    dashboards,
+  ).trim();
+
+  const metabaseProviderSnippet = getMetabaseProviderSnippet(
+    instanceUrl,
+    apiKey,
+  ).trim();
+
+  return [
+    {
+      name: "metabase-provider",
+      content: metabaseProviderSnippet,
+    },
+    {
+      name: "analytics-dashboard",
+      content: analyticsDashboardSnippet,
+    },
+    {
+      name: "theme-switcher",
+      content: THEME_SWITCHER_SNIPPET.trim(),
+    },
+    {
+      name: "analytics-page",
+      content: ANALYTICS_PAGE_SNIPPET.trim(),
+    },
+  ];
+}
diff --git a/enterprise/frontend/src/embedding-sdk/cli/utils/xray-models.ts b/enterprise/frontend/src/embedding-sdk/cli/utils/xray-models.ts
index 33294fff445cb74be242af6ca8d21285ebaa415e..e60f00ed92c159be85489c9aff286d6a9adcc6f5 100644
--- a/enterprise/frontend/src/embedding-sdk/cli/utils/xray-models.ts
+++ b/enterprise/frontend/src/embedding-sdk/cli/utils/xray-models.ts
@@ -1,5 +1,5 @@
 import { uuid } from "metabase/lib/uuid";
-import type { Dashboard } from "metabase-types/api";
+import type { Dashboard, DashboardId } from "metabase-types/api";
 
 import { propagateErrorResponse } from "./propagate-error-response";
 
@@ -10,7 +10,9 @@ interface Options {
   instanceUrl: string;
 }
 
-export async function createXrayDashboardFromModel(options: Options) {
+export async function createXrayDashboardFromModel(
+  options: Options,
+): Promise<DashboardId> {
   const { modelId, instanceUrl, cookie = "" } = options;
 
   // Queries an auto-generated dashboard layout for the model