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.md
source 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

PatternWhen to UseExample
safeCall
wrapper
All search and index callsStructured error with operation context
Retry on 429Bulk indexing pipelines3s delay before retry
Batch paginationIndexing > 100 documents
bulkIndex
with batch tracking
Auth validationClient initFail fast on missing
GLEAN_API_KEY

Resources

Next Steps

Apply patterns in

glean-core-workflow-a
.