# Productier AI Agent Skill

This skill enables any AI agent (OpenClaw, Claude, GPT, Cursor, etc.) to interact with Productier - a calm, lightweight productivity workspace.

## Overview

Productier combines calendar planning, kanban task management, notes, focus sessions, and mail-based task capture in a single workspace.

**Core Features:**
- Tasks with kanban board, labels, attachments, due dates
- Calendar with month/week/day views and event management
- Notes with markdown support
- Focus sessions (Pomodoro-style)
- Mail integration (IMAP/SMTP) with task creation from emails
- Workspace collaboration with roles (owner/admin/member)

---

## Authentication

**IMPORTANT:** All API operations require user authorization. The user must provide their session credentials before any operations can be performed.

### Auth Service

Productier uses Better Auth with magic link (passwordless) or email/password authentication.

**Auth Service URL:**
- Development: `http://localhost:43001`
- Production: Same domain at `/api/auth/*`

### Session-Based Authentication

After login, sessions are stored in cookies. Include cookies in all API requests.

**Login Methods:**

1. **Magic Link (Recommended):**
```http
POST /api/auth/sign-in/magic-link
Content-Type: application/json

{
  "email": "user@example.com"
}
```
User receives email with magic link. In development, check `/api/dev-mailbox` for the link.

2. **Email/Password:**
```http
POST /api/auth/sign-in/email
Content-Type: application/json

{
  "email": "user@example.com",
  "password": "user-password"
}
```

**Verify Session:**
```http
GET /api/auth/session
```
Returns user object if authenticated.

**Logout:**
```http
POST /api/auth/sign-out
```

### Authorization Header (Alternative)

For programmatic access, the user may provide a session token:
```http
Authorization: Bearer <session-token>
```

---

## API Reference

**Base URL:**
- Development: `http://localhost:48080/v1`
- Production: `/v1/*`

All endpoints (except public ones) require authentication via session cookies.

### Response Format

**Success:**
```json
{
  "data": { ... }
}
```

**Error:**
```json
{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable message",
    "requestId": "req-xxx"
  }
}
```

---

## Workspaces

Workspaces are the top-level container. Users can be members of multiple workspaces.

### List Workspaces
```http
GET /v1/workspaces
```

**Response:**
```json
{
  "data": [
    {
      "id": "uuid",
      "slug": "my-workspace",
      "name": "My Workspace",
      "role": "owner",
      "createdAt": "2024-01-01T00:00:00Z"
    }
  ]
}
```

### Workspace Context
Most operations require a `workspaceSlug` query parameter to identify the workspace context.

---

## Tasks

### List Tasks
```http
GET /v1/tasks?workspaceSlug={slug}
```

### Create Task
```http
POST /v1/tasks
Content-Type: application/json

{
  "workspaceSlug": "my-workspace",
  "title": "Task title",
  "description": "Markdown description",
  "boardGroupId": "group-uuid",
  "status": "todo",
  "color": "#3b82f6",
  "dueAt": "2024-01-15T10:00:00Z",
  "labelIds": ["label-uuid-1", "label-uuid-2"]
}
```

**Task Status Values:** `todo`, `in-progress`, `done`

### Update Task
```http
PATCH /v1/tasks/{taskId}
Content-Type: application/json

{
  "title": "Updated title",
  "status": "done",
  "dueAt": "2024-01-20T10:00:00Z"
}
```

### Task Attachments

**Upload Attachment:**
```http
POST /v1/tasks/{taskId}/attachments
Content-Type: multipart/form-data

file: <binary>
```
Max size: 20MB

**Download Attachment:**
```http
GET /v1/tasks/{taskId}/attachments/{attachmentId}/download
```

**Delete Attachment:**
```http
DELETE /v1/tasks/{taskId}/attachments/{attachmentId}
```

---

## Board Groups (Kanban Columns)

### List Board Groups
```http
GET /v1/board-groups?workspaceSlug={slug}
```

### Create Board Group
```http
POST /v1/board-groups
Content-Type: application/json

{
  "workspaceSlug": "my-workspace",
  "name": "In Review",
  "color": "#f59e0b",
  "sortOrder": 3
}
```

### Update Board Group
```http
PATCH /v1/board-groups/{groupId}
Content-Type: application/json

{
  "name": "New Name",
  "color": "#10b981",
  "sortOrder": 2
}
```

---

## Calendar Events

