ARCY AIv1.0
Quickstart

Manual

Install the package and wire everything up yourself. Full control over every change made to your codebase.

Use this path if you want full control over what gets added to your codebase.

Step 1: Install the package

bash
npm install @arcyai/sdk
bash
pnpm add @arcyai/sdk
bash
bun add @arcyai/sdk
bash
yarn add @arcyai/sdk

Step 2: Add your keys to .env

Get your keys from app.arcyai.com under Settings > API Keys.

Add to .env.local:

bash
NEXT_PUBLIC_ARCY_PUBLISHABLE_KEY=pk_live_...
ARCY_SECRET_KEY=sk_live_...

Add to .env:

bash
VITE_ARCY_PUBLISHABLE_KEY=pk_live_...
ARCY_SECRET_KEY=sk_live_...

The public key is used by ARCYProvider in the browser. The secret key is used by the CLI (arcy push, arcy pull, arcy status) and must never be sent to the browser.

Step 3: Wrap your app in ARCYProvider

ARCYProvider is a client component. It must wrap the entire app. Pass your public key always, and userId plus userTraits from your auth system so ARCY can tie sessions to real users in your dashboard.

ARCYProvider is a client component but your root layout.tsx is a server component. Create a thin client wrapper, read identity server-side, and pass it as props.

Create components/arcy-wrapper.tsx:

components/arcy-wrapper.tsx
"use client"
import { ARCYProvider } from "@arcyai/sdk/react"
import "@arcyai/sdk/styles"

interface ArcyWrapperProps {
  userId?: string | null
  userEmail?: string | null
  children: React.ReactNode
}

export function ArcyWrapper({ userId, userEmail, children }: ArcyWrapperProps) {
  return (
    <ARCYProvider
      publicKey={process.env.NEXT_PUBLIC_ARCY_PUBLISHABLE_KEY!}
      userId={userId ?? undefined}
      userTraits={userEmail ? { email: userEmail } : undefined}
    >
      {children}
    </ARCYProvider>
  )
}

Update app/layout.tsx:

app/layout.tsx
import { auth, currentUser } from "@clerk/nextjs/server"
import { ArcyWrapper } from "@/components/arcy-wrapper"

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const { userId } = await auth()
  const user = userId ? await currentUser() : null

  return (
    <html lang="en">
      <body>
        <ArcyWrapper
          userId={userId}
          userEmail={user?.primaryEmailAddress?.emailAddress ?? null}
        >
          {children}
        </ArcyWrapper>
      </body>
    </html>
  )
}

Create components/arcy-wrapper.tsx:

components/arcy-wrapper.tsx
"use client"
import { ARCYProvider } from "@arcyai/sdk/react"
import "@arcyai/sdk/styles"

interface ArcyWrapperProps {
  userId?: string | null
  userEmail?: string | null
  children: React.ReactNode
}

export function ArcyWrapper({ userId, userEmail, children }: ArcyWrapperProps) {
  return (
    <ARCYProvider
      publicKey={process.env.NEXT_PUBLIC_ARCY_PUBLISHABLE_KEY!}
      userId={userId ?? undefined}
      userTraits={userEmail ? { email: userEmail } : undefined}
    >
      {children}
    </ARCYProvider>
  )
}

Update app/layout.tsx:

app/layout.tsx
import { getServerSession } from "next-auth"
import { authOptions } from "@/lib/auth"
import { ArcyWrapper } from "@/components/arcy-wrapper"

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const session = await getServerSession(authOptions)

  return (
    <html lang="en">
      <body>
        <ArcyWrapper
          userId={session?.user?.id ?? null}
          userEmail={session?.user?.email ?? null}
        >
          {children}
        </ArcyWrapper>
      </body>
    </html>
  )
}

Create components/arcy-wrapper.tsx:

components/arcy-wrapper.tsx
"use client"
import { ARCYProvider } from "@arcyai/sdk/react"
import "@arcyai/sdk/styles"

interface ArcyWrapperProps {
  userId?: string | null
  userEmail?: string | null
  children: React.ReactNode
}

export function ArcyWrapper({ userId, userEmail, children }: ArcyWrapperProps) {
  return (
    <ARCYProvider
      publicKey={process.env.NEXT_PUBLIC_ARCY_PUBLISHABLE_KEY!}
      userId={userId ?? undefined}
      userTraits={userEmail ? { email: userEmail } : undefined}
    >
      {children}
    </ARCYProvider>
  )
}

