Thanks for your interest in contributing! This document covers setup, conventions, and how to add new GraphQL providers.
# Clone and install
git clone https://github.com/c99e/gqlx.git
cd gqlx
bun install
# Run tests
bun test
# Type check
bunx tsc --noEmit
# Test in pi (loads the extension from your local checkout)
pi -e ./- Bun for dev tooling (
bun test,bun install), but no Bun-specific APIs insrc/files. The extension runs inside pi's Node.js runtime via jiti — usefetch,node:fs,node:path, etc. - Tests first. Write failing tests before implementation (red → green → refactor).
- TypeScript strict mode.
bunx tsc --noEmitmust pass. - Keep tool output token-efficient. The LLM reads every byte. Prefer compact formats; use verbose/expanded modes as opt-in.
- Fork the repo and create a feature branch
- Make your changes with tests
- Run
bun testandbunx tsc --noEmit— both must pass - Open a PR with a clear description of what and why
src/
index.ts — Extension entry point, tool registration, TUI renderers
types.ts — All TypeScript interfaces (introspection, schema index, providers)
schema.ts — Introspection query, fetch, and parsing into SchemaIndex
search.ts — Schema search with scoring, filtering, signatures
format.ts — SDL formatting, response formatting, result sorting
execute.ts — GraphQL execution, retry logic, batch/alias construction
providers.ts — Provider abstraction, Shopify + Linear implementations
test/
helpers.ts — Test utilities (SDL → introspection helper, shared test schema)
*.test.ts — Unit tests per module
The extension supports multiple GraphQL APIs through the GqlProvider interface. Adding a new one takes three steps:
The interface is in src/types.ts:
interface GqlProvider {
readonly name: string;
getEndpoint(): string;
getHeaders(): Promise<Record<string, string>>;
reset(): void;
}Create a new class in src/providers.ts:
export class ExampleProvider implements GqlProvider {
readonly name = 'example';
private config: { apiKey: string };
constructor(env: Record<string, string | undefined> = process.env) {
const apiKey = env.EXAMPLE_API_KEY;
if (!apiKey) {
throw new Error(
'Missing Example configuration. Required:\n\n EXAMPLE_API_KEY=your-api-key'
);
}
this.config = { apiKey };
}
getEndpoint(): string {
return 'https://api.example.com/graphql';
}
async getHeaders(): Promise<Record<string, string>> {
return {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.config.apiKey}`,
};
}
reset(): void {
// Clear any cached tokens/state. No-op for simple API key auth.
}
}Key points:
- Accept
envas a constructor parameter (defaults toprocess.env) so tests can inject values without touching the real environment. - Throw a helpful error with the exact variable names if config is missing.
- If auth involves token exchange (like Shopify's OAuth), cache the token in an instance field and clear it in
reset().
Update detectProviders() in src/providers.ts to check for your provider's env vars:
export function detectProviders(env = process.env): Map<string, GqlProvider> {
const providers = new Map<string, GqlProvider>();
// ... existing checks ...
const hasExample = !!env.EXAMPLE_API_KEY;
if (hasShopify) providers.set('shopify', new ShopifyProvider(env));
if (hasLinear) providers.set('linear', new LinearProvider(env));
if (hasExample) providers.set('example', new ExampleProvider(env)); // add here
return providers;
}All configured providers are detected simultaneously — the user can work with multiple APIs in the same session.
Update the README:
- Add a configuration section with the required env vars
- Update
.env.examplewith commented-out example values - Add to the
NO_PROVIDERS_MESSAGEconstant inproviders.ts