### List Events
```http
GET /v1/calendar/events?workspaceSlug={slug}
```

### Create Event
```http
POST /v1/calendar/events
Content-Type: application/json

{
  "workspaceSlug": "my-workspace",
  "title": "Meeting",
  "description": "Weekly sync",
  "startsAt": "2024-01-15T10:00:00Z",
  "endsAt": "2024-01-15T11:00:00Z",
  "color": "#3b82f6",
  "linkedTaskId": "task-uuid"
}
```

### Update Event
```http
PATCH /v1/calendar/events/{eventId}
Content-Type: application/json

{
  "title": "Updated Meeting",
  "startsAt": "2024-01-15T14:00:00Z",
  "endsAt": "2024-01-15T15:00:00Z"
}
```

---

## Notes

### List Notes
```http
GET /v1/notes?workspaceSlug={slug}
```

### Create Note
```http
POST /v1/notes
Content-Type: application/json

{
  "workspaceSlug": "my-workspace",
  "title": "Meeting Notes",
  "content": "# Heading\n\nMarkdown content here"
}
```

### Update Note
```http
PATCH /v1/notes/{noteId}
Content-Type: application/json

{
  "title": "Updated Title",
  "content": "Updated markdown content"
}
```

---

## Focus Sessions

### List Sessions
```http
GET /v1/focus/sessions?workspaceSlug={slug}
```

### Create Session
```http
POST /v1/focus/sessions
Content-Type: application/json

{
  "workspaceSlug": "my-workspace",
  "taskId": "task-uuid",
  "mode": "pomodoro",
  "durationSeconds": 1500
}
```

### Update Session (Complete/Pause)
```http
PATCH /v1/focus/sessions/{sessionId}
Content-Type: application/json

{
  "completedAt": "2024-01-15T11:00:00Z",
  "pausedAt": null,
  "pausedTotalSeconds": 120
}
```

---

## Labels

### List Labels
```http
GET /v1/labels?workspaceSlug={slug}
```

### Create Label
```http
POST /v1/labels
Content-Type: application/json

{
  "workspaceSlug": "my-workspace",
  "name": "Priority",
  "color": "#ef4444"
}
```

---

## Mail Integration

### List Mailboxes
```http
GET /v1/mailboxes?workspaceSlug={slug}
```

### Connect Mailbox
```http
POST /v1/mailboxes
Content-Type: application/json

{
  "workspaceSlug": "my-workspace",
  "label": "Work Email",
  "email": "user@example.com",
  "displayName": "John Doe",
  "imapHost": "imap.example.com",
  "imapPort": 993,
  "imapUsername": "user@example.com",
  "imapPassword": "password",
  "imapUseTls": true,
  "smtpHost": "smtp.example.com",
  "smtpPort": 587,
  "smtpUsername": "user@example.com",
  "smtpPassword": "password",
  "smtpUseTls": true
}
```

### Sync Mailbox
```http
POST /v1/mailboxes/{mailboxId}/sync
```

### List Mail Messages
```http
GET /v1/mail/messages?workspaceSlug={slug}&mailboxId={mailboxId}
```

### List Outgoing Mails
```http
GET /v1/mail/outgoing?workspaceSlug={slug}&mailboxId={mailboxId}
```

### Create Task from Mail
```http
POST /v1/mail/messages/{messageId}/create-task
Content-Type: application/json

{
  "boardGroupId": "group-uuid",
  "title": "Task from email",
  "dueAt": "2024-01-20T10:00:00Z",
  "color": "#3b82f6"
}
```

### Send/Schedule Outgoing Mail
```http
POST /v1/mail/outgoing
Content-Type: application/json

{
  "workspaceSlug": "my-workspace",
  "mailboxId": "mailbox-uuid",
  "to": [{"name": "Jane", "email": "jane@example.com"}],
  "cc": [],
  "bcc": [],
  "subject": "Hello",
  "textBody": "Email content",
  "htmlBody": "<p>Email content</p>",
  "scheduledFor": "2024-01-15T09:00:00Z"
}
```

---

## Activity Feed

### List Activity
```http
GET /v1/activity?workspaceSlug={slug}&limit=40&type=task&q=search
```

**Query Parameters:**
- `workspaceSlug` (required) - Workspace identifier
- `limit` - Number of entries (default: 40)
- `type` - Filter by type: `task`, `calendar`, `note`, `focus`, `mail`, `invite`, `system`
- `q` - Search query

---

