Daily Agent
Setup GuideAdmin GuideUser GuideCustomization

Customization Guide

How to rebrand, retheme, extend, and customize the app.

Branding

App name

Set NEXT_PUBLIC_SITE_NAME to change the name everywhere: the sidebar, browser tab title, PWA manifest, and meta tags. Defaults to Daily Agent.

App description

Set NEXT_PUBLIC_SITE_DESCRIPTION to customize the meta description used for SEO and the PWA install prompt.

Icons

Replace the following files in /public/ with your own images:

Favicon

Replace /public/favicon.ico with your own favicon.

Theming

Built-in themes

Dark (default), Light, and System. Users can toggle in the sidebar footer or Settings.

Modifying colors

All colors are CSS variables defined in src/app/globals.css. Edit the variable values inside the :root (light) and [data-theme="dark"] (dark) selectors.

/* globals.css */
:root {
  --color-bg: #ffffff;
  --color-bg-secondary: #f5f5f5;
  --color-bg-card: #fafafa;
  --color-text: #111111;
  --color-text-secondary: #555555;
  --color-accent: #6366f1;
  --color-accent-hover: #4f46e5;
  --color-accent-dim: #eef2ff;
  --color-border: #e5e7eb;
}

[data-theme="dark"] {
  --color-bg: #0f0f0f;
  --color-bg-secondary: #1a1a1a;
  --color-bg-card: #161616;
  --color-text: #f5f5f5;
  --color-text-secondary: #a3a3a3;
  --color-accent: #818cf8;
  --color-accent-hover: #6366f1;
  --color-accent-dim: #1e1b4b;
  --color-border: #2a2a2a;
}

Changing the accent color

Update --color-accent and --color-accent-hover in both the light and dark theme blocks. All buttons, links, and highlights reference these variables — no component changes needed.

System Prompt

Default behavior

The default system prompt produces a direct, opinionated AI with no filler phrases. It includes tool instructions and context injection when those features are enabled.

Customization levels

Prompts are layered in priority order (highest to lowest):

  1. Conversation-level prompt
  2. Project prompt
  3. User profile prompt
  4. Default system prompt

Memory notes from the user profile are always appended regardless of which level is active.

Editing the default

Edit src/lib/system-prompt.ts. Changes require redeployment.

Adding Tools

Tools run in Agent Mode. Each tool needs an entry in the definitions list, an executor implementation, and registration in the API route.

1. Define the tool

Add to the PRODUCTIVITY_TOOLS array in src/lib/tools/definitions.ts:

{
  name: "get_my_data",
  description: "Retrieves the user's custom data.",
  input_schema: {
    type: "object",
    properties: {
      limit: {
        type: "number",
        description: "Maximum number of records to return.",
      },
    },
    required: [],
  },
}

2. Mark as read-only (if applicable)

Add the tool name to the READ_ONLY_TOOLS set in src/lib/tools/definitions.ts if it never writes data. Read-only tools execute automatically without user approval.

export const READ_ONLY_TOOLS = new Set([
  // existing tools...
  "get_my_data",
]);

3. Implement the executor

Add a case to the executor switch in src/lib/tools/executor.ts:

case "get_my_data": {
  const { limit = 10 } = input as { limit?: number };
  const { data, error } = await supabase
    .from("my_table")
    .select("*")
    .eq("user_id", userId)
    .limit(limit);
  if (error) throw error;
  return data;
}

4. Register in the API route

Add the tool name to the ALLOWED_TOOLS array in src/app/api/chat/tool-execute/route.ts:

const ALLOWED_TOOLS = [
  // existing tools...
  "get_my_data",
];

5. Update tool instructions (optional)

Add a description of your tool to TOOL_SYSTEM_INSTRUCTIONS in src/lib/tools/definitions.ts so the AI knows when to use it.

Database Schema

Adding tables

  1. Write your SQL in supabase/migrations/schema.sql
  2. Run it in the Supabase SQL Editor
  3. Add the corresponding TypeScript types to src/types/database.ts

RLS policy pattern

Every user-owned table should follow this standard RLS pattern:

-- Enable RLS
ALTER TABLE my_table ENABLE ROW LEVEL SECURITY;

-- Users can only see their own rows
CREATE POLICY "Users can view own data"
  ON my_table FOR SELECT
  USING (auth.uid() = user_id);

-- Users can only insert their own rows
CREATE POLICY "Users can insert own data"
  ON my_table FOR INSERT
  WITH CHECK (auth.uid() = user_id);

-- Users can only update their own rows
CREATE POLICY "Users can update own data"
  ON my_table FOR UPDATE
  USING (auth.uid() = user_id);

-- Users can only delete their own rows
CREATE POLICY "Users can delete own data"
  ON my_table FOR DELETE
  USING (auth.uid() = user_id);

Adding Pages

Protected pages

Create your page at src/app/(protected)/your-page/page.tsx. The (protected) route group is handled by middleware — any page inside it automatically requires authentication.

Sidebar

Add navigation links by editing src/components/Sidebar.tsx. Match the existing link pattern for consistent styling.

Mobile nav

Add items to the mobile bottom navigation by editing src/components/BottomNav.tsx. Keep the item count low — the bar has limited space on small screens.

API Routes

Standard route pattern

All API routes follow this pattern: authenticate the user, query Supabase, return the result.

// src/app/api/my-feature/route.ts
import { createServerClient } from "@/lib/supabase/server";
import { NextResponse } from "next/server";

export async function GET() {
  const supabase = createServerClient();

  const {
    data: { user },
    error: authError,
  } = await supabase.auth.getUser();

  if (authError || !user) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  const { data, error } = await supabase
    .from("my_table")
    .select("*")
    .eq("user_id", user.id);

  if (error) {
    return NextResponse.json({ error: error.message }, { status: 500 });
  }

  return NextResponse.json(data);
}

Auth check

Always use supabase.auth.getUser() with the server-side client (initialized with cookies). Never trust client-supplied user IDs.

Rate limiting

Import and call checkRateLimit at the top of any route that calls AI providers or performs expensive operations:

import { checkRateLimit } from "@/lib/rate-limit";

const rateLimitResult = await checkRateLimit(user.id, "chat");
if (!rateLimitResult.allowed) {
  return NextResponse.json(
    { error: "Rate limit exceeded. Please wait before trying again." },
    { status: 429 }
  );
}