npm package: @syncagent/angular
Description: SyncAgent Angular SDK — service and component for AI database chat
Latest version: 0.1.0

# @syncagent/angular

Angular SDK for [SyncAgent](https://syncagent.dev) — injectable service for AI database chat.

## Install

```bash
npm install @syncagent/angular @syncagent/js
```

## Quick Start

```typescript
// app.component.ts
import { Component, OnInit } from "@angular/core";
import { SyncAgentService } from "@syncagent/angular";
import { environment } from "./environments/environment";

@Component({
  selector: "app-root",
  providers: [SyncAgentService],
  template: `
    <div class="chat">
      <div *ngIf="agent.status() as s" class="status">⏳ {{ s.label }}</div>

      <div class="messages">
        <div
          *ngFor="let msg of agent.messages()"
          [class]="'message ' + msg.role"
        >
          {{ msg.content }}
        </div>
      </div>

      <div *ngIf="agent.error() as err" class="error">
        ⚠️ {{ err.message }}
        <button (click)="retry()">Retry</button>
      </div>

      <div class="input-row">
        <input
          [(ngModel)]="input"
          (keydown.enter)="send()"
          placeholder="Ask about your data..."
          [disabled]="agent.isLoading()"
        />
        <button (click)="send()" [disabled]="agent.isLoading() || !input.trim()">Send</button>
        <button *ngIf="agent.isLoading()" (click)="agent.stop()">Stop</button>
        <button (click)="agent.reset()">Clear</button>
      </div>
    </div>
  `,
})
export class AppComponent implements OnInit {
  input = "";
  lastInput = "";

  constructor(public agent: SyncAgentService) {}

  ngOnInit() {
    this.agent.configure({
      apiKey: environment.syncagentKey,
      connectionString: environment.databaseUrl,
    });
  }

  send() {
    if (!this.input.trim()) return;
    this.lastInput = this.input;
    this.agent.sendMessage(this.input);
    this.input = "";
  }

  retry() {
    if (this.lastInput) this.agent.sendMessage(this.lastInput);
  }
}
```

## Multi-tenant SaaS

```typescript
@Component({
  providers: [SyncAgentService],
})
export class DashboardComponent implements OnInit {
  constructor(
    public agent: SyncAgentService,
    private authService: AuthService,
  ) {}

  ngOnInit() {
    const user = this.authService.currentUser;

    this.agent.configure({
      apiKey: environment.syncagentKey,
      connectionString: environment.databaseUrl,
      // Scope to current user's organization
      filter: { organizationId: user.orgId },
      // Role-based access
      operations: user.isAdmin
        ? ["read", "create", "update", "delete"]
        : ["read"],
      // Context for the agent
      context: {
        userId: user.id,
        userRole: user.role,
        currentPage: "dashboard",
      },
    });
  }
}
```

## Using RxJS observables

```typescript
@Component({ providers: [SyncAgentService] })
export class ChatComponent implements OnInit, OnDestroy {
  private destroy$ = new Subject<void>();

  constructor(public agent: SyncAgentService) {}

  ngOnInit() {
    this.agent.configure({ apiKey: "...", connectionString: "..." });

    // Subscribe to messages stream
    this.agent.messages$
      .pipe(takeUntil(this.destroy$))
      .subscribe(messages => {
        console.log("Messages updated:", messages.length);
      });

    // Subscribe to each new message
    this.agent.message$
      .pipe(takeUntil(this.destroy$))
      .subscribe(msg => {
        console.log("New message:", msg.content.slice(0, 50));
      });

    // Subscribe to status
    this.agent.status$
      .pipe(takeUntil(this.destroy$), filter(Boolean))
      .subscribe(({ step, label }) => {
        console.log(`[${step}] ${label}`);
      });
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }
}
```

## With custom tools

```typescript
this.agent.configure({
  apiKey: environment.syncagentKey,
  connectionString: environment.databaseUrl,
  tools: {
    sendEmail: {
      description: "Send an email to a user",
      inputSchema: {
        to:      { type: "string", description: "Recipient email" },
        subject: { type: "string", description: "Subject line" },
        body:    { type: "string", description: "Email body" },
      },
      execute: async ({ to, subject, body }) => {
        await this.emailService.send({ to, subject, body });
        return { sent: true };
      },
    },
  },
  onData: (data) => {
    // Update your own table/chart when agent queries data
    if (data.collection === "orders") {
      this.orders = data.data;
    }
  },
});
```

## API Reference

### `SyncAgentService`

**Methods:**

| Method | Description |
|--------|-------------|
| `configure(config)` | Initialize with API key, connection string, and options |
| `sendMessage(content)` | Send a message and start streaming |
| `stop()` | Abort the current stream |
| `reset()` | Clear all messages |

**Angular Signals (recommended):**

| Signal | Type | Description |
|--------|------|-------------|
| `messages()` | `Message[]` | Conversation history |
| `isLoading()` | `boolean` | True while streaming |
| `error()` | `Error \| null` | Last error |
| `status()` | `{step,label} \| null` | Live status |
| `lastData()` | `ToolData \| null` | Last DB query result |

**RxJS Observables:**

| Observable | Type | Description |
|------------|------|-------------|
| `messages$` | `Observable<Message[]>` | Conversation history stream |
| `isLoading$` | `Observable<boolean>` | Loading state stream |
| `error$` | `Observable<Error\|null>` | Error stream |
| `status$` | `Observable<{step,label}\|null>` | Status stream |
| `message$` | `Observable<Message>` | Emits each new assistant message |

## License

MIT