## Members & Invites

### List Members
```http
GET /v1/members?workspaceSlug={slug}
```

### Update Member Role
```http
PATCH /v1/members/{memberId}
Content-Type: application/json

{
  "role": "admin",
  "status": "active"
}
```

**Roles:** `owner`, `admin`, `member`
**Status:** `active`, `inactive`

### List Invites
```http
GET /v1/invites?workspaceSlug={slug}
```

### Create Invite
```http
POST /v1/invites
Content-Type: application/json

{
  "workspaceSlug": "my-workspace",
  "email": "newuser@example.com",
  "role": "member"
}
```

### Accept Invite (Requires Auth)
```http
POST /v1/invites/{token}/accept
Content-Type: application/json

{
  "name": "New User"
}
```

### Revoke Invite
```http
POST /v1/invites/{token}/revoke
```

### Get Invite Details (Public - No Auth Required)
```http
GET /v1/invites/{token}
```

---

## Health & Metrics

### Health Check (Public)
```http
GET /v1/health
```
Returns service status and storage health.

**Response:**
```json
{
  "ok": true,
  "mode": "production",
  "timestamp": "2024-01-15T10:00:00Z",
  "storage": {
    "provider": "s3",
    "ok": true
  }
}
```

### Metrics (Requires Auth Token)
```http
GET /v1/metrics
Authorization: Bearer {METRICS_AUTH_TOKEN}
```

### Prometheus Metrics
```http
GET /v1/metrics/prometheus
Authorization: Bearer {METRICS_AUTH_TOKEN}
```

---

## Common Operations

### Creating a Complete Task

```javascript
// 1. Get workspace slug
const workspacesRes = await fetch('/v1/workspaces', { credentials: 'include' });
const workspaces = await workspacesRes.json();
const workspaceSlug = workspaces.data[0].slug;

// 2. Get or create board group
const groupsRes = await fetch(`/v1/board-groups?workspaceSlug=${workspaceSlug}`, { credentials: 'include' });
const groups = await groupsRes.json();
const groupId = groups.data[0].id;

// 3. Create task
const taskRes = await fetch('/v1/tasks', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  credentials: 'include',
  body: JSON.stringify({
    workspaceSlug,
    title: 'New Task',
    boardGroupId: groupId,
    status: 'todo'
  })
});
const task = await taskRes.json();
```

### Planning a Day

```javascript
// Create calendar event linked to a task
const eventRes = await fetch('/v1/calendar/events', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  credentials: 'include',
  body: JSON.stringify({
    workspaceSlug,
    title: 'Deep Work',
    startsAt: '2024-01-15T09:00:00Z',
    endsAt: '2024-01-15T11:00:00Z',
    linkedTaskId: task.data.id
  })
});

// Start a focus session
const sessionRes = await fetch('/v1/focus/sessions', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  credentials: 'include',
  body: JSON.stringify({
    workspaceSlug,
    taskId: task.data.id,
    mode: 'pomodoro',
    durationSeconds: 1500
  })
});
```

### Converting Email to Task

```javascript
// 1. Get mail messages
const messagesRes = await fetch(`/v1/mail/messages?workspaceSlug=${workspaceSlug}&mailboxId=${mailboxId}`, { credentials: 'include' });
const messages = await messagesRes.json();

// 2. Get board groups
const groupsRes = await fetch(`/v1/board-groups?workspaceSlug=${workspaceSlug}`, { credentials: 'include' });
const groups = await groupsRes.json();

// 3. Create task from message
const taskRes = await fetch(`/v1/mail/messages/${messages.data[0].id}/create-task`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  credentials: 'include',
  body: JSON.stringify({
    boardGroupId: groups.data[0].id,
    title: 'Follow up on email'
  })
});
```

---

## Error Handling

Always check for error responses:

```javascript
const response = await fetch('/v1/tasks', { ... });
const data = await response.json();

if (data.error) {
  console.error(`Error [${data.error.code}]: ${data.error.message}`);
  // Handle error appropriately
}
```

**Common Error Codes:**
- `UNAUTHORIZED` - Session invalid or expired (401)
- `FORBIDDEN` - No access to workspace (403)
- `NOT_FOUND` - Resource not found (404)
- `VALIDATION_ERROR` - Invalid request data (400)
- `CONFLICT` - Business rule conflict (409)
- `INTERNAL_ERROR` - Server error (500)

---

## Agent Guidelines

