All your context. One command.

Mail, calendars, files — and anything an Extension defines — grouped into Realms, indexed on your machine.

See it work before you connect anything

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 →
no-auth demo · about a minute, end to end
  1. Get the code

    Bun 1.3.14 · published CLI, no checkout
    bun add --global ctxindex
    ctxindex init
  2. Install the demo Extension

    standalone public repo · pinned Git commit
    ctxindex extension install git \
      'git+https://github.com:443/barisgit/ctxindex-extensions.git#main' \
      barisgit.github-issues
  3. Create a Realm, add the Source, sync

    public GitHub data · no OAuth or secrets
    ctxindex 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-issues
  4. Search it like an agent would

    typed Refs, filters, agent-efficient JSON
    ctxindex search issue --source gh-issues --local-only --format json
representative result · source id is created locally
{
  "results": [{
    "ref": "ctx://<source-id>/issue/84",
    "profile": { "id": "software.issue", "version": 1 },
    "title": "Ship the portable Agent Skill",
    "origin": "local"
  }],
  "warnings": []
}

If an agent can run a shell command, it can use ctxindex

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 json

Local access without taking ownership of your context

ctxindex is a gateway over the places where your context already lives, with explicit boundaries an operator can inspect.

Read the trust model →

Providers stay canonical

ctxindex keeps a local, purgeable materialization for fast discovery. Mail, Calendar Events, and files remain in the systems that own them.

Access stays explicit

Every Source belongs to a user-created Realm. Accounts, permissions, and provider operations remain bound to the configured Source.

Actions stay narrow

Typed provider mutations currently stop at reversible email Draft create and update. ctxindex never sends mail.

New context uses the same type-safe Extension SDK

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 Extension
index.ts · condensed from github.com/barisgit/ctxindex-extensions
import { 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],
})