Update app/layout.tsx:

app/layout.tsx
import { createServerComponentClient } from "@supabase/auth-helpers-nextjs"
import { cookies } from "next/headers"
import { ArcyWrapper } from "@/components/arcy-wrapper"

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const supabase = createServerComponentClient({ cookies })
  const { data: { user } } = await supabase.auth.getUser()

  return (
    <html lang="en">
      <body>
        <ArcyWrapper
          userId={user?.id ?? null}
          userEmail={user?.email ?? null}
        >
          {children}
        </ArcyWrapper>
      </body>
    </html>
  )
}

Firebase Auth is client-only. The wrapper reads the current user from a client hook instead of server-side.

Create components/arcy-wrapper.tsx:

components/arcy-wrapper.tsx
"use client"
import { useAuthState } from "react-firebase-hooks/auth"
import { auth } from "@/lib/firebase"
import { ARCYProvider } from "@arcyai/sdk/react"
import "@arcyai/sdk/styles"

export function ArcyWrapper({ children }: { children: React.ReactNode }) {
  const [user] = useAuthState(auth)
  return (
    <ARCYProvider
      publicKey={process.env.NEXT_PUBLIC_ARCY_PUBLISHABLE_KEY!}
      userId={user?.uid ?? undefined}
      userTraits={user?.email ? { email: user.email } : undefined}
    >
      {children}
    </ARCYProvider>
  )
}

Update app/layout.tsx:

app/layout.tsx
import { ArcyWrapper } from "@/components/arcy-wrapper"

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <ArcyWrapper>{children}</ArcyWrapper>
      </body>
    </html>
  )
}

Create components/arcy-wrapper.tsx:

components/arcy-wrapper.tsx
"use client"
import { ARCYProvider } from "@arcyai/sdk/react"
import "@arcyai/sdk/styles"

export function ArcyWrapper({ children }: { children: React.ReactNode }) {
  return (
    <ARCYProvider publicKey={process.env.NEXT_PUBLIC_ARCY_PUBLISHABLE_KEY!}>
      {children}
    </ARCYProvider>
  )
}

Update app/layout.tsx:

app/layout.tsx
import { ArcyWrapper } from "@/components/arcy-wrapper"

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <ArcyWrapper>{children}</ArcyWrapper>
      </body>
    </html>
  )
}

Add ARCYProvider directly in pages/_app.tsx using client-side auth hooks.

pages/_app.tsx
import { useAuth } from "@clerk/nextjs"
import { ARCYProvider } from "@arcyai/sdk/react"
import "@arcyai/sdk/styles"
import type { AppProps } from "next/app"

function ArcyWrapper({ children }: { children: React.ReactNode }) {
  const { userId } = useAuth()
  return (
    <ARCYProvider
      publicKey={process.env.NEXT_PUBLIC_ARCY_PUBLISHABLE_KEY!}
      userId={userId ?? undefined}
    >
      {children}
    </ARCYProvider>
  )
}

export default function App({ Component, pageProps }: AppProps) {
  return (
    <ArcyWrapper>
      <Component {...pageProps} />
    </ArcyWrapper>
  )
}
pages/_app.tsx
import { useSession } from "next-auth/react"
import { ARCYProvider } from "@arcyai/sdk/react"
import "@arcyai/sdk/styles"
import type { AppProps } from "next/app"

function ArcyWrapper({ children }: { children: React.ReactNode }) {
  const { data: session } = useSession()
  return (
    <ARCYProvider
      publicKey={process.env.NEXT_PUBLIC_ARCY_PUBLISHABLE_KEY!}
      userId={session?.user?.id ?? undefined}
      userTraits={session?.user?.email ? { email: session.user.email } : undefined}
    >
      {children}
    </ARCYProvider>
  )
}

export default function App({ Component, pageProps }: AppProps) {
  return (
    <ArcyWrapper>
      <Component {...pageProps} />
    </ArcyWrapper>
  )
}
pages/_app.tsx
import { useUser } from "@supabase/auth-helpers-react"
import { ARCYProvider } from "@arcyai/sdk/react"
import "@arcyai/sdk/styles"
import type { AppProps } from "next/app"

