From hootsuite-pack
Sets up local dev for Hootsuite API integrations with TypeScript OAuth client, auto token refresh, project structure, and mocked tests.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin hootsuite-packThis skill is limited to using the following tools:
Set up a development workflow for Hootsuite API integrations with mocked API responses, token management, and testing.
Configures Hootsuite OAuth 2.0 for REST API with app registration, .env setup, and TypeScript code for authorization flow, token exchange, and refresh.
Sets up local dev workflow for HubSpot integrations: project structure, TS client singleton, Vitest tests, API mocks, and sandbox accounts.
Sets up local Intercom dev environment with TypeScript client singleton, Vitest mocks, test isolation, and env configs for API testing and fast iteration.
Share bugs, ideas, or general feedback.
Set up a development workflow for Hootsuite API integrations with mocked API responses, token management, and testing.
hootsuite-integration/
├── src/
│ ├── hootsuite/
│ │ ├── client.ts # API client with token refresh
│ │ ├── auth.ts # OAuth 2.0 flow
│ │ ├── publishing.ts # Message scheduling
│ │ └── analytics.ts # Metrics retrieval
│ └── index.ts
├── tests/
│ ├── fixtures/ # Mock API responses
│ │ ├── profiles.json
│ │ └── messages.json
│ └── publishing.test.ts
├── .env.local
└── package.json
// src/hootsuite/client.ts
import 'dotenv/config';
class HootsuiteClient {
private accessToken: string;
private refreshToken: string;
private expiresAt: number;
private base = 'https://platform.hootsuite.com/v1';
constructor() {
this.accessToken = process.env.HOOTSUITE_ACCESS_TOKEN!;
this.refreshToken = process.env.HOOTSUITE_REFRESH_TOKEN!;
this.expiresAt = Date.now() + 3600000;
}
async request(path: string, options: RequestInit = {}) {
if (Date.now() > this.expiresAt - 60000) await this.refresh();
const response = await fetch(`${this.base}${path}`, {
...options,
headers: { 'Authorization': `Bearer ${this.accessToken}`, 'Content-Type': 'application/json', ...options.headers },
});
if (!response.ok) throw new Error(`Hootsuite API ${response.status}: ${await response.text()}`);
return response.json();
}
private async refresh() {
const res = await fetch('https://platform.hootsuite.com/oauth2/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': `Basic ${Buffer.from(`${process.env.HOOTSUITE_CLIENT_ID}:${process.env.HOOTSUITE_CLIENT_SECRET}`).toString('base64')}` },
body: new URLSearchParams({ grant_type: 'refresh_token', refresh_token: this.refreshToken }),
});
const tokens = await res.json();
this.accessToken = tokens.access_token;
this.refreshToken = tokens.refresh_token;
this.expiresAt = Date.now() + tokens.expires_in * 1000;
}
}
export const hootsuite = new HootsuiteClient();
// tests/publishing.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
const mockFetch = vi.fn();
vi.stubGlobal('fetch', mockFetch);
describe('Hootsuite Publishing', () => {
beforeEach(() => vi.clearAllMocks());
it('should schedule a message', async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({ data: [{ id: 'msg_123', state: 'SCHEDULED' }] }),
});
// Test scheduling logic
});
it('should list social profiles', async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({ data: [{ id: 'prof_1', type: 'TWITTER', socialNetworkUsername: 'test' }] }),
});
// Test profile listing
});
});
See hootsuite-sdk-patterns for production patterns.