Claude Code Plugins

Community-maintained marketplace

Feedback

Implements real-time pub/sub messaging with Ably's edge infrastructure. Use when building real-time features requiring enterprise reliability, presence, message history, and global low-latency delivery.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name ably
description Implements real-time pub/sub messaging with Ably's edge infrastructure. Use when building real-time features requiring enterprise reliability, presence, message history, and global low-latency delivery.

Ably Pub/Sub

Enterprise-grade real-time messaging platform with global edge network. Supports pub/sub, presence, message history, and push notifications.

Quick Start

npm install ably

Client (Realtime)

import * as Ably from 'ably';

const ably = new Ably.Realtime({
  key: 'YOUR_API_KEY',  // Use token auth in production
  clientId: 'user-123'
});

// Wait for connection
await ably.connection.once('connected');

// Get a channel
const channel = ably.channels.get('my-channel');

// Subscribe to messages
await channel.subscribe('greeting', (message) => {
  console.log('Received:', message.data);
});

// Publish a message
await channel.publish('greeting', 'Hello World!');

Server (REST)

import * as Ably from 'ably';

const ably = new Ably.Rest({ key: 'YOUR_API_KEY' });

// Publish without maintaining connection
const channel = ably.channels.get('notifications');
await channel.publish('alert', { message: 'Server notification' });

Authentication

Token Auth (Recommended for Production)

// Client with auth endpoint
const ably = new Ably.Realtime({
  authUrl: '/api/ably-token',
  clientId: 'user-123'
});

// Server endpoint (Next.js example)
// app/api/ably-token/route.ts
import * as Ably from 'ably';

export async function GET(req: Request) {
  const ably = new Ably.Rest({ key: process.env.ABLY_API_KEY });

  const tokenParams = {
    clientId: 'user-123',  // Get from session
    capability: { '*': ['publish', 'subscribe', 'presence'] }
  };

  const tokenRequest = await ably.auth.createTokenRequest(tokenParams);
  return Response.json(tokenRequest);
}

Token Capabilities

const tokenParams = {
  clientId: 'user-123',
  capability: {
    'public-*': ['subscribe'],           // Subscribe to public channels
    'private-user-123': ['*'],            // Full access to own channel
    'chat-room-*': ['publish', 'subscribe', 'presence']
  }
};

Channels

Subscribe & Publish

const channel = ably.channels.get('chat');

// Subscribe to all messages
await channel.subscribe((message) => {
  console.log(message.name, message.data);
});

// Subscribe to specific event
await channel.subscribe('message', (message) => {
  console.log('Chat message:', message.data);
});

// Publish
await channel.publish('message', {
  text: 'Hello!',
  author: 'Alice'
});

// Publish multiple
await channel.publish([
  { name: 'message', data: 'First' },
  { name: 'message', data: 'Second' }
]);

// Unsubscribe
channel.unsubscribe('message', myHandler);
channel.unsubscribe();  // All handlers

Channel States

channel.on('attached', () => console.log('Channel attached'));
channel.on('detached', () => console.log('Channel detached'));
channel.on('failed', (err) => console.error('Channel failed:', err));

// Check state
console.log(channel.state);  // initialized, attaching, attached, detaching, detached, failed

Presence

Track who's online in a channel.

const channel = ably.channels.get('room-1');

// Enter presence
await channel.presence.enter({ status: 'online', name: 'Alice' });

// Update presence data
await channel.presence.update({ status: 'away' });

// Leave presence
await channel.presence.leave();

// Get current members
const members = await channel.presence.get();
members.forEach((member) => {
  console.log(member.clientId, member.data);
});

// Subscribe to presence events
await channel.presence.subscribe('enter', (member) => {
  console.log(member.clientId, 'entered');
});

await channel.presence.subscribe('leave', (member) => {
  console.log(member.clientId, 'left');
});

await channel.presence.subscribe('update', (member) => {
  console.log(member.clientId, 'updated:', member.data);
});

// Subscribe to all presence events
await channel.presence.subscribe((member) => {
  console.log(member.action, member.clientId, member.data);
});

Message History

const channel = ably.channels.get('chat');

// Get last 100 messages
const history = await channel.history({ limit: 100 });

history.items.forEach((message) => {
  console.log(message.timestamp, message.name, message.data);
});

// Paginate through history
let page = await channel.history({ limit: 50 });

while (page) {
  page.items.forEach(console.log);
  page = await page.next();  // null when no more pages
}

// Get messages from specific time
const history = await channel.history({
  start: Date.now() - 60000,  // Last minute
  direction: 'forwards'
});

Connection Management

// Connection events
ably.connection.on('connected', () => {
  console.log('Connected!');
});

ably.connection.on('disconnected', () => {
  console.log('Disconnected - will auto-reconnect');
});

ably.connection.on('suspended', () => {
  console.log('Connection suspended');
});

ably.connection.on('failed', (err) => {
  console.error('Connection failed:', err);
});

