Claude-code-plugins-plus-skills glean-sdk-patterns
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/glean-pack/skills/glean-sdk-patterns" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-glean-sdk-patterns && rm -rf "$T"
manifest:
plugins/saas-packs/glean-pack/skills/glean-sdk-patterns/SKILL.mdsource content
Glean SDK Patterns
Overview
Production-ready patterns for the Glean enterprise search platform. Glean uses POST-based REST endpoints for both search and indexing. Search queries go to the Client API while document ingestion uses the Indexing API. A structured client centralizes token management, enforces batch pagination for bulk indexing, and provides typed responses for search results.
Singleton Client
let _client: GleanClient | null = null; export function getClient(): GleanClient { if (!_client) { const domain = process.env.GLEAN_DOMAIN, key = process.env.GLEAN_API_KEY; if (!domain || !key) throw new Error('GLEAN_DOMAIN and GLEAN_API_KEY must be set'); _client = new GleanClient(domain, key); } return _client; } class GleanClient { private base: string; private h: Record<string, string>; constructor(domain: string, key: string) { this.base = `https://${domain}/api`; this.h = { 'Authorization': `Bearer ${key}`, 'Content-Type': 'application/json' }; } async search(query: string, opts: { pageSize?: number; datasource?: string } = {}) { const r = await fetch(`${this.base}/client/v1/search`, { method: 'POST', headers: { ...this.h, 'X-Glean-Auth-Type': 'BEARER' }, body: JSON.stringify({ query, pageSize: opts.pageSize ?? 20, requestOptions: opts.datasource ? { datasourceFilter: opts.datasource } : undefined }) }); if (!r.ok) throw new GleanError(r.status, await r.text()); return r.json() as Promise<GleanSearchResponse>; } async indexDocuments(datasource: string, docs: GleanDocument[]): Promise<void> { const r = await fetch(`${this.base}/index/v1/indexdocuments`, { method: 'POST', headers: this.h, body: JSON.stringify({ datasource, documents: docs }) }); if (!r.ok) throw new GleanError(r.status, await r.text()); } async bulkIndex(ds: string, docs: GleanDocument[], batch = 100): Promise<void> { for (let i = 0; i < docs.length; i += batch) await this.indexDocuments(ds, docs.slice(i, i + batch)); } }
Error Wrapper
export class GleanError extends Error { constructor(public status: number, message: string) { super(message); this.name = 'GleanError'; } } export async function safeCall<T>(operation: string, fn: () => Promise<T>): Promise<T> { try { return await fn(); } catch (err: any) { if (err instanceof GleanError && err.status === 429) { await new Promise(r => setTimeout(r, 3000)); return fn(); } if (err instanceof GleanError && err.status === 401) throw new GleanError(401, 'Invalid GLEAN_API_KEY'); throw new GleanError(err.status ?? 0, `${operation} failed: ${err.message}`); } }
Request Builder
class GleanSearchBuilder { private body: Record<string, any> = {}; query(q: string) { this.body.query = q; return this; } datasource(ds: string) { this.body.requestOptions = { datasourceFilter: ds }; return this; } pageSize(n: number) { this.body.pageSize = Math.min(n, 100); return this; } cursor(token: string) { this.body.cursor = token; return this; } facets(fields: string[]) { this.body.facetFilters = fields; return this; } build() { return this.body; } } // Usage: new GleanSearchBuilder().query('onboarding docs').datasource('confluence').pageSize(10).build();
Response Types
interface GleanDocument { id: string; title: string; url: string; body: { mimeType: string; textContent: string }; author?: { email: string }; updatedAt?: string; } interface GleanSearchResponse { results: Array<{ document: GleanDocument; snippets: string[]; score: number }>; totalResults: number; cursor?: string; } interface GleanDatasource { name: string; displayName: string; documentCount: number; lastCrawledAt: string; }
Testing Utilities
export function mockDocument(o: Partial<GleanDocument> = {}): GleanDocument { return { id: 'doc-001', title: 'Onboarding Guide', url: 'https://wiki.example.com/onboarding', body: { mimeType: 'text/plain', textContent: 'Welcome to the team...' }, author: { email: 'hr@example.com' }, updatedAt: '2025-03-01T00:00:00Z', ...o }; } export function mockSearchResponse(n = 3): GleanSearchResponse { return { results: Array.from({ length: n }, (_, i) => ({ document: mockDocument({ id: `doc-${i}` }), snippets: ['...match...'], score: 0.95 - i * 0.1 })), totalResults: n }; }
Error Handling
| Pattern | When to Use | Example |
|---|---|---|
wrapper | All search and index calls | Structured error with operation context |
| Retry on 429 | Bulk indexing pipelines | 3s delay before retry |
| Batch pagination | Indexing > 100 documents | with batch tracking |
| Auth validation | Client init | Fail fast on missing |
Resources
Next Steps
Apply patterns in
glean-core-workflow-a.