function ArcyWrapper({ children }: { children: React.ReactNode }) {
  const user = useUser()
  return (
    <ARCYProvider
      publicKey={process.env.NEXT_PUBLIC_ARCY_PUBLISHABLE_KEY!}
      userId={user?.id ?? undefined}
      userTraits={user?.email ? { email: user.email } : undefined}
    >
      {children}
    </ARCYProvider>
  )
}

export default function App({ Component, pageProps }: AppProps) {
  return (
    <ArcyWrapper>
      <Component {...pageProps} />
    </ArcyWrapper>
  )
}
pages/_app.tsx
import { useAuthState } from "react-firebase-hooks/auth"
import { auth } from "@/lib/firebase"
import { ARCYProvider } from "@arcyai/sdk/react"
import "@arcyai/sdk/styles"
import type { AppProps } from "next/app"

function ArcyWrapper({ children }: { children: React.ReactNode }) {
  const [user] = useAuthState(auth)
  return (
    <ARCYProvider
      publicKey={process.env.NEXT_PUBLIC_ARCY_PUBLISHABLE_KEY!}
      userId={user?.uid ?? undefined}
      userTraits={user?.email ? { email: user.email } : undefined}
    >
      {children}
    </ARCYProvider>
  )
}

export default function App({ Component, pageProps }: AppProps) {
  return (
    <ArcyWrapper>
      <Component {...pageProps} />
    </ArcyWrapper>
  )
}
pages/_app.tsx
import { ARCYProvider } from "@arcyai/sdk/react"
import "@arcyai/sdk/styles"
import type { AppProps } from "next/app"

function ArcyWrapper({ children }: { children: React.ReactNode }) {
  return (
    <ARCYProvider publicKey={process.env.NEXT_PUBLIC_ARCY_PUBLISHABLE_KEY!}>
      {children}
    </ARCYProvider>
  )
}

export default function App({ Component, pageProps }: AppProps) {
  return (
    <ArcyWrapper>
      <Component {...pageProps} />
    </ArcyWrapper>
  )
}

Add ARCYProvider in src/App.tsx using client-side auth hooks. Vite uses import.meta.env for environment variables.

src/App.tsx
import { useUser } from "@clerk/clerk-react"
import { ARCYProvider } from "@arcyai/sdk/react"
import "@arcyai/sdk/styles"

function ArcyWrapper({ children }: { children: React.ReactNode }) {
  const { user, isLoaded } = useUser()
  if (!isLoaded) return <>{children}</>
  return (
    <ARCYProvider
      publicKey={import.meta.env.VITE_ARCY_PUBLISHABLE_KEY}
      userId={user?.id ?? undefined}
      userTraits={
        user?.primaryEmailAddress
          ? { email: user.primaryEmailAddress.emailAddress }
          : undefined
      }
    >
      {children}
    </ARCYProvider>
  )
}

export default function App() {
  return (
    <ArcyWrapper>
      {/* your existing app content */}
    </ArcyWrapper>
  )
}
src/App.tsx
import { useUser } from "@supabase/auth-helpers-react"
import { ARCYProvider } from "@arcyai/sdk/react"
import "@arcyai/sdk/styles"

function ArcyWrapper({ children }: { children: React.ReactNode }) {
  const user = useUser()
  return (
    <ARCYProvider
      publicKey={import.meta.env.VITE_ARCY_PUBLISHABLE_KEY}
      userId={user?.id ?? undefined}
      userTraits={user?.email ? { email: user.email } : undefined}
    >
      {children}
    </ARCYProvider>
  )
}

export default function App() {
  return (
    <ArcyWrapper>
      {/* your existing app content */}
    </ArcyWrapper>
  )
}
src/App.tsx
import { useAuthState } from "react-firebase-hooks/auth"
import { auth } from "./lib/firebase"
import { ARCYProvider } from "@arcyai/sdk/react"
import "@arcyai/sdk/styles"

function ArcyWrapper({ children }: { children: React.ReactNode }) {
  const [user] = useAuthState(auth)
  return (
    <ARCYProvider
      publicKey={import.meta.env.VITE_ARCY_PUBLISHABLE_KEY}
      userId={user?.uid ?? undefined}
      userTraits={user?.email ? { email: user.email } : undefined}
    >
      {children}
    </ARCYProvider>
  )
}

export default function App() {
  return (
    <ArcyWrapper>
      {/* your existing app content */}
    </ArcyWrapper>
  )
}
src/App.tsx
import { ARCYProvider } from "@arcyai/sdk/react"
import "@arcyai/sdk/styles"

