
Research
/Security News
Laravel Lang Compromised with RCE Backdoor Across 700+ Versions
Laravel Lang packages were compromised with an RCE backdoor across hundreds of versions, exposing cloud, CI/CD, and developer secrets.
@syncagent/react
Advanced tools
React SDK for SyncAgent — drop-in AI database chat widget and hooks for React apps.
Works with MongoDB, PostgreSQL, MySQL, SQLite, SQL Server, and Supabase.
sa_)Every new project gets a 14-day trial with 500 free requests — no credit card required. After the trial, you get 100 free requests/month on the Free plan.
npm install @syncagent/react @syncagent/js
import { SyncAgentChat } from "@syncagent/react";
export default function App() {
return (
<SyncAgentChat
config={{
apiKey: "sa_your_api_key",
connectionString: process.env.DATABASE_URL,
}}
/>
);
}
A floating chat button appears in the bottom-right corner. Your users can now query your database in plain English.
<SyncAgentChat> Props| Prop | Type | Default | Description |
|---|---|---|---|
config | SyncAgentConfig | Required* | API key, connection string, tools, filter, operations |
mode | "floating" | "inline" | "floating" | Floating FAB or embedded inline panel |
position | "bottom-right" | "bottom-left" | "bottom-right" | FAB position (floating mode only) |
defaultOpen | boolean | false | Start with the panel open |
title | string | "SyncAgent" | Header title |
subtitle | string | "AI Database Assistant" | Header subtitle |
placeholder | string | "Ask anything..." | Input placeholder |
welcomeMessage | string | "Hi! I can query..." | Empty state message |
accentColor | string | "#10b981" | Brand color for header, FAB, send button |
suggestions | string[] | 3 defaults | Quick-start suggestion chips |
persistKey | string | — | localStorage key for conversation persistence |
context | Record<string, any> | — | Extra context injected into every message |
filter | Record<string, any> | — | Mandatory query filter for multi-tenancy |
operations | ("read"|"create"|"update"|"delete")[] | — | Restrict operations for this session |
onReaction | (idx, reaction, content) => void | — | Called when user reacts 👍/👎 |
onData | (data: ToolData) => void | — | Called when a DB tool returns structured data |
*config is required unless wrapped in <SyncAgentProvider>.
Embed the chat inside your layout instead of a floating button:
<div style={{ height: 600 }}>
<SyncAgentChat
config={{ apiKey: "...", connectionString: "..." }}
mode="inline"
/>
</div>
useSyncAgentBuild your own chat UI with full control:
import { SyncAgentProvider, useSyncAgent } from "@syncagent/react";
export default function App() {
return (
<SyncAgentProvider config={{ apiKey: "...", connectionString: "..." }}>
<MyChat />
</SyncAgentProvider>
);
}
function MyChat() {
const { messages, isLoading, error, status, lastData, sendMessage, stop, reset } = useSyncAgent();
return (
<div>
{status && <div>⏳ {status.label}</div>}
{messages.map((msg, i) => (
<div key={i}><strong>{msg.role}:</strong> {msg.content}</div>
))}
<button onClick={() => sendMessage("Show all users")}>Ask</button>
<button onClick={stop}>Stop</button>
<button onClick={reset}>Clear</button>
</div>
);
}
useSyncAgent Returns| Return | Type | Description |
|---|---|---|
messages | Message[] | Full conversation history |
isLoading | boolean | true while streaming |
error | Error | null | Last error |
status | { step, label } | null | Live status while agent is working |
lastData | ToolData | null | Last structured data from a DB tool |
sendMessage | (content: string) => void | Send a user message |
stop | () => void | Abort the current stream |
reset | () => void | Clear all messages |
● Querying users... while the agent worksprefers-color-schemePass filter to scope every agent operation to the current user's organization. Enforced server-side.
<SyncAgentChat
config={{
apiKey: "sa_your_key",
connectionString: process.env.DATABASE_URL,
filter: { organizationId: currentUser.orgId },
operations: currentUser.isAdmin
? ["read", "create", "update", "delete"]
: ["read"],
}}
/>
Give the agent capabilities beyond your database:
<SyncAgentChat
config={{
apiKey: "sa_your_key",
connectionString: process.env.DATABASE_URL,
tools: {
createInvoice: {
description: "Create a Stripe invoice for a customer",
inputSchema: {
customerId: { type: "string", description: "Stripe customer ID" },
amount: { type: "number", description: "Amount in cents" },
},
execute: async ({ customerId, amount }) => {
const inv = await stripe.invoices.create({ customer: customerId });
return { invoiceId: inv.id };
},
},
},
}}
/>
Use the agent with only your custom tools — no database access:
<SyncAgentChat
config={{
apiKey: "sa_your_key",
toolsOnly: true,
tools: {
searchProducts: {
description: "Search products by name",
inputSchema: { query: { type: "string", description: "Search query" } },
execute: async ({ query }) => {
const res = await fetch(`/api/products?q=${query}`);
return res.json();
},
},
},
}}
/>
Use the useCustomerChat hook to build customer-facing support interfaces powered by SyncAgent's customer agent pipeline — including persona, flows, knowledge base, escalation, and AI fallback.
import { SyncAgentProvider, useCustomerChat } from "@syncagent/react";
function App() {
return (
<SyncAgentProvider
config={{
apiKey: "sa_your_api_key",
connectionString: process.env.DATABASE_URL,
externalUserId: currentUser.id,
}}
>
<CustomerChat />
</SyncAgentProvider>
);
}
function CustomerChat() {
const {
messages,
conversationId,
isLoading,
isEscalated,
isResolved,
error,
welcomeMessage,
sendMessage,
rateConversation,
reset,
} = useCustomerChat({
onEscalated: () => console.log("Escalated to human agent"),
onResolved: (id) => console.log("Conversation resolved:", id),
});
return (
<div>
{welcomeMessage && <p>{welcomeMessage}</p>}
{messages.map((msg, i) => (
<div key={i}>
<strong>{msg.role}:</strong> {msg.content}
</div>
))}
{isEscalated && <p>You've been connected to a human agent.</p>}
{isResolved && (
<div>
<p>Conversation resolved!</p>
<button onClick={() => rateConversation(5)}>⭐ Rate 5/5</button>
</div>
)}
{error && <p>Error: {error.message}</p>}
<button onClick={() => sendMessage("I need help with my order")}>
Send
</button>
<button onClick={reset}>New Conversation</button>
</div>
);
}
useCustomerChat Return Values| Return | Type | Description |
|---|---|---|
messages | Message[] | Full conversation history (user and assistant messages) |
conversationId | string | null | Current conversation ID, set after first message |
isLoading | boolean | true while waiting for a response |
isEscalated | boolean | true when conversation has been escalated to a human agent |
isResolved | boolean | true when the conversation has been resolved |
error | Error | null | Last error encountered, or null |
welcomeMessage | string | null | Welcome message returned on first interaction |
sendMessage | (content: string, metadata?: Record<string, any>) => Promise<void> | Send a message to the customer agent |
rateConversation | (rating: number) => Promise<void> | Rate the conversation (1-5). Throws if no active conversation. |
reset | () => void | Clear all state and start a new conversation |
UseCustomerChatOptions| Field | Type | Description |
|---|---|---|
client | SyncAgentClient? | Optional client instance. If omitted, uses the client from SyncAgentProvider context. |
onEscalated | () => void? | Called when the conversation is escalated to a human agent |
onResolved | (conversationId: string) => void? | Called when the conversation is resolved |
Client resolution:
useCustomerChatfirst checks for aclientpassed directly in options. If none is provided, it falls back to the client from the nearest<SyncAgentProvider>. If neither is available, it throws an error prompting you to provide one.
When no externalUserId is provided in the config, the customer chat enters guest mode — anonymous visitors must identify themselves before sending messages. The useCustomerChat hook exposes guest identification state and a method to submit guest data programmatically.
| Return | Type | Description |
|---|---|---|
isIdentified | boolean | true if the user has an externalUserId or has completed guest identification |
guestIdentity | GuestIdentity | null | The stored guest identity object, or null if not yet identified |
identifyGuest | (data: { name: string; email: string; phone?: string }) => void | Submit guest form data — validates, generates a guest ID, persists to localStorage, and transitions to identified state |
GuestIdentificationFormThe GuestIdentificationForm component provides a ready-made form with built-in validation, accessibility attributes, and customizable text:
import { useCustomerChat, GuestIdentificationForm } from "@syncagent/react";
function CustomerChat() {
const {
messages,
isIdentified,
identifyGuest,
sendMessage,
} = useCustomerChat({
onGuestIdentified: (identity) => {
console.log("Guest identified:", identity.guestId);
},
});
if (!isIdentified) {
return (
<GuestIdentificationForm
onSubmit={(identity) => identifyGuest(identity)}
config={{
title: "Welcome!",
subtitle: "Tell us a bit about yourself",
submitButtonText: "Start Chat",
namePlaceholder: "Your name",
emailPlaceholder: "you@example.com",
phonePlaceholder: "Phone (optional)",
}}
className="my-guest-form"
/>
);
}
return (
<div>
{messages.map((msg, i) => (
<div key={i}><strong>{msg.role}:</strong> {msg.content}</div>
))}
<button onClick={() => sendMessage("Hello!")}>Send</button>
</div>
);
}
GuestIdentificationFormProps| Prop | Type | Required | Description |
|---|---|---|---|
onSubmit | (identity: GuestIdentity) => void | Yes | Called with the complete GuestIdentity (including generated guestId) on valid submission |
config | GuestFormConfig | No | Customize title, subtitle, button text, and placeholder strings |
className | string | No | Custom CSS class applied to the form container |
If you prefer to build your own form UI, use the identifyGuest method directly:
import { useState } from "react";
import { useCustomerChat } from "@syncagent/react";
function CustomGuestForm() {
const { isIdentified, identifyGuest, error } = useCustomerChat();
const [name, setName] = useState("");
const [email, setEmail] = useState("");
if (isIdentified) return null;
return (
<form
onSubmit={(e) => {
e.preventDefault();
identifyGuest({ name, email });
}}
>
<input value={name} onChange={(e) => setName(e.target.value)} placeholder="Name" />
<input value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" />
<button type="submit">Continue</button>
{error && <p>{error.message}</p>}
</form>
);
}
The identifyGuest method validates the input using validateGuestForm from @syncagent/js. If validation fails, it sets the error state with a descriptive message. On success, it generates a deterministic guest identifier from the email, persists the identity to localStorage, and sets isIdentified to true.
⚠️ Deprecated:
createDual()will be removed in a future major version. UseuseDualChat()instead — passexternalUserIddirectly to enable both modes on a single instance.
Before (deprecated):
import { SyncAgentClient } from "@syncagent/js";
import { SyncAgentProvider, useCustomerChat, useSyncAgent } from "@syncagent/react";
const { db, support } = SyncAgentClient.createDual({
apiKey: "sa_your_key",
connectionString: process.env.DATABASE_URL,
externalUserId: currentUser.id,
});
function AdminChat() {
return <SyncAgentProvider config={db}><AdminPanel /></SyncAgentProvider>;
}
function CustomerWidget() {
const chat = useCustomerChat({ client: support });
return <div>{/* customer chat UI */}</div>;
}
After (recommended):
import { SyncAgentProvider, useDualChat } from "@syncagent/react";
function App() {
return (
<SyncAgentProvider
config={{
apiKey: "sa_your_key",
connectionString: process.env.DATABASE_URL,
externalUserId: currentUser.id,
}}
>
<DualChatUI />
</SyncAgentProvider>
);
}
function DualChatUI() {
const { db, support } = useDualChat();
// Use db.sendMessage(), support.sendMessage(), etc.
}
Legacy pattern (deprecated):
import { SyncAgentClient } from "@syncagent/js";
import { SyncAgentProvider, useCustomerChat, useSyncAgent } from "@syncagent/react";
const { db, support } = SyncAgentClient.createDual({
apiKey: "sa_your_key",
connectionString: process.env.DATABASE_URL,
externalUserId: currentUser.id,
});
// Admin panel — database agent
function AdminChat() {
return <SyncAgentProvider config={db}><AdminPanel /></SyncAgentProvider>;
}
// Customer widget — support agent
function CustomerWidget() {
const chat = useCustomerChat({ client: support });
return <div>{/* customer chat UI */}</div>;
}
The <SyncAgentCustomerChat> component is a pre-built, drop-in customer support chat widget. It provides a complete experience including guest identification, real-time messaging with AI and human agents, escalation display, and satisfaction rating — all as a single component.
import { SyncAgentCustomerChat } from "@syncagent/react";
export default function App() {
return (
<SyncAgentCustomerChat
apiKey="sa_your_api_key"
connectionString={process.env.DATABASE_URL!}
/>
);
}
<SyncAgentCustomerChat> Props| Prop | Type | Default | Description |
|---|---|---|---|
config | SyncAgentConfig | — | Server config object. When provided, apiKey/connectionString are ignored. |
apiKey | string | — | API key for authentication (ignored if config provided) |
connectionString | string | — | Database connection string (ignored if config provided) |
externalUserId | string | — | Authenticated user ID — skips guest form when provided |
mode | "floating" | "inline" | "floating" | Floating toggle button or embedded inline panel |
position | "bottom-right" | "bottom-left" | "bottom-right" | Position of the floating widget (floating mode only) |
defaultOpen | boolean | false | Whether the floating panel starts open (floating mode only) |
title | string | "Customer Support" | Header title text (max 100 chars) |
subtitle | string | "How can we help you?" | Header subtitle text (max 200 chars) |
placeholder | string | "Type your message..." | Input placeholder text (max 150 chars) |
welcomeMessage | string | — | Initial welcome message displayed before any interaction |
accentColor | string | "#6366f1" | Primary accent color (hex, rgb, or hsl) |
darkMode | boolean | false | Enable dark mode color scheme |
className | string | — | Additional CSS class on root container |
style | CSSProperties | — | Inline styles merged onto root container |
guestForm | GuestFormConfig | — | Custom guest form configuration (title, subtitle, placeholders, button text) |
pusherKey | string | — | Pusher app key for real-time human agent messages |
pusherCluster | string | "us2" | Pusher cluster |
metadata | Record<string, any> | — | Custom metadata attached to conversations |
onEscalated | () => void | — | Called when conversation is escalated to a human agent |
onResolved | (conversationId: string) => void | — | Called when conversation is resolved |
onGuestIdentified | (identity: GuestIdentity) => void | — | Called after guest form submission |
<SyncAgentCustomerChat
apiKey="sa_your_api_key"
connectionString={process.env.DATABASE_URL!}
/>
import { SyncAgentCustomerChat } from "@syncagent/react";
// Config from server (e.g., Next.js createCustomerServerConfig)
<SyncAgentCustomerChat config={serverConfig} />
<SyncAgentCustomerChat
apiKey="sa_your_api_key"
connectionString={process.env.DATABASE_URL!}
mode="floating"
position="bottom-right"
defaultOpen={false}
/>
Embed the chat inside your layout — the panel fills its parent container and is always visible:
<div style={{ height: 600, width: 400 }}>
<SyncAgentCustomerChat
apiKey="sa_your_api_key"
connectionString={process.env.DATABASE_URL!}
mode="inline"
/>
</div>
<SyncAgentCustomerChat
apiKey="sa_your_api_key"
connectionString={process.env.DATABASE_URL!}
darkMode
accentColor="#8b5cf6"
/>
<SyncAgentCustomerChat
apiKey="sa_your_api_key"
connectionString={process.env.DATABASE_URL!}
guestForm={{
title: "Welcome!",
subtitle: "Tell us about yourself to get started",
submitButtonText: "Start Chat",
namePlaceholder: "Your name",
emailPlaceholder: "you@company.com",
phonePlaceholder: "+1 (555) 000-0000",
}}
onGuestIdentified={(identity) => {
console.log("Guest identified:", identity.guestId);
}}
/>
useCustomerChat HookFor full control over the UI, use the useCustomerChat hook directly to build a custom chat interface:
import { SyncAgentProvider, useCustomerChat } from "@syncagent/react";
function App() {
return (
<SyncAgentProvider config={{ apiKey: "sa_...", connectionString: "..." }}>
<CustomChat />
</SyncAgentProvider>
);
}
function CustomChat() {
const {
messages,
isLoading,
isEscalated,
isResolved,
isIdentified,
identifyGuest,
sendMessage,
rateConversation,
error,
welcomeMessage,
} = useCustomerChat({
onEscalated: () => console.log("Escalated"),
onResolved: (id) => console.log("Resolved:", id),
onGuestIdentified: (identity) => console.log("Guest:", identity),
});
if (!isIdentified) {
return (
<form onSubmit={(e) => {
e.preventDefault();
identifyGuest({ name: "Jane", email: "jane@example.com" });
}}>
<button type="submit">Identify</button>
</form>
);
}
return (
<div>
{welcomeMessage && <p>{welcomeMessage}</p>}
{messages.map((msg, i) => (
<div key={i}><strong>{msg.role}:</strong> {msg.content}</div>
))}
{isEscalated && <p>Connected to a human agent</p>}
{isResolved && <button onClick={() => rateConversation(5)}>⭐ Rate</button>}
{error && <p>Error: {error.message}</p>}
<button onClick={() => sendMessage("Hello!")} disabled={isLoading}>
Send
</button>
</div>
);
}
UseCustomerChatOptions| Option | Type | Description |
|---|---|---|
client | SyncAgentClient | Optional client instance. If omitted, uses the client from SyncAgentProvider context. |
externalUserId | string | When provided, the hook considers the user pre-authenticated (bypasses guest flow). |
onEscalated | () => void | Called when the conversation is escalated to a human agent. |
onResolved | (conversationId: string) => void | Called when the conversation is resolved. |
onGuestIdentified | (identity: GuestIdentity) => void | Called when a guest completes identification. |
useCustomerChat Return Values| Return | Type | Description |
|---|---|---|
messages | Message[] | Full conversation history |
conversationId | string | null | Current conversation ID, set after first message |
isLoading | boolean | true while waiting for a response |
isEscalated | boolean | true when conversation has been escalated to a human agent |
isResolved | boolean | true when the conversation has been resolved |
error | Error | null | Last error encountered |
welcomeMessage | string | null | Welcome message returned on first interaction |
sendMessage | (content: string, metadata?: Record<string, any>) => Promise<void> | Send a message to the customer agent |
rateConversation | (rating: number) => Promise<void> | Rate the conversation (1–5). Throws if no active conversation. |
reset | () => void | Clear all state and start a new conversation |
isIdentified | boolean | true if the user has an externalUserId or has completed guest identification |
guestIdentity | GuestIdentity | null | The stored guest identity object |
identifyGuest | (data: { name: string; email: string; phone?: string }) => void | Submit guest form data — validates, generates a guest ID, persists to localStorage |
The <SyncAgentCustomerChat> component uses a built-in theme engine to generate a complete color palette from two inputs:
accentColor — any valid CSS color (hex, rgb, hsl). Used for buttons, user message bubbles, focus outlines, and the floating toggle button. Default: "#6366f1".darkMode — when true, renders a dark color scheme with light text on dark backgrounds.The theme engine (computeTheme() from @syncagent/js) automatically adjusts all derived colors to meet WCAG AA contrast requirements:
All styles are applied inline — no external CSS is required and no styles leak to or from your application.
// Light mode with custom accent
<SyncAgentCustomerChat accentColor="#059669" />
// Dark mode with default accent
<SyncAgentCustomerChat darkMode />
// Dark mode with custom accent
<SyncAgentCustomerChat darkMode accentColor="#f59e0b" />
The <SyncAgentCustomerChat> component is built with WCAG AA compliance:
ARIA Roles and Labels:
role="region" with aria-label="Customer chat"role="log" to indicate a chronological message streamKeyboard Navigation:
Tab moves focus through all interactive elements (buttons, inputs, rating stars)Enter or Space activates buttons and submits formsFocus Management:
Screen Reader Support:
aria-live="polite")Contrast Requirements:
The useDualChat() hook provides a single interface for managing both database agent and customer support agent conversations from within a <SyncAgentProvider>. When your config includes externalUserId, both modes are available through namespaced db and support objects.
import { SyncAgentProvider, useDualChat } from "@syncagent/react";
function App() {
return (
<SyncAgentProvider
config={{
apiKey: "sa_your_api_key",
connectionString: process.env.DATABASE_URL,
externalUserId: currentUser.id,
}}
>
<DualChatUI />
</SyncAgentProvider>
);
}
function DualChatUI() {
const { db, support } = useDualChat({
context: { tenant: currentUser.orgId },
onData: (data) => console.log("DB tool result:", data),
onEscalated: () => console.log("Escalated to human"),
onResolved: (id) => console.log("Resolved:", id),
});
return (
<div>
{/* Database agent panel */}
<section>
<h2>Admin Database Chat</h2>
{db.messages.map((msg, i) => (
<div key={i}><strong>{msg.role}:</strong> {msg.content}</div>
))}
{db.isLoading && <p>Loading...</p>}
{db.error && <p>Error: {db.error.message}</p>}
<button onClick={() => db.sendMessage("Show all orders")}>
Query DB
</button>
<button onClick={db.stop}>Stop</button>
<button onClick={db.reset}>Clear</button>
</section>
{/* Customer support panel */}
<section>
<h2>Customer Support</h2>
{support.welcomeMessage && <p>{support.welcomeMessage}</p>}
{support.messages.map((msg, i) => (
<div key={i}><strong>{msg.role}:</strong> {msg.content}</div>
))}
{support.isLoading && <p>Loading...</p>}
{support.isEscalated && <p>Connected to a human agent.</p>}
{support.isResolved && (
<button onClick={() => support.rateConversation(5)}>⭐ Rate</button>
)}
{support.error && <p>Error: {support.error.message}</p>}
<button onClick={() => support.sendMessage("I need help")}>
Send
</button>
<button onClick={support.reset}>New Conversation</button>
</section>
</div>
);
}
If useDualChat() is called outside of a <SyncAgentProvider>, it throws:
useDualChat must be used within a <SyncAgentProvider>
Ensure your component is wrapped in a <SyncAgentProvider> with a valid config that includes externalUserId.
DualChatReturnThe useDualChat() hook returns an object with two namespaces: db for database agent state and support for customer agent state.
db Properties| Property | Type | Description |
|---|---|---|
messages | Message[] | Full database agent conversation history |
isLoading | boolean | true while the database agent is streaming |
error | Error | null | Last error from the database agent, or null |
status | { step: string; label: string } | null | Live status while the agent is working (e.g., querying) |
lastData | ToolData | null | Last structured data returned by a DB tool |
sendMessage | (content: string) => void | Send a message to the database agent |
stop | () => void | Abort the current database agent stream |
reset | () => void | Clear all database agent messages and state |
support Properties| Property | Type | Description |
|---|---|---|
messages | Message[] | Full customer support conversation history |
conversationId | string | null | Current conversation ID, set after first message |
isLoading | boolean | true while waiting for a support response |
isEscalated | boolean | true when conversation has been escalated to a human agent |
isResolved | boolean | true when the conversation has been resolved |
error | Error | null | Last error from the support agent, or null |
welcomeMessage | string | null | Welcome message returned on first interaction |
sendMessage | (content: string, metadata?: Record<string, any>) => Promise<void> | Send a message to the customer support agent |
rateConversation | (rating: number) => Promise<void> | Rate the conversation (1-5). Throws if no active conversation. |
reset | () => void | Clear all support state and start a new conversation |
UseDualChatOptions| Option | Type | Required | Description |
|---|---|---|---|
context | Record<string, any> | No | Extra context injected into every database agent message |
onData | (data: ToolData) => void | No | Called when a DB tool returns structured data |
onEscalated | () => void | No | Called when the customer conversation is escalated to a human agent |
onResolved | (conversationId: string) => void | No | Called when the customer conversation is resolved |
All config options from @syncagent/js work here too:
<SyncAgentChat
config={{
apiKey: "sa_your_key",
connectionString: process.env.DATABASE_URL,
systemInstruction: "You are a friendly sales assistant for Acme Corp.",
language: "French",
confirmWrites: true,
maxResults: 10,
sensitiveFields: ["ssn", "salary", "creditCard"],
onBeforeToolCall: (name, args) => { console.log(`[Audit] ${name}`, args); return true; },
onAfterToolCall: (name, args, result) => { analytics.track("tool_call", { tool: name }); },
}}
/>
<SyncAgentChat
config={{ apiKey: "...", connectionString: "..." }}
persistKey={currentUser.id}
/>
History saves to localStorage under sa_chat_{persistKey}. The "New" button clears it.
The SDK automatically detects the current page from window.location — zero config needed:
URL: /dashboard/orders/ord_123?tab=details
Auto-detected:
currentPage: "orders"
currentPath: "/dashboard/orders/ord_123"
currentRecordId: "ord_123"
param_tab: "details"
Pass additional context:
<SyncAgentChat
config={{ apiKey: "...", connectionString: "..." }}
context={{ userId: currentUser.id, userRole: "admin" }}
/>
No npm required — drop a script tag into any HTML page:
<script src="https://syncagentdev.vercel.app/api/v1/widget"></script>
<script>
SyncAgent.init({
apiKey: "sa_your_key",
connectionString: "your_database_url",
position: "right",
accentColor: "#10b981",
title: "AI Assistant",
persistKey: "my-app",
});
</script>
| Plan | Requests/mo | Collections | Price |
|---|---|---|---|
| Free (+ 14-day trial) | 100 (500 during trial) | 5 | GH₵0 |
| Starter | 5,000 | 20 | GH₵150/mo |
| Pro | 50,000 | Unlimited | GH₵500/mo |
| Enterprise | Unlimited | Unlimited | Custom |
The following types are exported from @syncagent/react for use in your TypeScript projects:
| Type | Description | Import |
|---|---|---|
DualChatReturn | Return type of the useDualChat() hook containing namespaced db and support objects | import { DualChatReturn } from "@syncagent/react" |
UseDualChatOptions | Options interface for configuring the useDualChat() hook (context, callbacks) | import { UseDualChatOptions } from "@syncagent/react" |
import { DualChatReturn, UseDualChatOptions } from "@syncagent/react";
MIT
FAQs
SyncAgent React SDK — AI database chat widget & hooks
The npm package @syncagent/react receives a total of 38 weekly downloads. As such, @syncagent/react popularity was classified as not popular.
We found that @syncagent/react demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
/Security News
Laravel Lang packages were compromised with an RCE backdoor across hundreds of versions, exposing cloud, CI/CD, and developer secrets.

Security News
Socket found a malicious postinstall hook across 700+ GitHub repos, including PHP packages on Packagist and Node.js project repositories.

Security News
Vibe coding at scale is reshaping how packages are created, contributed, and selected across the software supply chain