// Connection state
console.log(ably.connection.state);
// States: initialized, connecting, connected, disconnected, suspended, closing, closed, failed

// Manual control
ably.connection.close();
ably.connection.connect();

// Get connection ID
console.log(ably.connection.id);

React Integration

import * as Ably from 'ably';
import { AblyProvider, useChannel, usePresence } from 'ably/react';

// Setup client
const client = new Ably.Realtime({
  authUrl: '/api/ably-token'
});

function App() {
  return (
    <AblyProvider client={client}>
      <ChatRoom />
    </AblyProvider>
  );
}

function ChatRoom() {
  const [messages, setMessages] = useState([]);

  // Subscribe to channel
  const { channel } = useChannel('chat', 'message', (message) => {
    setMessages((prev) => [...prev, message.data]);
  });

  // Presence
  const { presenceData, updateStatus } = usePresence('chat', {
    name: 'Alice',
    status: 'online'
  });

  const sendMessage = () => {
    channel.publish('message', { text: 'Hello!' });
  };

  return (
    <div>
      <div>Online: {presenceData.length}</div>
      {messages.map((msg, i) => (
        <div key={i}>{msg.text}</div>
      ))}
      <button onClick={sendMessage}>Send</button>
    </div>
  );
}

Custom Hooks

import { useEffect, useState, useCallback } from 'react';
import * as Ably from 'ably';

const ably = new Ably.Realtime({ authUrl: '/api/ably-token' });

export function useAblyChannel(channelName) {
  const [channel, setChannel] = useState(null);
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    const ch = ably.channels.get(channelName);
    setChannel(ch);

    ch.subscribe((message) => {
      setMessages((prev) => [...prev, message]);
    });

    return () => {
      ch.unsubscribe();
      ch.detach();
    };
  }, [channelName]);

  const publish = useCallback((name, data) => {
    channel?.publish(name, data);
  }, [channel]);

  return { channel, messages, publish };
}

export function useAblyPresence(channelName, initialData) {
  const [members, setMembers] = useState([]);

  useEffect(() => {
    const channel = ably.channels.get(channelName);

    channel.presence.enter(initialData);

    channel.presence.subscribe((member) => {
      channel.presence.get().then(setMembers);
    });

    channel.presence.get().then(setMembers);

    return () => {
      channel.presence.leave();
      channel.presence.unsubscribe();
    };
  }, [channelName]);

  const updatePresence = useCallback((data) => {
    ably.channels.get(channelName).presence.update(data);
  }, [channelName]);

  return { members, updatePresence };
}

Advanced Features

Channel Rewind

Get historical messages on subscribe.

const channel = ably.channels.get('chat', {
  params: {
    rewind: '2m'  // Last 2 minutes, or '100' for last 100 messages
  }
});

await channel.subscribe((message) => {
  // Includes historical messages
});

Delta Compression

Reduce bandwidth for similar messages.

const channel = ably.channels.get('game-state', {
  params: { delta: 'vcdiff' }
});

Push Notifications

const channel = ably.channels.get('alerts');

// Subscribe device to push
await channel.push.subscribeDevice();

// Server: Publish with push notification
await channel.publish({
  name: 'alert',
  data: { message: 'Important update!' },
  extras: {
    push: {
      notification: {
        title: 'Alert',
        body: 'Check your app!'
      }
    }
  }
});

Error Handling

try {
  await channel.publish('event', data);
} catch (err) {
  if (err.code === 40160) {
    // Invalid credentials
  } else if (err.code === 42910) {
    // Rate limited
  }
  console.error('Ably error:', err.message);
}

Common Patterns

Chat Application

const channel = ably.channels.get('chat-room-1');

// Join room
await channel.presence.enter({ username: 'Alice' });

// Send message
async function sendMessage(text) {
  await channel.publish('message', {
    text,
    author: 'Alice',
    timestamp: Date.now()
  });
}

// Typing indicator
let typingTimeout;
function handleTyping() {
  channel.publish('typing', { user: 'Alice' });
  clearTimeout(typingTimeout);
  typingTimeout = setTimeout(() => {
    channel.publish('stopped-typing', { user: 'Alice' });
  }, 1000);
}

Live Dashboard

// Server: Publish metrics
setInterval(async () => {
  await channel.publish('metrics', {
    cpu: getCpuUsage(),
    memory: getMemoryUsage(),
    requests: getRequestCount()
  });
}, 1000);

// Client: Display metrics
channel.subscribe('metrics', (message) => {
  updateDashboard(message.data);
});

REST vs Realtime

Feature Realtime REST
Subscribe Yes No
Publish Yes Yes
Presence Yes Read only
History Yes Yes
Connection Persistent Per-request
Use case Browsers, apps Servers, scripts
// Use REST for server-side publishing
const rest = new Ably.Rest({ key: 'API_KEY' });
await rest.channels.get('updates').publish('event', data);