function ArcyWrapper({ children }: { children: React.ReactNode }) {
  return (
    <ARCYProvider publicKey={import.meta.env.VITE_ARCY_PUBLISHABLE_KEY}>
      {children}
    </ARCYProvider>
  )
}

export default function App() {
  return (
    <ArcyWrapper>
      {/* your existing app content */}
    </ArcyWrapper>
  )
}

Step 4: Add data-arcy attributes

Add data-arcy to every interactive and semantically important element in your UI. The assistant uses these IDs to highlight elements, scroll to them, and point users to the right place. More anchors = better AI accuracy.

Rules:

  • IDs must be lowercase and hyphen-separated, describing what the element does: create-project-button, not btn-1
  • Every ID must be globally unique across the codebase. No two elements may share the same value.
  • For items rendered from a list, include the item's stable identifier: project-card-{slug}, not just project-card
  • Add data-arcy to BOTH the trigger and the content inside modals or drawers. Note which are conditional in your anchor table.

What to cover (annotate all of these):

  • Every nav link and menu item
  • Every primary and secondary action button (create, save, delete, invite, upgrade)
  • Every form and its submit button
  • Every input field (search, filters, selects, toggles)
  • Every section container representing a distinct area (billing section, team list, activity feed)
  • Every empty state CTA
  • Every modal trigger and the elements inside it
  • Every entity card or row (project card, user row, invoice row)
tsx
<nav data-arcy="main-sidebar">
  <a href="/dashboard" data-arcy="nav-dashboard">Dashboard</a>
  <a href="/projects" data-arcy="nav-projects">Projects</a>
  <a href="/settings" data-arcy="nav-settings">Settings</a>
  <a href="/settings/billing" data-arcy="nav-billing">Billing</a>
</nav>
<input data-arcy="global-search-input" type="search" />
<button data-arcy="create-project-button">New project</button>
<button data-arcy="invite-member-button">Invite team</button>
<div data-arcy="billing-section">
  <div data-arcy="current-plan-card">...</div>
  <button data-arcy="upgrade-plan-button">Upgrade plan</button>
</div>
<button data-arcy="open-create-modal">New item</button>
<dialog data-arcy="create-item-modal">
  <input data-arcy="create-item-name-input" />
  <button data-arcy="create-item-submit">Create</button>
</dialog>
{projects.map(p => (
  <div key={p.id} data-arcy={`project-card-${p.slug}`}>...</div>
))}

There is no upper limit. A small app might have 20-30 anchors. A medium app should have 50-100+. See data-arcy attributes for full placement guidance.

Step 5: Create your .arcy/ knowledge graph

Create a .arcy/ directory at the project root. This is the AI assistant's knowledge base: what your product does, every page it can navigate to, and the data-arcy anchors it can reference.

At minimum, create .arcy/app.yaml. Replace all bracketed placeholders with your app's real content before pushing.

.arcy/app.yaml
app:
  name: "Your App Name"
  description: "2-3 sentences: what your product does and who uses it."

navigation:
  - path: /dashboard
    label: Dashboard
    description: "Main dashboard. Overview of activity and key metrics."
  - path: /projects
    label: Projects
    description: "List of all projects the user has access to."
  - path: /settings
    label: Settings
    description: "User profile, preferences, and notification settings."
  - path: /settings/billing
    label: Billing
    description: "Subscription plan and payment method management."
  - path: /settings/team
    label: Team
    description: "Invite members, manage roles and permissions."

For richer context, also create route files in .arcy/routes/[slug].yaml that describe sections, interactive elements, and common questions per page. The slug is the first path segment: /settings/team becomes routes/settings.yaml.

.arcy/routes/dashboard.yaml
route:
  path: /dashboard
  label: Dashboard
  description: "Main dashboard with activity overview."

sections:
  - id: overview
    label: Overview
    anchorId: dashboard-overview-section
    elements:
      - anchorId: create-project-button
        label: Create project
        type: button
        description: "Opens the new project form."

After creating the files, push to ARCY:

bash
arcy push

See CLI commands for the full .arcy/ schema and arcy validate to check your files before pushing.

Step 6: Verify

Start your dev server. A floating widget should appear in the bottom-center. Clicking it opens the ARCY.

Run arcy status to confirm the connection is live.


What's next

On this page