diff --git a/README.md b/README.md
index e8960ed4b23..5f99167688b 100644
--- a/README.md
+++ b/README.md
@@ -119,6 +119,47 @@ This will render as:
The suggestions are rendered in a new panel to the right of the agent cell. These are generated as a background task, so they can happen after the agent cell has returned its response.
+## Other Fork Modifications 🔧
+
+This fork includes additional server configuration options for enhanced security and control in shared environments:
+
+### UI Restrictions
+
+**Disable Panels**: Hide specific panels from the sidebar and navigation menus.
+```toml
+# In .marimo.toml
+[server]
+disabled_panels = ["chat", "secrets", "feedback", "files"]
+```
+```bash
+# Via CLI
+marimo edit --disabled-panels "chat,secrets,feedback"
+```
+
+Available panels to disable: `files`, `errors`, `variables`, `outline`, `dependencies`, `tracing`, `packages`, `documentation`, `snippets`, `datasources`, `scratchpad`, `chat`, `agents`, `secrets`, `logs`, `suggestions`, `feedback`.
+
+**Disable Terminal**: Completely disable terminal access in the UI.
+```toml
+# In .marimo.toml
+[server]
+disable_terminal = true
+```
+```bash
+# Via CLI
+marimo edit --disable-terminal
+```
+
+**Disable Package Installation**: Hide package installation features while keeping package viewing.
+```toml
+# In .marimo.toml
+[server]
+disable_package_installation = true
+```
+```bash
+# Via CLI
+marimo edit --disable-package-installation
+```
+
---
# Original README
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 1d93de836c8..c7fd1737925 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -9,10 +9,10 @@ RUN useradd -m appuser
WORKDIR /app
-ARG marimo_version=0.12.8
+ARG marimo_version=0.16.5.1
ENV MARIMO_SKIP_UPDATE_CHECK=1
ENV UV_SYSTEM_PYTHON=1
-RUN uv pip install --no-cache-dir marimo==${marimo_version} && \
+RUN uv pip install --no-cache-dir marimo_agents==${marimo_version} && \
mkdir -p /app/data && \
chown -R appuser:appuser /app
diff --git a/docker/Dockerfile.local b/docker/Dockerfile.local
new file mode 100644
index 00000000000..014c74038b3
--- /dev/null
+++ b/docker/Dockerfile.local
@@ -0,0 +1,67 @@
+# syntax=docker/dockerfile:1.12
+FROM node:20-slim AS frontend-builder
+
+# Build frontend assets
+WORKDIR /app
+COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.json turbo.json ./
+COPY frontend/package.json frontend/tsconfig.json frontend/vite.config.mts frontend/tailwind.config.cjs frontend/postcss.config.cjs ./frontend/
+COPY packages/*/package.json ./packages/*/
+COPY patches ./patches
+
+# Install pnpm and dependencies
+RUN npm install -g pnpm
+RUN pnpm install --frozen-lockfile
+
+# Copy frontend source and build
+COPY frontend ./frontend
+COPY packages ./packages
+RUN cd frontend && pnpm build
+
+# Python build stage
+FROM python:3.13-slim AS python-builder
+
+# Make `uv` available
+COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
+
+WORKDIR /app
+
+# Copy Python source
+COPY pyproject.toml ./
+COPY marimo ./marimo
+COPY README.md LICENSE ./
+
+# Copy built frontend assets from previous stage
+COPY --from=frontend-builder /app/frontend/dist ./marimo/_static
+
+# Install the package
+ENV UV_SYSTEM_PYTHON=1
+RUN uv pip install --no-cache-dir -e .
+
+# Runtime stage
+FROM python:3.13-slim AS runtime
+
+# Make `uv` available
+COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
+
+# Create non-root user
+RUN useradd -m appuser
+
+# Copy installed package from builder
+COPY --from=python-builder /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.13/site-packages
+COPY --from=python-builder /usr/local/bin /usr/local/bin
+
+WORKDIR /app
+
+# Copy tutorials
+COPY --chown=appuser:appuser marimo/_tutorials tutorials
+RUN rm -rf tutorials/__init__.py
+
+# Set up directories and permissions
+RUN mkdir -p /app/data && chown -R appuser:appuser /app
+
+ENV PORT=8080
+EXPOSE $PORT
+ENV HOST=0.0.0.0
+ENV MARIMO_SKIP_UPDATE_CHECK=1
+
+CMD ["marimo", "edit", "--no-token", "-p", "8080", "--host", "0.0.0.0"]
\ No newline at end of file
diff --git a/docker/Dockerfile.simple b/docker/Dockerfile.simple
new file mode 100644
index 00000000000..6490b28a877
--- /dev/null
+++ b/docker/Dockerfile.simple
@@ -0,0 +1,35 @@
+# syntax=docker/dockerfile:1.12
+FROM python:3.13-slim AS runtime
+
+# Make `uv` available
+COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
+
+WORKDIR /app
+
+# Copy Python source
+COPY pyproject.toml README.md LICENSE ./
+COPY marimo ./marimo
+
+# Install the package with pre-built frontend assets
+ENV UV_SYSTEM_PYTHON=1
+RUN uv pip install --no-cache-dir -e .
+
+# Create non-root user
+RUN useradd -m appuser
+
+# Copy tutorials
+COPY --chown=appuser:appuser marimo/_tutorials tutorials
+RUN rm -rf tutorials/__init__.py
+
+# Set up directories and permissions
+RUN mkdir -p /app/data && chown -R appuser:appuser /app
+
+ENV PORT=8080
+EXPOSE $PORT
+ENV HOST=0.0.0.0
+ENV MARIMO_SKIP_UPDATE_CHECK=1
+
+# Switch to non-root user
+USER appuser
+
+CMD ["marimo", "edit", "--no-token", "-p", "8080", "--host", "0.0.0.0"]
\ No newline at end of file
diff --git a/docker/README_AGENTS.md b/docker/README_AGENTS.md
new file mode 100644
index 00000000000..1d2c5ebb568
--- /dev/null
+++ b/docker/README_AGENTS.md
@@ -0,0 +1,223 @@
+# Marimo Agents Docker Setup
+
+This directory contains Docker configurations and scripts for building and running marimo with agents functionality.
+
+## Overview
+
+The marimo-agents fork includes AI agent functionality with a complete frontend. This setup provides Docker containers that include both the Python backend and the bundled frontend assets.
+
+## Architecture
+
+The build process follows these key steps:
+
+1. **Frontend Build**: Build the React/TypeScript frontend locally using pnpm
+2. **Asset Bundling**: Copy frontend assets to `marimo/_static/` for packaging
+3. **Docker Build**: Create container with Python package including bundled assets
+4. **Distribution**: Publish to PyPI with complete frontend included
+
+## Files
+
+- `Dockerfile.simple` - Simplified Docker build that uses pre-built frontend assets
+- `Dockerfile.local` - Full Docker build with frontend compilation (may have dependency issues)
+- `build_agents.sh` - Complete build script for marimo-agents
+- `run_agents.sh` - Script to run the marimo-agents Docker container
+- `README_AGENTS.md` - This documentation
+
+## Quick Start
+
+### Option 1: Use Pre-built Package from PyPI
+
+```bash
+# Build Docker image using published package
+docker build -t marimo-agents-simple -f docker/Dockerfile.simple .
+
+# Run the container
+docker run -p 8081:8080 marimo-agents-simple
+
+# Access marimo at http://localhost:8081
+```
+
+### Option 2: Build Complete Package Locally
+
+```bash
+# Run the complete build process
+./docker/build_agents.sh
+
+# Run the container
+./docker/run_agents.sh
+
+# Access marimo at http://localhost:8081
+```
+
+## Build Process Details
+
+### 1. Frontend Build
+
+The frontend is built using pnpm and Turbo:
+
+```bash
+cd frontend
+pnpm install
+pnpm build
+```
+
+This generates ~36MB of assets including:
+
+- Main JavaScript bundle (~629KB)
+- React Plotly bundle (~4.7MB)
+- Node SQL Parser bundle (~2.6MB)
+- Hundreds of additional language and theme assets
+
+### 2. Asset Integration
+
+Frontend assets are copied to the Python package:
+
+```bash
+bash scripts/buildfrontend.sh
+```
+
+This script:
+
+- Removes old static files from `marimo/_static/`
+- Copies new built assets from `frontend/dist/`
+- Results in ~36MB of bundled frontend assets
+
+### 3. Package Building
+
+The Python package is built with bundled assets:
+
+```bash
+python -m build
+```
+
+This creates a ~33MB wheel file containing both Python code and frontend assets.
+
+### 4. Docker Image
+
+The Docker image includes:
+
+- Python 3.11+ runtime
+- Complete marimo-agents package
+- All frontend assets
+- Agent functionality
+
+## Docker Images
+
+### Dockerfile.simple (Recommended)
+
+Uses the pre-built package from PyPI or local wheel:
+
+```dockerfile
+FROM python:3.11-slim
+RUN pip install marimo-agents
+EXPOSE 8080
+CMD ["marimo", "edit", "--host", "0.0.0.0", "--port", "8080", "--no-token"]
+```
+
+**Pros:**
+
+- Fast build time
+- Reliable (uses tested package)
+- Small final image
+
+**Cons:**
+
+- Requires package to be published or locally built
+
+### Dockerfile.local
+
+Builds everything from source including frontend:
+
+```dockerfile
+# Multi-stage build with Node.js and Python
+```
+
+**Pros:**
+
+- Self-contained build
+- No external dependencies
+
+**Cons:**
+
+- Complex dependency resolution
+- Longer build time
+- May fail on frontend dependencies
+
+## Agent Functionality
+
+The Docker image includes full agent support:
+
+```python
+import marimo as mo
+
+# Register an agent
+def my_agent(query: str) -> str:
+ return f"Agent response to: {query}"
+
+mo.ai.agents.register_agent(my_agent, name="my-agent")
+
+# Use in cells
+result = mo.ai.agents.run_agent("my-agent", "Hello!")
+```
+
+## Troubleshooting
+
+### Frontend Build Issues
+
+If local frontend build fails:
+
+```bash
+# Clean and rebuild
+cd frontend
+rm -rf node_modules dist
+pnpm install
+pnpm build
+```
+
+### Docker Build Issues
+
+If Docker build fails:
+
+1. Ensure frontend assets are built locally first
+2. Use `Dockerfile.simple` instead of `Dockerfile.local`
+3. Check that `marimo/_static/assets` contains ~36MB of files
+
+### Container Runtime Issues
+
+If container won't start:
+
+1. Check Python version compatibility (requires 3.10+)
+2. Verify frontend assets are included
+3. Check port conflicts (default 8081)
+
+## Development Workflow
+
+For active development:
+
+1. Make code changes
+2. Rebuild frontend: `cd frontend && pnpm build`
+3. Copy assets: `bash scripts/buildfrontend.sh`
+4. Rebuild Docker: `docker build -t marimo-agents-simple -f docker/Dockerfile.simple .`
+5. Test: `docker run -p 8081:8080 marimo-agents-simple`
+
+## Production Deployment
+
+For production use:
+
+1. Build and publish package: `python -m build && hatch publish`
+2. Use published package in Dockerfile.simple
+3. Deploy with proper networking and persistence configurations
+
+## Package Sizes
+
+- Frontend assets: ~36MB
+- Python wheel: ~33MB
+- Docker image: ~150MB (with Python runtime)
+- Published PyPI package: ~35MB
+
+## Compatibility
+
+- **Python**: 3.10+ (uses modern union syntax `str | None`)
+- **Node.js**: 20+ (for frontend build)
+- **Docker**: Any recent version
+- **Browsers**: Modern browsers with ES2020+ support
diff --git a/docker/build_agents.sh b/docker/build_agents.sh
new file mode 100755
index 00000000000..511d863fc8b
--- /dev/null
+++ b/docker/build_agents.sh
@@ -0,0 +1,89 @@
+#!/bin/bash
+set -e
+
+echo "🚀 Building marimo-agents complete package..."
+
+# Check if we're in the right directory
+if [[ ! -f "pyproject.toml" ]]; then
+ echo "❌ Error: Must run from marimo project root directory"
+ exit 1
+fi
+
+# Check if frontend directory exists
+if [[ ! -d "frontend" ]]; then
+ echo "❌ Error: frontend directory not found"
+ exit 1
+fi
+
+echo "📦 Step 1: Installing frontend dependencies..."
+cd frontend
+if ! command -v pnpm &> /dev/null; then
+ echo "❌ Error: pnpm not found. Please install pnpm first:"
+ echo " npm install -g pnpm"
+ exit 1
+fi
+
+pnpm install
+
+echo "🔨 Step 2: Building frontend assets..."
+pnpm build
+
+echo "📁 Frontend build completed. Assets generated in frontend/dist/"
+
+cd ..
+
+echo "📋 Step 3: Copying frontend assets to marimo/_static/..."
+if [[ ! -f "scripts/buildfrontend.sh" ]]; then
+ echo "❌ Error: scripts/buildfrontend.sh not found"
+ exit 1
+fi
+
+bash scripts/buildfrontend.sh
+
+echo "✅ Frontend assets copied to marimo/_static/"
+
+echo "📦 Step 4: Building Python package..."
+if ! command -v python &> /dev/null; then
+ echo "❌ Error: python not found"
+ exit 1
+fi
+
+# Install build if not available
+if ! python -c "import build" 2>/dev/null; then
+ echo "Installing build package..."
+ pip install build
+fi
+
+python -m build
+
+echo "📊 Step 5: Checking package size..."
+if [[ -d "dist" ]]; then
+ latest_wheel=$(ls -t dist/*.whl | head -1)
+ if [[ -n "$latest_wheel" ]]; then
+ wheel_size=$(du -h "$latest_wheel" | cut -f1)
+ echo "✅ Package built: $latest_wheel ($wheel_size)"
+ fi
+fi
+
+echo "🐳 Step 6: Building Docker image..."
+docker build -t marimo-agents-simple -f docker/Dockerfile.simple .
+
+echo "✅ Step 7: Verifying Docker image..."
+echo "Testing Docker image startup..."
+timeout 10s docker run --rm marimo-agents-simple marimo --help > /dev/null || true
+
+echo ""
+echo "🎉 Build complete!"
+echo ""
+echo "📋 What was built:"
+echo " • Frontend assets: $(du -sh marimo/_static/assets | cut -f1)"
+echo " • Python package: $(ls -t dist/*.whl | head -1)"
+echo " • Docker image: marimo-agents-simple"
+echo ""
+echo "🚀 To run:"
+echo " docker run -p 8081:8080 marimo-agents-simple"
+echo " Then visit: http://localhost:8081"
+echo ""
+echo "📦 To publish to PyPI:"
+echo " hatch publish"
+echo ""
\ No newline at end of file
diff --git a/frontend/src/components/editor/actions/useNotebookActions.tsx b/frontend/src/components/editor/actions/useNotebookActions.tsx
index 748ea3de4f1..41e13581ef2 100644
--- a/frontend/src/components/editor/actions/useNotebookActions.tsx
+++ b/frontend/src/components/editor/actions/useNotebookActions.tsx
@@ -55,7 +55,7 @@ import {
useCellActions,
} from "@/core/cells/cells";
import { disabledCellIds, enabledCellIds } from "@/core/cells/utils";
-import { useResolvedMarimoConfig } from "@/core/config/config";
+import { useResolvedMarimoConfig, useServerConfig } from "@/core/config/config";
import { Constants } from "@/core/constants";
import { useLayoutActions, useLayoutState } from "@/core/layout/layout";
import { useTogglePresenting } from "@/core/layout/useTogglePresenting";
@@ -97,6 +97,7 @@ export function useNotebookActions() {
const kioskMode = useAtomValue(kioskModeAtom);
const hideAllMarkdownCode = useHideAllMarkdownCode();
const [resolvedConfig] = useResolvedMarimoConfig();
+ const serverConfig = useServerConfig();
const {
updateCellConfig,
@@ -272,7 +273,7 @@ export function useNotebookActions() {
label: "Helper panel",
handle: NOOP_HANDLER,
dropdown: PANELS.flatMap(({ type, Icon, hidden }) => {
- if (hidden) {
+ if (hidden || serverConfig.disabled_panels.includes(type)) {
return [];
}
return {
@@ -512,8 +513,9 @@ export function useNotebookActions() {
icon: ,
label: "Return home",
// If file is in the url, then we ran `marimo edit`
- // without a specific file
- hidden: !location.search.includes("file"),
+ // without a specific file, or if homepage is disabled
+ hidden:
+ !location.search.includes("file") || serverConfig.disable_home_page,
handle: () => {
const withoutSearch = document.baseURI.split("?")[0];
window.open(withoutSearch, "_self");
diff --git a/frontend/src/components/editor/chrome/panels/packages-panel.tsx b/frontend/src/components/editor/chrome/panels/packages-panel.tsx
index 0e3a80b5673..70ccf839aa5 100644
--- a/frontend/src/components/editor/chrome/panels/packages-panel.tsx
+++ b/frontend/src/components/editor/chrome/panels/packages-panel.tsx
@@ -20,7 +20,7 @@ import {
} from "@/components/ui/table";
import { Tooltip } from "@/components/ui/tooltip";
import { toast } from "@/components/ui/use-toast";
-import { useResolvedMarimoConfig } from "@/core/config/config";
+import { useResolvedMarimoConfig, useServerConfig } from "@/core/config/config";
import { useRequestClient } from "@/core/network/requests";
import type { DependencyTreeNode } from "@/core/network/types";
import {
@@ -67,6 +67,7 @@ const PackageActionButton: React.FC<{
const PackagesPanel: React.FC = () => {
const [config] = useResolvedMarimoConfig();
+ const serverConfig = useServerConfig();
const packageManager = config.package_management.manager;
const { getDependencyTree, getPackageList } = useRequestClient();
@@ -104,7 +105,12 @@ const PackagesPanel: React.FC = () => {
return (
-
+ {!serverConfig.disable_package_installation && (
+
+ )}
{isTreeSupported && (
@@ -352,9 +358,10 @@ const UpgradeButton: React.FC<{
}> = ({ packageName, onSuccess }) => {
const [loading, setLoading] = React.useState(false);
const { addPackage } = useRequestClient();
+ const serverConfig = useServerConfig();
- // Hide upgrade button in WASM
- if (isWasm()) {
+ // Hide upgrade button in WASM or if package installation is disabled
+ if (isWasm() || serverConfig.disable_package_installation) {
return null;
}
@@ -389,6 +396,12 @@ const RemoveButton: React.FC<{
}> = ({ packageName, onSuccess }) => {
const [loading, setLoading] = React.useState(false);
const { removePackage } = useRequestClient();
+ const serverConfig = useServerConfig();
+
+ // Hide remove button if package installation is disabled
+ if (serverConfig.disable_package_installation) {
+ return null;
+ }
const handleRemovePackage = async () => {
try {
diff --git a/frontend/src/components/editor/chrome/wrapper/app-chrome.tsx b/frontend/src/components/editor/chrome/wrapper/app-chrome.tsx
index d04ee35434c..26af669c0ab 100644
--- a/frontend/src/components/editor/chrome/wrapper/app-chrome.tsx
+++ b/frontend/src/components/editor/chrome/wrapper/app-chrome.tsx
@@ -19,6 +19,7 @@ import { XIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { LazyMount } from "@/components/utils/lazy-mount";
import { IfCapability } from "@/core/config/if-capability";
+import { useServerConfig } from "@/core/config/config";
import { cn } from "@/utils/cn";
import { ErrorBoundary } from "../../boundary/ErrorBoundary";
import { ContextAwarePanel } from "../panels/context-aware-panel/context-aware-panel";
@@ -61,6 +62,7 @@ const LazyVariablePanel = React.lazy(() => import("../panels/variable-panel"));
export const AppChrome: React.FC
= ({ children }) => {
const { isSidebarOpen, isTerminalOpen, selectedPanel } = useChromeState();
const { setIsSidebarOpen, setIsTerminalOpen } = useChromeActions();
+ const serverConfig = useServerConfig();
const sidebarRef = React.useRef(null);
const terminalRef = React.useRef(null);
@@ -266,7 +268,9 @@ export const AppChrome: React.FC = ({ children }) => {
{appBodyPanel}
- {terminalPanel}
+
+ {!serverConfig.disable_terminal && terminalPanel}
+
diff --git a/frontend/src/components/editor/chrome/wrapper/footer.tsx b/frontend/src/components/editor/chrome/wrapper/footer.tsx
index 89d4f96ed1e..2f4e25c4537 100644
--- a/frontend/src/components/editor/chrome/wrapper/footer.tsx
+++ b/frontend/src/components/editor/chrome/wrapper/footer.tsx
@@ -6,6 +6,7 @@ import type React from "react";
import { Tooltip } from "@/components/ui/tooltip";
import { cellErrorCount } from "@/core/cells/cells";
import { IfCapability } from "@/core/config/if-capability";
+import { useServerConfig } from "@/core/config/config";
import { useHotkey } from "@/hooks/useHotkey";
import { cn } from "@/utils/cn";
import { invariant } from "@/utils/invariant";
@@ -25,6 +26,7 @@ export const Footer: React.FC = () => {
const { selectedPanel, isTerminalOpen } = useChromeState();
const { toggleApplication, toggleTerminal } = useChromeActions();
const errorCount = useAtomValue(cellErrorCount);
+ const serverConfig = useServerConfig();
const renderIcon = ({ Icon }: PanelDescriptor, className?: string) => {
return ;
@@ -50,14 +52,16 @@ export const Footer: React.FC = () => {
- toggleTerminal()}
- data-testid="footer-terminal"
- >
-
-
+ {!serverConfig.disable_terminal && (
+ toggleTerminal()}
+ data-testid="footer-terminal"
+ >
+
+
+ )}
diff --git a/frontend/src/components/editor/chrome/wrapper/sidebar.tsx b/frontend/src/components/editor/chrome/wrapper/sidebar.tsx
index 15ba9a69fbb..ceecd6e0d3e 100644
--- a/frontend/src/components/editor/chrome/wrapper/sidebar.tsx
+++ b/frontend/src/components/editor/chrome/wrapper/sidebar.tsx
@@ -6,6 +6,7 @@ import type React from "react";
import type { PropsWithChildren } from "react";
import { Tooltip } from "@/components/ui/tooltip";
import { notebookQueuedOrRunningCountAtom } from "@/core/cells/cells";
+import { useServerConfig } from "@/core/config/config";
import { cn } from "@/utils/cn";
import { FeedbackButton } from "../components/feedback-button";
import { useChromeActions, useChromeState } from "../state";
@@ -14,13 +15,17 @@ import { PANELS, type PanelDescriptor } from "../types";
export const Sidebar: React.FC = () => {
const { selectedPanel } = useChromeState();
const { toggleApplication } = useChromeActions();
+ const serverConfig = useServerConfig();
const renderIcon = ({ Icon }: PanelDescriptor, className?: string) => {
return ;
};
const sidebarItems = PANELS.filter(
- (p) => !p.hidden && p.position === "sidebar",
+ (p) =>
+ !p.hidden &&
+ p.position === "sidebar" &&
+ !serverConfig.disabled_panels.includes(p.type),
);
return (
@@ -35,11 +40,13 @@ export const Sidebar: React.FC = () => {
{renderIcon(p)}
))}
-
-
-
-
-
+ {!serverConfig.disabled_panels.includes("feedback") && (
+
+
+
+
+
+ )}
diff --git a/frontend/src/components/editor/package-alert.tsx b/frontend/src/components/editor/package-alert.tsx
index bf681563dc7..32b12adbdec 100644
--- a/frontend/src/components/editor/package-alert.tsx
+++ b/frontend/src/components/editor/package-alert.tsx
@@ -29,7 +29,7 @@ import {
useAlertActions,
useAlerts,
} from "@/core/alerts/state";
-import { useResolvedMarimoConfig } from "@/core/config/config";
+import { useResolvedMarimoConfig, useServerConfig } from "@/core/config/config";
import type { PackageInstallationStatus } from "@/core/kernel/messages";
import { useRequestClient } from "@/core/network/requests";
import { isWasm } from "@/core/wasm/utils";
@@ -79,6 +79,7 @@ export const PackageAlert: React.FC = () => {
const { packageAlert, packageLogs } = useAlerts();
const { clearPackageAlert } = useAlertActions();
const [userConfig] = useResolvedMarimoConfig();
+ const serverConfig = useServerConfig();
const [desiredPackageVersions, setDesiredPackageVersions] = useState<
Record
>({});
@@ -86,7 +87,12 @@ export const PackageAlert: React.FC = () => {
Record
>({});
- if (packageAlert === null) {
+ // Don't show package alerts if package installation is disabled
+ if (
+ packageAlert === null ||
+ (isMissingPackageAlert(packageAlert) &&
+ serverConfig.disable_package_installation)
+ ) {
return null;
}
diff --git a/frontend/src/core/config/config-schema.ts b/frontend/src/core/config/config-schema.ts
index c1b8007c0d0..c9edacc92d9 100644
--- a/frontend/src/core/config/config-schema.ts
+++ b/frontend/src/core/config/config-schema.ts
@@ -182,7 +182,12 @@ export const UserConfigSchema = z
})
// Pass through so that we don't remove any extra keys that the user has added.
.prefault(() => ({})),
- server: z.looseObject({}).prefault(() => ({})),
+ server: z
+ .looseObject({
+ disable_home_page: z.boolean().optional(),
+ disable_terminal: z.boolean().optional(),
+ })
+ .prefault(() => ({})),
sharing: z
.looseObject({
html: z.boolean().optional(),
@@ -218,6 +223,7 @@ export type LSPConfig = UserConfig["language_servers"];
export type DiagnosticsConfig = UserConfig["diagnostics"];
export type DisplayConfig = UserConfig["display"];
export type AiConfig = UserConfig["ai"];
+export type ServerConfig = UserConfig["server"];
export const AppTitleSchema = z.string();
export const SqlOutputSchema = z
diff --git a/frontend/src/core/config/config.ts b/frontend/src/core/config/config.ts
index c786acdeb9c..da0a956d0f0 100644
--- a/frontend/src/core/config/config.ts
+++ b/frontend/src/core/config/config.ts
@@ -3,6 +3,8 @@ import { atom, useAtom, useAtomValue, useSetAtom } from "jotai";
import { merge } from "lodash-es";
import { OverridingHotkeyProvider } from "../hotkeys/hotkeys";
import { type Platform, resolvePlatform } from "../hotkeys/shortcuts";
+import { Logger } from "../../utils/Logger";
+import { API } from "../network/api";
import { store } from "../state/jotai";
import {
type AppConfig,
@@ -48,6 +50,36 @@ export const aiAtom = atom((get) => {
return get(resolvedMarimoConfigAtom).ai;
});
+export const serverConfigAtom = atom(async () => {
+ try {
+ const response = await API.get<{
+ disable_home_page: boolean;
+ disable_terminal: boolean;
+ disable_package_installation: boolean;
+ disabled_panels: string[];
+ }>("/kernel/server_config");
+ return {
+ disable_home_page: response?.disable_home_page ?? false,
+ disable_terminal: response?.disable_terminal ?? false,
+ disable_package_installation:
+ response?.disable_package_installation ?? false,
+ disabled_panels: response?.disabled_panels ?? [],
+ browser: "default",
+ follow_symlink: false,
+ };
+ } catch (error) {
+ Logger.warn("Failed to fetch server config:", error);
+ return {
+ disable_home_page: false,
+ disable_terminal: false,
+ disable_package_installation: false,
+ disabled_panels: [],
+ browser: "default",
+ follow_symlink: false,
+ };
+ }
+});
+
export const completionAtom = atom((get) => {
return get(resolvedMarimoConfigAtom).completion;
});
@@ -74,6 +106,10 @@ export function getResolvedMarimoConfig() {
return store.get(resolvedMarimoConfigAtom);
}
+export function useServerConfig() {
+ return useAtomValue(serverConfigAtom);
+}
+
export const aiEnabledAtom = atom((get) => {
return isAiEnabled(get(resolvedMarimoConfigAtom));
});
diff --git a/frontend/src/core/islands/main.ts b/frontend/src/core/islands/main.ts
index 288ca9ad2c0..9cb7ebeeb51 100644
--- a/frontend/src/core/islands/main.ts
+++ b/frontend/src/core/islands/main.ts
@@ -183,6 +183,10 @@ export async function initialize() {
case "query-params-clear":
queryParamHandlers.clear();
return;
+ case "suggestions":
+ // Handle suggestions - this might be for AI prompts or other UI suggestions
+ // For now, we'll just log them as they may not be relevant in islands mode
+ return;
case "reconnected":
return;
default:
diff --git a/marimo/_cli/cli.py b/marimo/_cli/cli.py
index 87d3eb8c622..218d4ef3366 100644
--- a/marimo/_cli/cli.py
+++ b/marimo/_cli/cli.py
@@ -475,6 +475,36 @@ def _get_stdin_contents() -> str | None:
hidden=True,
help="Custom asset URL for loading static resources. Can include {version} placeholder.",
)
+@click.option(
+ "--disable-home-page",
+ is_flag=True,
+ default=False,
+ show_default=True,
+ type=bool,
+ help="Disable the home page, return 404 for root path when no file is specified.",
+)
+@click.option(
+ "--disable-terminal",
+ is_flag=True,
+ default=False,
+ show_default=True,
+ type=bool,
+ help="Disable terminal access in the UI and reject terminal websocket connections.",
+)
+@click.option(
+ "--disable-package-installation",
+ is_flag=True,
+ default=False,
+ show_default=True,
+ type=bool,
+ help="Disable package installation features including install input and missing package prompts.",
+)
+@click.option(
+ "--disabled-panels",
+ default="",
+ type=str,
+ help="Comma-separated list of panel names to disable (e.g., 'files,packages,chat').",
+)
@click.option(
"--timeout",
required=False,
@@ -509,6 +539,10 @@ def edit(
mcp: bool,
server_startup_command: Optional[str],
asset_url: Optional[str],
+ disable_home_page: bool,
+ disable_terminal: bool,
+ disable_package_installation: bool,
+ disabled_panels: str,
timeout: Optional[float],
name: Optional[str],
args: tuple[str, ...],
@@ -580,6 +614,10 @@ def edit(
return
GLOBAL_SETTINGS.PROFILE_DIR = profile_dir
+ GLOBAL_SETTINGS.DISABLE_HOME_PAGE = disable_home_page
+ GLOBAL_SETTINGS.DISABLE_TERMINAL = disable_terminal
+ GLOBAL_SETTINGS.DISABLE_PACKAGE_INSTALLATION = disable_package_installation
+ GLOBAL_SETTINGS.DISABLED_PANELS = disabled_panels.split(",") if disabled_panels else []
if not skip_update_check and os.getenv("MARIMO_SKIP_UPDATE_CHECK") != "1":
GLOBAL_SETTINGS.CHECK_STATUS_UPDATE = True
# Check for version updates
diff --git a/marimo/_config/config.py b/marimo/_config/config.py
index df340e88bd6..1cc40080e2f 100644
--- a/marimo/_config/config.py
+++ b/marimo/_config/config.py
@@ -213,10 +213,26 @@ class ServerConfig(TypedDict):
with Python's webbrowser module (eg, `"firefox"` or `"chrome"`)
- `follow_symlink`: if true, the server will follow symlinks it finds
inside its static assets directory.
+ - `disable_home_page`: if true, the server will return 404 for the root path
+ when no file is specified, preventing access to the home page
+ - `disable_terminal`: if true, the server will disable terminal access
+ in the UI and reject terminal websocket connections
+ - `disable_package_installation`: if true, the server will disable package
+ installation features including the install input in packages panel
+ and missing package installation prompts
+ - `disabled_panels`: list of panel names to disable in the UI.
+ Valid panel names: "files", "errors", "variables", "outline",
+ "dependencies", "tracing", "packages", "documentation", "snippets",
+ "datasources", "scratchpad", "chat", "agents", "secrets", "logs",
+ "suggestions", "feedback"
"""
browser: Union[Literal["default"], str]
follow_symlink: bool
+ disable_home_page: bool
+ disable_terminal: bool
+ disable_package_installation: bool
+ disabled_panels: list[str]
@dataclass
@@ -651,6 +667,10 @@ class PartialMarimoConfig(TypedDict, total=False):
"server": {
"browser": "default",
"follow_symlink": False,
+ "disable_home_page": False,
+ "disable_terminal": False,
+ "disable_package_installation": False,
+ "disabled_panels": [],
},
"language_servers": {
"pylsp": {
diff --git a/marimo/_config/settings.py b/marimo/_config/settings.py
index 23b928c1100..4a564d94a45 100644
--- a/marimo/_config/settings.py
+++ b/marimo/_config/settings.py
@@ -3,7 +3,7 @@
import logging
import os
-from dataclasses import dataclass
+from dataclasses import dataclass, field
@dataclass
@@ -21,6 +21,10 @@ class GlobalSettings:
IN_SECURE_ENVIRONMENT: bool = os.getenv(
"MARIMO_IN_SECURE_ENVIRONMENT", "false"
) in ("true", "1")
+ DISABLE_HOME_PAGE: bool = False
+ DISABLE_TERMINAL: bool = False
+ DISABLE_PACKAGE_INSTALLATION: bool = False
+ DISABLED_PANELS: list[str] = field(default_factory=list)
GLOBAL_SETTINGS = GlobalSettings()
diff --git a/marimo/_server/api/endpoints/assets.py b/marimo/_server/api/endpoints/assets.py
index f78e3c16312..7e087d4aa80 100644
--- a/marimo/_server/api/endpoints/assets.py
+++ b/marimo/_server/api/endpoints/assets.py
@@ -85,6 +85,18 @@ async def index(request: Request) -> HTMLResponse:
html = index_html.read_text()
if not file_key:
+ # Check if home page is disabled in user configuration or CLI flag
+ user_config = app_state.config_manager.get_user_config()
+ from marimo._config.settings import GLOBAL_SETTINGS
+
+ # Access config as dictionary since MarimoConfig is a TypedDict
+ server_config = user_config.get("server", {})
+ disable_home_page_config = server_config.get("disable_home_page", False)
+
+ if disable_home_page_config or GLOBAL_SETTINGS.DISABLE_HOME_PAGE:
+ LOGGER.debug("Home page disabled by configuration, returning 404")
+ raise HTTPException(status_code=404, detail="Home page is disabled")
+
# We don't know which file to use, so we need to render a homepage
LOGGER.debug("No file key provided, serving homepage")
html = home_page_template(
diff --git a/marimo/_server/api/endpoints/config.py b/marimo/_server/api/endpoints/config.py
index ffdc1c89229..1dd7149805a 100644
--- a/marimo/_server/api/endpoints/config.py
+++ b/marimo/_server/api/endpoints/config.py
@@ -122,3 +122,45 @@ async def handle_background_tasks() -> None:
content=asdict(SuccessResponse()),
background=background_task,
)
+
+
+@router.get("/server_config")
+@requires("edit")
+async def get_server_config(request: Request) -> JSONResponse:
+ """
+ Get server runtime configuration
+
+ responses:
+ 200:
+ description: Get the server runtime configuration
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ disable_home_page:
+ type: boolean
+ description: Whether the homepage is disabled
+ disable_terminal:
+ type: boolean
+ description: Whether terminal access is disabled
+ """
+ from marimo._cli.cli import GLOBAL_SETTINGS
+ app_state = AppState(request)
+
+ # Get the user config and check for server config
+ user_config = app_state.config_manager.get_user_config()
+ server_config = user_config.get("server", {})
+
+ # Return runtime configuration combining user config and CLI flags
+ # CLI flags override config file settings
+ return JSONResponse(
+ content={
+ "browser": server_config.get("browser", "default"),
+ "follow_symlink": server_config.get("follow_symlink", False),
+ "disable_home_page": server_config.get("disable_home_page", False) or GLOBAL_SETTINGS.DISABLE_HOME_PAGE,
+ "disable_terminal": server_config.get("disable_terminal", False) or GLOBAL_SETTINGS.DISABLE_TERMINAL,
+ "disable_package_installation": server_config.get("disable_package_installation", False) or GLOBAL_SETTINGS.DISABLE_PACKAGE_INSTALLATION,
+ "disabled_panels": server_config.get("disabled_panels", []) + GLOBAL_SETTINGS.DISABLED_PANELS,
+ }
+ )
diff --git a/pyproject.toml b/pyproject.toml
index 206b9ffbaec..3811c821b40 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "uv_build"
[project]
name = "marimo-agents"
-version = "0.16.5.0"
+version = "0.16.5.2"
description = "A Marimo concept fork with some support for LLM execution as cells"
# We try to keep dependencies to a minimum, to avoid conflicts with
# user environments;we need a very compelling reason for each dependency added.