ctxrcodes
v0.6.0
Published
The official TypeScript SDK for CTXR, the persistent context layer for coding agents.
Readme
CTXR TypeScript SDK
The official TypeScript SDK for CTXR, the persistent context layer for coding agents. It provides typed access to projects, memories, AI Project Guides, Build Plans, Codebase Maps, and Agent Context Packs from Node.js 18+, modern browsers, Bun, and serverless runtimes. ESM and CommonJS are supported.
ctxrcodes is intentionally dependency-free at runtime. It builds on the Web Platform APIs already provided by supported runtimes and centralizes authentication, request serialization, timeout control, retry policy, response parsing, error normalization, and resource-specific types behind one client.
Table of contents
Capabilities
CTXR provides structured resources for managing projects, persistent knowledge, repository context, and implementation workflows.
| Capability | SDK Resource | Key Operations | Returns |
| --- | --- | --- | --- |
| Project Lifecycle | ctxr.projects | List · Get · Create · Update · Delete | Project |
| Persistent Knowledge | ctxr.memories | CRUD · Filter · AI Suggestions | Memory · MemorySuggestions |
| Repository Guidance | ctxr.projectGuides | Generate implementation guidance | ProjectGuide |
| Build Planning | ctxr.buildPlans | CRUD · Status · Check Tracking | BuildPlan |
| Codebase Intelligence | ctxr.codebaseMaps | Discover · Get · Generate · Delete | CodebaseMap |
| Agent-ready context | ctxr.contextPacks | List, retrieve, generate, delete | ContextPack |
Architecture
The public client owns small resource classes. Every resource delegates transport work to one shared HTTP layer, so authentication, timeouts, retries, response parsing, and error behavior remain consistent.
flowchart LR
A[Application or coding agent] --> B[Ctxr client]
B --> P[Projects]
B --> M[Memories]
B --> G[Project Guides]
B --> BP[Build Plans]
B --> CM[Codebase Maps]
B --> CP[Context Packs]
P & M & G & BP & CM & CP --> H[Shared HTTP client]
H --> V[Input and URL validation]
H --> AU[Bearer authentication]
H --> T[AbortController timeout]
H --> R[Retry policy]
H --> E[Response and error normalization]
E --> API[CTXR REST API]
Package layout
src/
├── client.ts # SDK client and composition root
├── config.ts # Configuration and environment handling
├── errors.ts # Error types, mapping, and redaction
│
├── http/ # HTTP transport layer
│ ├── request.ts # Fetch, headers, timeouts, URLs, and retries
│ ├── response.ts # Response parsing and HTTP status mapping
│ └── retry.ts # Retry-After handling and exponential backoff
│
├── resources/ # Domain-specific API resource clients
├── types/ # Public request and response types
└── utils/ # Shared validation and pagination utilities
Technology Stack
The SDK is built with a lightweight, standards-based TypeScript stack designed for portability, type safety, and minimal runtime dependencies.
| Layer | Technology | Purpose |
| --- | --- | --- |
| Language | TypeScript 5 · Strict Mode | Type-safe request, response, and resource contracts |
| HTTP Transport | Native Fetch API | Portable networking with no additional runtime dependency |
| Cancellation | AbortController | Request cancellation and per-request timeouts |
| URL Handling | WHATWG URL · URLSearchParams | Safe URL construction and query serialization |
| Module Formats | ESM · CommonJS | Compatibility with both import and require |
| Bundling | tsup · esbuild | Dual-format bundles, declarations, and source maps |
| Testing | Vitest | Isolated Fetch mocking with no production API traffic |
| Linting | ESLint 9 · typescript-eslint | Static analysis and code-quality enforcement |
| Package Target | npm · Node.js 18+ | Standards-based package distribution |
Design constraints
Zero production dependencies.
Strict TypeScript with exported declarations.
One request pipeline for every resource.
No API keys in query strings, logs, or structured error details.
No automatic replay of non-idempotent POST requests.
Runtime feature detection before reading Node.js environment variables or setting Node-specific headers.
Dependency injection through the optional
fetchconfiguration field.
Installation
npm install ctxrcodes
Or:
npm i ctxrcodes
Authentication
Create an API key in CTXR and pass it as a Bearer credential. The SDK never places keys in URLs.
import { Ctxr } from "ctxrcodes";
const ctxr = new Ctxr({ apiKey: process.env.CTXR_API_KEY! });
new Ctxr() reads CTXR_API_KEY when environment variables are available.
Security: Never expose an unrestricted CTXR API key in a public browser application. Use a restricted client token or call CTXR through your authenticated backend proxy.
Quick start
import { Ctxr } from "ctxrcodes";
const ctxr = new Ctxr({ apiKey: process.env.CTXR_API_KEY! });
const projects = await ctxr.projects.list();
console.log(projects.data);
Configuration
Configure the SDK using constructor options or environment variables.
| Option | Environment Variable | Default | Description |
| --- | --- | :---: | --- |
| apiKey | CTXR_API_KEY | Required | Bearer API key used to authenticate requests |
| baseUrl | CTXR_BASE_URL | https://api.ctxr.dev | Base URL for the CTXR API |
| timeout | CTXR_TIMEOUT | 30000 | Request timeout in milliseconds |
| maxRetries | CTXR_MAX_RETRIES | 2 | Maximum retries for safe requests and transient failures |
| headers | — | {} | Additional headers included with every request |
| fetch | — | globalThis.fetch | Custom Fetch implementation |
Environment Variables
CTXR_API_KEY=your_api_key
CTXR_BASE_URL=https://api.ctxr.dev
CTXR_TIMEOUT=30000
CTXR_MAX_RETRIES=2
Explicit options take precedence over environment variables. The SDK retries safe requests on network errors, `429`, and temporary `5xx` responses, respects `Retry-After`, and does not automatically retry ordinary POST requests.
### Configuration precedence
```mermaid
flowchart TD
A[Constructor option] -->|defined| D[Resolved configuration]
A -->|omitted| B[CTXR environment variable]
B -->|defined| D
B -->|omitted| C[SDK default]
C --> D
D --> E[Validate API key, URL and integers]
E --> F[Create shared HTTP client]
The API key has no fallback default: initialization fails synchronously with CtxrAuthenticationError when the resolved key is absent or blank. timeout and maxRetries must be non-negative integers. baseUrl must be an absolute HTTP or HTTPS URL and is normalized without a trailing slash.
Environment-based initialization
CTXR_API_KEY=ctxr_your_api_key
CTXR_BASE_URL=https://api.ctxr.dev
CTXR_TIMEOUT=30000
CTXR_MAX_RETRIES=2
import { Ctxr } from "ctxrcodes";
// Reads CTXR_* only when process.env exists in the current runtime.
const ctxr = new Ctxr();
Custom Fetch implementation
const ctxr = new Ctxr({
apiKey: "ctxr_restricted_key",
fetch: async (input, init) => {
// Add tracing or route through a runtime-specific Fetch adapter.
return globalThis.fetch(input, init);
},
});
The injected function must match typeof globalThis.fetch. This seam is also used by the test suite to guarantee that tests cannot contact production services.
API surface and endpoint map
All paths are joined against baseUrl. Dynamic identifiers are URL-encoded and API keys are sent exclusively through Authorization: Bearer <key>.
API Reference
The SDK provides a simple interface over the BTD REST API.
Projects
| SDK Method | HTTP | Endpoint |
| --- | :---: | --- |
| projects.list() | GET | /v1/projects |
| projects.get(id) | GET | /v1/projects/:id |
| projects.create(input) | POST | /v1/projects |
| projects.update(id, input) | PATCH | /v1/projects/:id |
| projects.delete(id) | DELETE | /v1/projects/:id |
Memories
| SDK Method | HTTP | Endpoint |
| --- | :---: | --- |
| memories.list() | GET | /v1/memories |
| memories.get(id) | GET | /v1/memories/:id |
| memories.create(input) | POST | /v1/memories |
| memories.update(id, input) | PATCH | /v1/memories/:id |
| memories.delete(id) | DELETE | /v1/memories/:id |
| memories.suggest(projectId) | POST | /v1/projects/:projectId/memories/suggestions |
Project Guides
| SDK Method | HTTP | Endpoint |
| --- | :---: | --- |
| projectGuides.generate(input) | POST | /v1/project-guides/generate |
Build Plans
| SDK Method | HTTP | Endpoint |
| --- | :---: | --- |
| buildPlans.list() | GET | /v1/build-plans |
| buildPlans.create(input) | POST | /v1/build-plans |
| buildPlans.get(id) | GET | /v1/build-plans/:id |
| buildPlans.update(id, input) | PATCH | /v1/build-plans/:id |
| buildPlans.delete(id) | DELETE | /v1/build-plans/:id |
Codebase Maps
| SDK Method | HTTP | Endpoint |
| --- | :---: | --- |
| codebaseMaps.listProjects() | GET | /v1/codebase-maps/projects |
| codebaseMaps.get(projectId) | GET | /v1/projects/:projectId/codebase-map |
| codebaseMaps.generate(projectId) | POST | /v1/projects/:projectId/codebase-map |
| codebaseMaps.delete(projectId) | DELETE | /v1/projects/:projectId/codebase-map |
Context Packs
| SDK Method | HTTP | Endpoint |
| --- | :---: | --- |
| contextPacks.list() | GET | /v1/context-packs |
| contextPacks.get(id) | GET | /v1/context-packs/:id |
| contextPacks.generate(input) | POST | /v1/context-packs/generate |
| contextPacks.delete(id) | DELETE | /v1/context-packs/:id |
The endpoint table describes the v0.6.0 SDK contract. A CTXR server deployment must implement these routes with authentication, authorization, validation, ownership checks, and compatible response schemas.
Projects
const page = await ctxr.projects.list({ page: 1, pageSize: 12 });
const project = await ctxr.projects.create({ name: "Storefront", language: "TypeScript" });
await ctxr.projects.update(project.id, { framework: "Next.js" });
await ctxr.projects.get(project.id);
await ctxr.projects.delete(project.id);
list(options?): Promise<PaginatedResult<Project>>— lists owned projects; may throw authentication, permission, validation, rate-limit, network, timeout, or API errors.get(projectId): Promise<Project>— returns a project; may additionally throwCtxrNotFoundError.create(input): Promise<Project>— creates a project.update(projectId, input): Promise<Project>— updates a project.delete(projectId): Promise<DeleteResult>— deletes a project.
Memories
const memory = await ctxr.memories.create({
projectId: "project-id", title: "Use server components by default",
content: "Only use client components for browser state or interaction.",
category: "convention", importance: "high", pinned: true,
});
await ctxr.memories.update(memory.id, { pinned: false });
list(options?): Promise<PaginatedResult<Memory>>— filters by project, query, category, importance, pinned state, and pagination.get(memoryId): Promise<Memory>— returns one memory.create(input): Promise<Memory>— creates a memory.update(memoryId, input): Promise<Memory>— updates a memory.delete(memoryId): Promise<DeleteResult>— deletes a memory.
All methods can throw the common SDK errors; single-resource methods may throw CtxrNotFoundError.
AI Memory Suggestions
const suggestions = await ctxr.memories.suggest("project-id", { task: "Add SSO", limit: 5 });
suggest(projectId, options?): Promise<MemorySuggestions> returns candidate memories with reasons. Generation errors may include validation, permission, rate-limit, timeout, network, and API errors.
AI Project Guides
const guide = await ctxr.projectGuides.generate({ projectId: "project-id", request: "Add organization workspaces" });
console.log(guide.agentPrompt);
generate(input): Promise<ProjectGuide> returns a summary, approach, relevant files, risks, safeguards, checks, agent prompt, and repository sources. It may throw any common SDK error.
Build Plans
const plan = await ctxr.buildPlans.create({ projectId: "project-id", title: "Workspace roles", objective: "Add role-based permissions" });
await ctxr.buildPlans.update(plan.id, { status: "in_progress", completedVerificationCheckIds: ["check-id"] });
list(options?): Promise<PaginatedResult<BuildPlan>>get(buildPlanId): Promise<BuildPlan>create(input): Promise<BuildPlan>update(buildPlanId, input): Promise<BuildPlan>delete(buildPlanId): Promise<DeleteResult>
These methods may throw common SDK errors; get, update, and delete may report not-found.
Codebase Maps
const projects = await ctxr.codebaseMaps.listProjects();
const map = await ctxr.codebaseMaps.generate("project-id");
console.log(map.executionFlows);
listProjects(): Promise<PaginatedResult<CodebaseMapProject>>get(projectId): Promise<CodebaseMap>generate(projectId): Promise<CodebaseMap>delete(projectId): Promise<DeleteResult>
Maps include repository overview, entry points, modules, responsibilities, dependencies, flows, models, agent context, and sources. Methods may throw common SDK errors.
Agent Context Packs
const pack = await ctxr.contextPacks.generate({
projectId: "project-id", task: "Add team invitations and workspace roles",
mode: "balanced",
sources: { memories: true, repository: true, codebaseMap: true, buildPlan: true },
});
console.log(pack.content.agentPrompt, pack.estimatedTokens);
list(options?): Promise<PaginatedResult<ContextPack>>get(contextPackId): Promise<ContextPack>generate(input): Promise<ContextPack>— defaults tobalancedwith all sources enabled.delete(contextPackId): Promise<DeleteResult>
Methods may throw common SDK errors; single-resource operations may report not-found.
Pagination
const result = await ctxr.projects.list({ page: 1, pageSize: 12 });
console.log(result.data, result.totalPages);
Paginated responses contain data, page, pageSize, total, and totalPages.
The SDK forwards pagination rather than accumulating pages in memory. This keeps network and memory use explicit in long-running agents:
let page = 1;
for (;;) {
const result = await ctxr.memories.list({
projectId: "project-id",
page,
pageSize: 50,
});
for (const memory of result.data) {
console.log(memory.title);
}
if (page >= result.totalPages) break;
page += 1;
}
Request lifecycle
Each resource call passes through the same deterministic pipeline:
sequenceDiagram
participant App as Application
participant Resource as Resource class
participant HTTP as HTTP client
participant API as CTXR API
App->>Resource: Typed method call
Resource->>Resource: Validate required identifiers
Resource->>HTTP: Path, method, query, body
HTTP->>HTTP: Construct URL and headers
HTTP->>HTTP: Start timeout controller
HTTP->>API: Fetch request
alt Successful JSON response
API-->>HTTP: 2xx + JSON
HTTP-->>Resource: Typed result
Resource-->>App: Promise<T>
else Empty response
API-->>HTTP: 204 or zero-length body
HTTP-->>App: undefined
else Retryable response
API-->>HTTP: 429 or temporary 5xx
HTTP->>HTTP: Retry-After or exponential backoff
HTTP->>API: Replay safe request
else API error
API-->>HTTP: 4xx or non-retried 5xx
HTTP-->>App: CtxrError subclass
else Timeout or network failure
HTTP-->>App: CtxrTimeoutError or CtxrNetworkError
end
Request headers
The shared transport builds headers in the following order:
SDK defaults such as
Accept: application/json.Content-Type: application/jsonwhen a body is present.User-Agent: ctxrcodes/0.6.0in Node.js runtimes that permit it.Client-level custom headers.
Request-level headers used internally by a resource.
The resolved
Authorizationheader, preventing accidental credential override.
JSON bodies are serialized exactly once. Query values accept strings, numbers, booleans, or undefined; undefined values are omitted.
Retry and idempotency model
Retries are deliberately conservative because replaying a mutation can duplicate state.
| Condition | GET | DELETE | PUT | PATCH | POST |
|---|---:|---:|---:|---:|---:|
| Network failure | Retry | Retry | Retry | No retry | No retry |
| 429 Too Many Requests | Retry | Retry | Retry | No retry | No retry |
| 500, 502, 503, 504 | Retry | Retry | Retry | No retry | No retry |
| Validation/auth/permission error | No retry | No retry | No retry | No retry | No retry |
POST can only become retryable inside the transport when a resource supplies an idempotency key. The current public resource methods do not claim endpoint-level idempotency and therefore do not replay POST requests.
When Retry-After is present, the SDK supports both formats defined for HTTP:
Delta seconds, such as
Retry-After: 2.An HTTP date, such as
Retry-After: Wed, 21 Oct 2026 07:28:00 GMT.
Without that header, the delay uses capped exponential backoff with jitter:
delay = min(500 ms × 2^attempt + random(0..250 ms), 10 seconds)
maxRetries counts additional attempts after the initial request. A value of 0 disables retrying.
TypeScript model
All request inputs, response entities, pagination contracts, error details, status values, categories, importance levels, context modes, and source settings are exported from the package root:
import type {
BuildPlanStatus,
ContextPack,
CreateMemoryInput,
MemoryCategory,
PaginatedResult,
Project,
} from "ctxrcodes";
Known values use string unions rather than unconstrained strings:
type BuildPlanStatus = "draft" | "ready" | "in_progress" | "completed";
type ContextPackMode = "compact" | "balanced" | "complete";
type MemoryImportance = "low" | "normal" | "high" | "critical";
The project enables strict, noUncheckedIndexedAccess, and exactOptionalPropertyTypes. Generated .d.ts and .d.cts declarations are published beside the ESM and CommonJS bundles.
Context Pack source resolution
Generation defaults are resolved client-side before the request is sent:
const pack = await ctxr.contextPacks.generate({
projectId: "project-id",
task: "Move authorization checks into a shared policy layer",
// mode defaults to "balanced"
sources: {
repository: false,
// memories, codebaseMap, and buildPlan remain true
},
});
This produces the following effective source selection:
{
"memories": true,
"repository": false,
"codebaseMap": true,
"buildPlan": true
}
Error handling
import { CtxrNotFoundError, CtxrRateLimitError } from "ctxrcodes";
try { await ctxr.projects.get("invalid-id"); }
catch (error) {
if (error instanceof CtxrNotFoundError) console.error("Project was not found");
else if (error instanceof CtxrRateLimitError) console.error(error.retryAfter);
else throw error;
}
Every error extends CtxrError and can expose status, code, requestId, retryAfter, and sanitized details. Subclasses cover authentication, permission, validation, not-found, rate-limit, timeout, network, and general API failures.
HTTP-to-error mapping
| Failure | SDK error | Retryable by transport |
|---|---|---:|
| Missing or blank API key | CtxrAuthenticationError | No request sent |
| Invalid local configuration/input | CtxrValidationError | No request sent |
| 400, 422 | CtxrValidationError | No |
| 401 | CtxrAuthenticationError | No |
| 403 | CtxrPermissionError | No |
| 404 | CtxrNotFoundError | No |
| 429 | CtxrRateLimitError | Safe methods only |
| Timeout/abort | CtxrTimeoutError | No after deadline |
| Fetch/network failure | CtxrNetworkError | Safe methods only |
| Other non-2xx response | CtxrApiError | Temporary safe-method 5xx responses only |
Sensitive keys within structured error details—including authorization, API key, token, and cookie-shaped fields—are replaced recursively with [REDACTED]. Error objects may safely be reported to telemetry, but applications should still apply their own logging policy to surrounding data.
Runtime compatibility
The package uses native Fetch, AbortController, URL, and Web Platform response types. It supports Node.js 18+, current browsers, Bun, and modern serverless runtimes. Inject fetch if a runtime does not provide it globally.
| Runtime | Support | Module format | Notes |
|---|---:|---|---|
| Node.js 18+ | Yes | ESM and CommonJS | Native Fetch and AbortController required |
| Bun | Yes | ESM and CommonJS | Uses Bun's Web Platform implementation |
| Modern browsers | Yes | ESM/bundled | Use restricted credentials or a backend proxy |
| Vercel/Cloudflare-style serverless | Yes | ESM | Ensure the runtime exposes standard Fetch APIs |
| Node.js below 18 | No | — | Not covered by the package engine contract |
The package declares sideEffects: false, allowing compatible bundlers to tree-shake unused exports. Resource instances are lightweight and share a single HTTP configuration.
Security model
The SDK is one layer of the security boundary; server-side authorization remains mandatory. A valid API key must never imply access to every resource.
flowchart LR
U[User or agent] --> S[Trusted backend]
S -->|Bearer API key over HTTPS| C[CTXR API]
C --> A[Authenticate key]
A --> O[Verify resource ownership]
O --> V[Validate operation and payload]
V --> D[(CTXR data)]
B[Public browser] -. restricted token or backend proxy .-> S
Keep unrestricted keys server-side and rotate exposed credentials.
Scope keys to the minimum required projects and permissions.
Do not put keys in URLs, logs, source control, or browser bundles.
Use HTTPS and validate ownership in every server endpoint.
Treat custom headers and generated agent prompts as potentially sensitive application data.
Enforce authentication, ownership, validation, rate limits, and audit logging in the CTXR API.
Never expose database, GitHub, OpenAI, session, or server authentication credentials through SDK responses.
Examples
Runnable examples live in examples/: initialization, projects, memories, Build Plans, Codebase Maps, Context Packs, and a complete Node.js agent workflow that writes agent-context.md.
| File | Demonstrates |
|---|---|
| examples/basic.ts | Explicit initialization and project listing |
| examples/projects.ts | Project creation and retrieval |
| examples/memories.ts | Typed persistent-memory creation |
| examples/build-plan.ts | Build Plan creation |
| examples/codebase-map.ts | Retrieve-or-generate control flow |
| examples/context-pack.ts | Balanced Agent Context Pack generation |
| examples/agent-workflow.ts | End-to-end project, memory, map, plan, pack, and Markdown workflow |
Complete agent workflow
flowchart LR
P[Load project] --> M[Query relevant memories]
M --> C{Codebase Map exists?}
C -->|Yes| G[Use existing map]
C -->|No| N[Generate map]
G & N --> B[Create Build Plan]
B --> A[Generate Agent Context Pack]
A --> F[Write agent-context.md]
The workflow example uses node:fs/promises only in the Node-specific file. Core SDK code does not import Node filesystem modules.
Development
npm install
npm run typecheck
npm run lint
npm test
npm run build
Tests mock Fetch and never contact production services.
Scripts
| Command | Purpose |
|---|---|
| npm run dev | Rebuild bundles in watch mode |
| npm run typecheck | Validate strict TypeScript without emitting files |
| npm run lint | Run ESLint across source, tests, and examples |
| npm test | Run the Vitest suite once |
| npm run test:watch | Run Vitest interactively in watch mode |
| npm run build | Generate clean ESM, CJS, declaration, and source-map output |
| npm run prepublishOnly | Type-check, test, and build before publication |
Test strategy
The test suite injects deterministic Fetch mocks and covers:
Explicit configuration and missing API keys.
Authorization and custom request headers.
Base URL normalization and query construction.
Successful JSON and empty
204responses.Validation, authentication, permission, not-found, rate-limit, and general API errors.
Recursive redaction of credential-shaped error details.
Retry-After, safe-request retries, and prevention of POST replay.Timeout cancellation and terminal network failures.
Pagination and Context Pack default resolution.
When adding a resource method, test the exact method, path, query, serialized body, response type, and failure behavior. Production API calls are prohibited in unit tests.
Contribution checklist
Keep resource methods thin and delegate transport behavior to
HttpClient.Export all new public input and response types from
src/types/index.tsandsrc/index.ts.Use string unions for closed server enums.
Add JSDoc to public classes and methods.
Add Fetch-mocked tests for success, validation, authorization, and relevant retry behavior.
Run the full validation pipeline before opening a release change.
Build output and publishing
tsup compiles one TypeScript entry point into parallel package targets:
flowchart LR
S[src/index.ts] --> T[tsup]
T --> E[dist/index.js - ESM]
T --> C[dist/index.cjs - CommonJS]
T --> D[dist/index.d.ts]
T --> DC[dist/index.d.cts]
T --> SM[Source maps]
The package export map selects the correct artifact automatically:
{
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
}
Only dist, README.md, LICENSE, and npm's required package.json are included in the published tarball. Source files, tests, examples, local environment files, caches, and credentials are excluded.
Run npm pack --dry-run to inspect the package. prepublishOnly type-checks, tests, and builds. Confirm the version is 0.6.0 before publishing.
npm run typecheck
npm run lint
npm test
npm run build
npm pack --dry-run
Before release, also verify both resolution paths against the generated output:
// ESM
import { Ctxr } from "ctxrcodes";
// CommonJS
const { Ctxr } = require("ctxrcodes");
Published artifact contract
| Artifact | Consumer |
|---|---|
| dist/index.js | ESM runtimes and bundlers |
| dist/index.cjs | CommonJS applications |
| dist/index.d.ts | ESM TypeScript resolution |
| dist/index.d.cts | CommonJS TypeScript resolution |
| dist/*.map | Debuggers and stack-trace tooling |
| README.md | npm package documentation |
| LICENSE | MIT license terms |
