agentsclimarketplace

Glean sdk patterns

Skill jeremylongshore/claude-code-plugins-plus-skills/plugins/saas-packs/glean-pack/skills/glean-sdk-patterns

'Apply production-ready Glean API patterns with typed clients, batch indexing, pagination, and error handling.From its SKILL.md

Install
npx -y skills add jeremylongshore/claude-code-plugins-plus-skills --skill glean-sdk-patterns

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its file declares

Copied from the file, not written here

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

5.3 KB, ~1.3k tokens by cl100k_base, as published. Nobody here has run it

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: '[email protected]' }, 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 wrapperAll search and index callsStructured error with operation context
Retry on 429Bulk indexing pipelines3s delay before retry
Batch paginationIndexing > 100 documentsbulkIndex with batch tracking
Auth validationClient initFail fast on missing GLEAN_API_KEY

Resources

Next Steps

Apply patterns in glean-core-workflow-a.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,144. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.