1. **Always verify authentication** before attempting operations - check session status
2. **Ask for workspace context** if not provided (workspace slug)
3. **Handle errors gracefully** and inform the user with clear messages
4. **Respect user authorization** - never attempt operations without proper credentials
5. **Use appropriate HTTP methods**: GET for reading, POST for creating, PATCH for updating, DELETE for removing
6. **Include Content-Type header** for all JSON requests
7. **Include credentials** in fetch requests for cookie-based auth

---

## Development Notes

- **Dev Mailbox:** In development, magic links are captured at `/api/dev-mailbox`
- **CORS:** Configure `CORS_ALLOW_ORIGINS` for production
- **File Uploads:** Max 20MB for attachments
- **Session Cookies:** Must be included in requests (`credentials: 'include'` in fetch)

---

## Quick Reference Table

| Resource | List | Create | Update | Delete |
|----------|------|--------|--------|--------|
| Workspaces | `GET /v1/workspaces` | - | - | - |
| Tasks | `GET /v1/tasks?workspaceSlug=X` | `POST /v1/tasks` | `PATCH /v1/tasks/{id}` | - |
| Board Groups | `GET /v1/board-groups?workspaceSlug=X` | `POST /v1/board-groups` | `PATCH /v1/board-groups/{id}` | - |
| Calendar Events | `GET /v1/calendar/events?workspaceSlug=X` | `POST /v1/calendar/events` | `PATCH /v1/calendar/events/{id}` | - |
| Notes | `GET /v1/notes?workspaceSlug=X` | `POST /v1/notes` | `PATCH /v1/notes/{id}` | - |
| Focus Sessions | `GET /v1/focus/sessions?workspaceSlug=X` | `POST /v1/focus/sessions` | `PATCH /v1/focus/sessions/{id}` | - |
| Labels | `GET /v1/labels?workspaceSlug=X` | `POST /v1/labels` | - | - |
| Members | `GET /v1/members?workspaceSlug=X` | - | `PATCH /v1/members/{id}` | - |
| Invites | `GET /v1/invites?workspaceSlug=X` | `POST /v1/invites` | - | `POST /v1/invites/{token}/revoke` |
| Mailboxes | `GET /v1/mailboxes?workspaceSlug=X` | `POST /v1/mailboxes` | - | - |
| Mail Messages | `GET /v1/mail/messages?workspaceSlug=X&mailboxId=Y` | - | - | - |
| Outgoing Mail | `GET /v1/mail/outgoing?workspaceSlug=X&mailboxId=Y` | `POST /v1/mail/outgoing` | - | - |
| Activity | `GET /v1/activity?workspaceSlug=X` | - | - | - |
| Health | `GET /v1/health` | - | - | - |
| Metrics | `GET /v1/metrics` | - | - | - |

---

## Data Models

### Task
```typescript
interface Task {
  id: string;
  workspaceSlug: string;
  boardGroupId: string;
  title: string;
  description: string;
  status: 'todo' | 'in-progress' | 'done';
  color: string;
  dueAt?: string;
  scheduledStart?: string;
  scheduledEnd?: string;
  assigneeId?: string;
  labelIds: string[];
  attachments: Attachment[];
  comments: Comment[];
  createdAt: string;
  updatedAt: string;
}
```

### CalendarEvent
```typescript
interface CalendarEvent {
  id: string;
  workspaceSlug: string;
  title: string;
  description: string;
  startsAt: string;
  endsAt: string;
  color: string;
  linkedTaskId?: string;
  attachments: Attachment[];
}
```

### Note
```typescript
interface Note {
  id: string;
  workspaceSlug: string;
  title: string;
  content: string;
  updatedAt: string;
}
```

### FocusSession
```typescript
interface FocusSession {
  id: string;
  workspaceSlug: string;
  taskId?: string;
  mode: string;
  startedAt: string;
  completedAt?: string;
  pausedAt?: string;
  pausedTotalSeconds: number;
  durationSeconds: number;
}
```

### Member
```typescript
interface Member {
  id: string;
  workspaceSlug: string;
  name: string;
  email: string;
  role: 'owner' | 'admin' | 'member';
  status: 'active' | 'inactive';
}
```

### Attachment
```typescript
interface Attachment {
  id: string;
  name: string;
  mimeType: string;
  size: number;
  dataURL: string;
}
```

### MailAddress
```typescript
interface MailAddress {
  name: string;
  email: string;
}
```