npm package: @syncagent/vue
Description: SyncAgent Vue 3 SDK — composables and chat component
Latest version: 0.1.0

# @syncagent/vue

Vue 3 SDK for [SyncAgent](https://syncagent.dev) — composables for AI database chat.

## Install

```bash
npm install @syncagent/vue @syncagent/js
```

## Quick Start

```vue
<script setup lang="ts">
import { ref } from "vue";
import { SyncAgentClient } from "@syncagent/js";
import { useSyncAgent } from "@syncagent/vue";

const client = new SyncAgentClient({
  apiKey: import.meta.env.VITE_SYNCAGENT_KEY,
  connectionString: import.meta.env.VITE_DATABASE_URL,
});

const { messages, isLoading, error, status, sendMessage, stop, reset } = useSyncAgent({ client });

const input = ref("");

function send() {
  if (!input.value.trim()) return;
  sendMessage(input.value);
  input.value = "";
}
</script>

<template>
  <div class="chat">
    <div v-if="status" class="status">⏳ {{ status.label }}</div>

    <div class="messages">
      <div
        v-for="(msg, i) in messages"
        :key="i"
        :class="['message', msg.role]"
      >
        {{ msg.content }}
      </div>
    </div>

    <div v-if="error" class="error">
      ⚠️ {{ error.message }}
    </div>

    <div class="input-row">
      <input
        v-model="input"
        @keydown.enter="send"
        placeholder="Ask about your data..."
        :disabled="isLoading"
      />
      <button @click="send" :disabled="isLoading || !input.trim()">Send</button>
      <button v-if="isLoading" @click="stop">Stop</button>
      <button @click="reset">Clear</button>
    </div>
  </div>
</template>
```

## Multi-tenant SaaS

```vue
<script setup lang="ts">
import { computed } from "vue";
import { SyncAgentClient } from "@syncagent/js";
import { useSyncAgent } from "@syncagent/vue";
import { useAuth } from "@/composables/auth";

const { user } = useAuth();

const client = computed(() => new SyncAgentClient({
  apiKey: import.meta.env.VITE_SYNCAGENT_KEY,
  connectionString: import.meta.env.VITE_DATABASE_URL,
  filter: { organizationId: user.value.orgId },
  operations: user.value.isAdmin
    ? ["read", "create", "update", "delete"]
    : ["read"],
}));

const { messages, isLoading, sendMessage } = useSyncAgent({
  client: client.value,
  context: { userId: user.value.id, page: "dashboard" },
});
</script>
```

## With custom tools

```vue
<script setup lang="ts">
import { SyncAgentClient } from "@syncagent/js";
import { useSyncAgent } from "@syncagent/vue";

const client = new SyncAgentClient({
  apiKey: import.meta.env.VITE_SYNCAGENT_KEY,
  connectionString: import.meta.env.VITE_DATABASE_URL,
  tools: {
    sendNotification: {
      description: "Send a push notification to a user",
      inputSchema: {
        userId:  { type: "string", description: "User ID" },
        message: { type: "string", description: "Notification message" },
      },
      execute: async ({ userId, message }) => {
        await pushService.send(userId, message);
        return { sent: true };
      },
    },
  },
});

const { messages, sendMessage } = useSyncAgent({ client });
</script>
```

## API Reference

### `useSyncAgent(options)`

| Option | Type | Description |
|--------|------|-------------|
| `client` | `SyncAgentClient` | Required. Create with `new SyncAgentClient(config)` |
| `context` | `Record<string,any>` | Extra context injected into every message |
| `onData` | `(data: ToolData) => void` | Called when a DB tool returns data |

**Returns:**

| Name | Type | Description |
|------|------|-------------|
| `messages` | `Readonly<Ref<Message[]>>` | Conversation history |
| `isLoading` | `Readonly<Ref<boolean>>` | True while streaming |
| `error` | `Readonly<Ref<Error\|null>>` | Last error |
| `status` | `Readonly<Ref<{step,label}\|null>>` | Live status |
| `lastData` | `Readonly<Ref<ToolData\|null>>` | Last DB query result |
| `sendMessage` | `(content: string) => Promise<void>` | Send a message |
| `stop` | `() => void` | Abort current stream |
| `reset` | `() => void` | Clear all messages |

## License

MIT