<!--
hoody-notifications Subskill (sdk)
Auto-generated by Hoody Skills Generator
Generated: 2026-06-20T00:06:16.758Z
Model: mimo-v2.5-pro
Mode: sdk


Tokens: 5836

DO NOT EDIT MANUALLY - Changes will be overwritten on next generation
-->

# hoody-notifications Subskill

## Overview

`hoody-notifications` delivers desktop and device notifications over HTTP. It enables agents and services to trigger, retrieve, dismiss, and stream real-time notifications to specified displays (e.g., `:0`, `:1`) using the standard `notify-send` mechanism under the hood.

**When to use this service:**

- Triggering desktop notifications to alert a user of a completed task, error, or event.
- Polling or streaming notifications for a specific display or all displays.
- Dismissing notifications programmatically after user acknowledgement.
- Serving notification icons referenced by notification payloads.
- Monitoring service health and Prometheus-compatible metrics.

**How it fits into Hoody philosophy:**

Like all Hoody Kit services, `hoody-notifications` runs inside a container, is addressed via a standardized URL pattern, and requires no manual DNS or SSL configuration. The service uses the Hoody Proxy routing system with the URL segment `n` (e.g., `...-n-{serviceId}.node.containers.hoody.com`). Authentication is handled through the Hoody API gateway; SDK examples below assume the client is already authenticated.

**Key concepts:**

- **Display**: An X11 display identifier (e.g., `"0"`, `":0"`, `"all"`) that determines where notifications appear.
- **Dismissal**: A two-phase workflow — `POST` marks notifications dismissed; `DELETE` clears that state.
- **Streaming**: `GET /api/v1/notifications/stream` opens a WebSocket for real-time push updates.

---

## Common Workflows

### Workflow 1: Trigger a Desktop Notification

Send a notification summary to a specific display.

```
import { HoodyClient } from '@hoody-ai/hoody-sdk'

const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: 'TOKEN' })

// Trigger notification on display :0
const result = await client.notifications.notify.trigger({
  display: ':0',
  summary: 'Build completed successfully',
})

console.log(result)
```

**State verification**: After triggering, retrieve notifications for that display to confirm the notification appeared.

```
const notifications = await client.notifications.list({ display: '0' })
console.log(notifications)
```

### Workflow 2: Retrieve Notifications for a Display

Fetch all notifications currently visible on a display. Supports a single ID, comma-separated list, or `"all"`.

```
import { HoodyClient } from '@hoody-ai/hoody-sdk'

const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: 'TOKEN' })

// Single display
const singleDisplay = await client.notifications.list({ display: '0' })

// Multiple displays
const multipleDisplays = await client.notifications.list({ display: '0,1' })

// All displays
const allDisplays = await client.notifications.list({ display: 'all' })

// With optional filters
const filtered = await client.notifications.list('0', 50, undefined, 'alice')
```

### Workflow 3: Paginate Through All Notifications

Use `listAll` to collect every page automatically, or `listIterator` for streaming control.

```
import { HoodyClient } from '@hoody-ai/hoody-sdk'

const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: 'TOKEN' })

// Collect all pages at once
const allNotifications = await client.notifications.listAll('0', 100)
console.log(`Total notifications: ${allNotifications.length}`)

// Async iterator for memory-efficient processing
const iterator = client.notifications.listIterator('0', 25)
for await (const notification of iterator) {
  console.log(notification)
}
```

### Workflow 4: Dismiss and Restore Notifications

Dismiss specific notifications by ID, then optionally restore them.

```
import { HoodyClient } from '@hoody-ai/hoody-sdk'

const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: 'TOKEN' })

// Step 1: Retrieve current notifications
const notifications = await client.notifications.list({ display: '0' })
const idsToDismiss = ['notif-001', 'notif-002']

// Step 2: Dismiss selected notifications
await client.notifications.dismiss({ notificationIds: idsToDismiss })

// Step 3: Verify dismissed notifications no longer appear
const remaining = await client.notifications.list({ display: '0' })
console.log(remaining)

// Step 4: Restore dismissed notifications if needed
await client.notifications.clearDismissed()

// Step 5: Verify restored notifications reappear
const restored = await client.notifications.list({ display: '0' })
console.log(restored)
```

### Workflow 5: Real-Time Notification Streaming

Subscribe to live updates for one or more displays via WebSocket.

```
import { HoodyClient } from '@hoody-ai/hoody-sdk'

const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: 'TOKEN' })

// Stream notifications for display :0
client.notifications.connectStream({ displays: '0' })

// Stream notifications for multiple displays
client.notifications.connectStream({ displays: '0,1' })

// Stream all displays
client.notifications.connectStream({ displays: 'all' })
```

### Workflow 6: Retrieve a Notification Icon

Fetch the icon image associated with a specific notification.

```
import { HoodyClient } from '@hoody-ai/hoody-sdk'

const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: 'TOKEN' })

const icon = await client.notifications.icons.get({ iconId: 'icon-abc123' })
// Returns the icon image binary data
```

### Workflow 7: Health Check and Metrics

Monitor service status and export Prometheus-compatible metrics.

```
import { HoodyClient } from '@hoody-ai/hoody-sdk'

const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: 'TOKEN' })

// Standardized 9-field health response (unauthenticated, always HTTP 200)
const health = await client.notifications.health.check()
console.log(health)

// Prometheus text exposition format
const metrics = await client.notifications.health.getMetrics()
console.log(metrics)
```

---

## Advanced Operations

### Advanced Workflow 1: Full Notify-Acknowledge-Dismiss Cycle

A complete end-to-end pattern: send a notification, wait for user interaction, then dismiss.

