Providers stay canonical
ctxindex keeps a local, purgeable materialization for fast discovery. Mail, Calendar Events, and files remain in the systems that own them.
Mail, calendars, files — and anything an Extension defines — grouped into Realms, indexed on your machine.
Connecting real accounts means OAuth consent screens — the wrong first step for a two-minute evaluation. So the repo ships a demo Extension with deterministic fixture data: you run the exact Realm → Source → sync → search motions of a real setup, with zero credentials.
Requires Bun 1.3.14. The demo makes no provider request and creates no Account or Grant — and it doubles as a working example of the Extension SDK below.
Follow the guided quickstart →bun add --global ctxindex
ctxindex initctxindex extension install git \
'git+https://github.com:443/barisgit/ctxindex-extensions.git#main' \
barisgit.github-issuesctxindex realm add demo --name "Demo"
ctxindex source add github.issues --realm demo --label gh-issues \
--config-owner barisgit --config-repository ctxindex
ctxindex sync --source gh-issuesctxindex search issue --source gh-issues --local-only --format json{
"results": [{
"ref": "ctx://<source-id>/issue/84",
"profile": { "id": "software.issue", "version": 1 },
"title": "Ship the portable Agent Skill",
"origin": "local"
}],
"warnings": []
}Codex CLI, Claude Code, OpenClaw, and other code-executing agents compose the same commands you do. JSON and stable exit codes form the contract; generated CLI reference provides the exact surface.
Set up agent usage →Give your agent one instruction
“Use ctxindex to search the work Realm. Pass returned ctx:// Refs through unchanged, and retrieve only the result you need.”ctxindex search "FedEx invoice" --realm work --format jsonctxindex is a gateway over the places where your context already lives, with explicit boundaries an operator can inspect.
Read the trust model →ctxindex keeps a local, purgeable materialization for fast discovery. Mail, Calendar Events, and files remain in the systems that own them.
Every Source belongs to a user-created Realm. Accounts, permissions, and provider operations remain bound to the configured Source.
Typed provider mutations currently stop at reversible email Draft create and update. ctxindex never sends mail.
Define Profiles, Source Adapters, Providers, OAuth Apps, and passive documentation as ordinary typed values. Providerless Adapters need no authentication or synthetic Provider.
Extensions are trusted in-process code. Install only packages you trust; they are not sandboxed plugins.
Build an Extensionimport { auth, defineAdapter, defineExtension, defineProfile, defineProvider, z } from '@ctxindex/extension-sdk'
const issueProfile = defineProfile({
id: 'software.issue',
version: 1,
schema: z.object({
number: z.number().int().positive(),
title: z.string().min(1),
state: z.enum(['open', 'closed']),
updatedAt: z.string().datetime(),
}),
search: {
title: (payload) => payload.title,
fields: {
state: { type: 'string', extract: (payload) => payload.state },
updatedAt: { type: 'datetime', extract: (payload) => new Date(payload.updatedAt) },
},
},
})
const github = defineProvider({ id: 'github.public', auth: auth.none() })
const issues = defineAdapter({
id: 'github.issues',
provider: github,
providerApiHosts: ['api.github.com'],
profiles: [issueProfile],
routing: 'indexed',
capabilities: ['sync'],
operations: {
sync: async (context) => {
for (const payload of await fetchIssues(context)) {
await context.emit({ type: 'upsertResource', resource: toResource(context, payload) })
}
},
},
})
export default defineExtension({
id: 'barisgit.github-issues',
providers: [github],
adapters: [issues],
})