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
npm install @arcyai/sdkpnpm add @arcyai/sdkbun add @arcyai/sdkyarn add @arcyai/sdkStep 2: Add your keys to .env
Get your keys from app.arcyai.com under Settings > API Keys.
Add to .env.local:
NEXT_PUBLIC_ARCY_PUBLISHABLE_KEY=pk_live_...
ARCY_SECRET_KEY=sk_live_...Add to .env:
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:
"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:
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:
"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:
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:
"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:
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:
"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:
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:
"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:
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.
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>
)
}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>
)
}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>
)
}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>
)
}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.
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>
)
}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>
)
}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>
)
}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, notbtn-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 justproject-card - Add
data-arcyto 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)
<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.
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.
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:
arcy pushSee 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
- CLI commands:
arcy push,arcy sync, andarcy status - Configuration: all
ARCYProviderprops - AI modes: how Chat, Teach, and Agent modes work