```
import { HoodyClient } from '@hoody-ai/hoody-sdk'

const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: 'TOKEN' })

// Step 1: Trigger notification
await client.notifications.notify.trigger({
  display: ':0',
  summary: 'Deployment finished — click to acknowledge',
})

// Step 2: Poll for the new notification
const pollForNotification = async (retries = 5, delayMs = 1000) => {
  for (let i = 0; i < retries; i++) {
    const notifications = await client.notifications.list({ display: '0' })
    const match = notifications.find((n: any) =>
      n.summary?.includes('Deployment finished')
    )
    if (match) return match
    await new Promise((res) => setTimeout(res, delayMs))
  }
  return null
}

const notification = await pollForNotification()
if (notification) {
  // Step 3: Dismiss once acknowledged
  await client.notifications.dismiss({ notificationIds: [notification.id] })
  console.log('Notification dismissed')
} else {
  console.log('Notification not found after retries')
}
```

### Advanced Workflow 2: Stream-Based Reactive Handling

Combine streaming with dismiss logic for real-time acknowledgement patterns.

```
import { HoodyClient } from '@hoody-ai/hoody-sdk'

const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: 'TOKEN' })

// Establish stream connection
client.notifications.connectStream({ displays: '0' })

// In practice, the stream emits events through the SDK's WebSocket handler.
// Application code processes each event as it arrives.
```

### Advanced Workflow 3: Clear All Dismissals Across Displays

A maintenance pattern to reset notification state globally.

```
import { HoodyClient } from '@hoody-ai/hoody-sdk'

const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: 'TOKEN' })

// Clear all dismissed notifications (display-agnostic)
await client.notifications.clearDismissed()

// Verify all notifications are now visible again across displays
const display0 = await client.notifications.list({ display: '0' })
const display1 = await client.notifications.list({ display: '1' })
console.log(`Display 0: ${display0.length} notifications`)
console.log(`Display 1: ${display1.length} notifications`)
```

### Advanced Workflow 4: Dismiss by Display Query

Retrieve notifications for a display, then dismiss in bulk.

```
import { HoodyClient } from '@hoody-ai/hoody-sdk'

const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: 'TOKEN' })

// Collect all notifications for a display
const all = await client.notifications.listAll('0', 200)

// Dismiss every notification
const allIds = all.map((n: any) => n.id)
if (allIds.length > 0) {
  await client.notifications.dismiss({ notificationIds: allIds })
  console.log(`Dismissed ${allIds.length} notifications`)
}

// Verify clean state
const remaining = await client.notifications.list({ display: '0' })
console.log(`Remaining: ${remaining.length}`)
```

### Error Recovery Patterns

```
import { HoodyClient } from '@hoody-ai/hoody-sdk'

const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: 'TOKEN' })

// Wrap dismiss operations with error handling
try {
  await client.notifications.dismiss({ notificationIds: ['invalid-id'] })
} catch (error: any) {
  if (error.status === 404) {
    console.log('Notification already dismissed or does not exist')
  } else if (error.status === 400) {
    console.log('Invalid request — check notificationIds format')
  } else {
    console.error(`Unexpected error: ${error.message}`)
  }
}

// Health check before critical operations
const health = await client.notifications.health.check()
if (health.status !== 'ok') {
  throw new Error('Notifications service is unhealthy')
}
```

### Performance Considerations

- **listAll vs list**: `listAll` collects all pages into memory. For large datasets, prefer `listIterator` to process one page at a time.
- **Streaming vs Polling**: Use `connectStream` for real-time needs. Polling `list` adds latency and server load.
- **Batch dismiss**: Always send one `dismiss` call with an array of IDs rather than looping individual calls.
- **Icon caching**: Icon IDs are derived from notification data (session, ID, timestamp, extension). Cache icon responses client-side when possible.

---

## Quick Reference

### Base URL Pattern

```
https://{projectId}-{containerId}-n-{serviceId}.{node}.containers.hoody.com
```

### Endpoints

| Method | Path | Description |
|--------|------|-------------|
| `POST` | `/api/v1/notifications/notify` | Trigger a desktop notification |
| `GET` | `/api/v1/notifications/{display}` | Get notifications for display(s) |
| `POST` | `/api/v1/notifications/dismiss` | Dismiss notifications by ID |
| `DELETE` | `/api/v1/notifications/dismiss` | Restore dismissed notifications |
| `GET` | `/api/v1/notifications/stream` | WebSocket real-time stream |
| `GET` | `/api/v1/notifications/icons/{iconId}` | Fetch notification icon |
| `GET` | `/api/v1/notifications/health` | Health check (unauthenticated) |
| `GET` | `/api/v1/notifications/metrics` | Prometheus metrics |

### Essential SDK Methods

```
// Notifications CRUD
client.notifications.notify.trigger({ display: ':0', summary: 'text' })
client.notifications.list('0', limit?, since?, username?, session?)
client.notifications.listAll({ display: '0' })
client.notifications.listIterator({ display: '0' })
client.notifications.dismiss({ notificationIds: ['id1', 'id2'] })
client.notifications.clearDismissed()

// Real-time
client.notifications.connectStream({ displays: '0,1' })

// Icons
client.notifications.icons.get({ iconId: 'iconId' })

// Health & Metrics
client.notifications.health.check()
client.notifications.health.getMetrics()
```

### Required Fields Summary

| Endpoint | Required Fields |
|----------|----------------|
| `POST /notify` | `display` (string), `summary` (string) |
| `POST /dismiss` | `notificationIds` (array) |
| `GET /stream` | `displays` (query string) |
| `GET /{display}` | `display` (path string) |
| `GET /icons/{iconId}` | `iconId` (path string) |
| `DELETE /dismiss` | None |
| `GET /health` | None |
| `GET /metrics` | None |