@froredion/codebase-indexer-sdk 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -2
- package/dist/index.d.ts +77 -0
- package/dist/index.js +126 -0
- package/package.json +31 -4
package/README.md
CHANGED
|
@@ -1,3 +1,64 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Codebase Indexer SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Search the code and documents indexed on your [Codebase Indexer](https://codebaseindexer.com) account from your own app:
|
|
4
|
+
ranked passages with their file, line range and heading trail, ready to put in a prompt. No dependencies, so it runs
|
|
5
|
+
anywhere `fetch` works: Node 18+, serverless, and edge runtimes.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @froredion/codebase-indexer-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { CodebaseIndexer, formatContext } from '@froredion/codebase-indexer-sdk';
|
|
13
|
+
|
|
14
|
+
const cbi = new CodebaseIndexer({ apiKey: process.env.CODEBASE_INDEXER_API_KEY });
|
|
15
|
+
|
|
16
|
+
const results = await cbi.search('What is our refund policy?', { limit: 5 });
|
|
17
|
+
const context = formatContext(results);
|
|
18
|
+
// [1] handbook/policies/refunds.md:6-8 — Refund policy › Who approves refunds
|
|
19
|
+
// Support leads approve refunds up to $500; anything larger goes to the finance team.
|
|
20
|
+
// …
|
|
21
|
+
|
|
22
|
+
// e.g. with any LLM:
|
|
23
|
+
// system: "Answer from the context and cite sources by number.\n\n" + context
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## API key
|
|
27
|
+
|
|
28
|
+
Create one under **API keys** in the [dashboard](https://codebaseindexer.com/api-keys). Keep it on your server, never in
|
|
29
|
+
a browser bundle. Without `apiKey`, the SDK reads `CODEBASE_INDEXER_API_KEY`.
|
|
30
|
+
|
|
31
|
+
## Getting content in
|
|
32
|
+
|
|
33
|
+
The SDK searches; it doesn't upload. Files are read and chunked where they live, with the CLI. Run it from CI to keep a
|
|
34
|
+
docs folder or repo up to date (only changed files are re-embedded):
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
CODEBASE_INDEXER_TOKEN=<api key> npx @froredion/codebase-indexer index ./docs
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Markdown/MDX, plain text, reStructuredText and code in 30+ languages are indexed.
|
|
41
|
+
|
|
42
|
+
## Options
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
cbi.search(query, {
|
|
46
|
+
scope: 'account', // default: everything on the account; or 'codebase' with codebaseId
|
|
47
|
+
codebaseId: '…',
|
|
48
|
+
limit: 10, // 1–50
|
|
49
|
+
languages: ['markdown'],
|
|
50
|
+
contentTypes: ['markdown'], // 'code' | 'text' | 'markdown' | 'markdown_code' | 'markdown_table' | 'frontmatter'
|
|
51
|
+
pathPrefix: 'policies/',
|
|
52
|
+
signal, // AbortSignal
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Each result has `codebaseName`, `filePath`, `startLine`, `endLine`, `headingPath`, `content`, `score`, `language`,
|
|
57
|
+
`contentType`, `codebaseId` and `workspaceId`.
|
|
58
|
+
|
|
59
|
+
Errors are `CodebaseIndexerError` with the HTTP `status` and an API `code`, for example `unauthorized`,
|
|
60
|
+
`payment_required` (billing needs attention; the message says what to do), or `spending_limit_reached`. Network errors,
|
|
61
|
+
`429` and `5xx` are retried twice, and a retried search is billed once.
|
|
62
|
+
|
|
63
|
+
Usage is billed to the account the key belongs to: pay per use, with prices at
|
|
64
|
+
[codebaseindexer.com](https://codebaseindexer.com/#pricing).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codebase Indexer SDK: search the code and documents indexed on your account from your own app,
|
|
3
|
+
* e.g. as the retrieval step of a RAG pipeline. No dependencies; runs anywhere `fetch` does (Node 18+,
|
|
4
|
+
* serverless, edge runtimes). Indexing stays with the CLI (`npx @froredion/codebase-indexer index`),
|
|
5
|
+
* because files are read and chunked where they live.
|
|
6
|
+
*
|
|
7
|
+
* const cbi = new CodebaseIndexer({ apiKey: process.env.CODEBASE_INDEXER_API_KEY });
|
|
8
|
+
* const results = await cbi.search('What is our refund policy?', { limit: 5 });
|
|
9
|
+
* const context = formatContext(results); // numbered passages with their sources, for a prompt
|
|
10
|
+
*/
|
|
11
|
+
export declare const DEFAULT_BASE_URL = "https://api.codebaseindexer.com";
|
|
12
|
+
export type ContentType = 'code' | 'text' | 'markdown' | 'markdown_code' | 'markdown_table' | 'frontmatter';
|
|
13
|
+
export interface CodebaseIndexerOptions {
|
|
14
|
+
/** A key from the dashboard (or a machine token). Defaults to CODEBASE_INDEXER_API_KEY, then CODEBASE_INDEXER_TOKEN. */
|
|
15
|
+
apiKey?: string;
|
|
16
|
+
baseUrl?: string;
|
|
17
|
+
fetch?: typeof fetch;
|
|
18
|
+
/** Retries for network errors, 429 and 5xx (default 2). A retried search is never billed twice. */
|
|
19
|
+
maxRetries?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface SearchOptions {
|
|
22
|
+
/** `account` (default): everything indexed on the account. `codebase`: one codebase, by `codebaseId`. */
|
|
23
|
+
scope?: 'account' | 'codebase';
|
|
24
|
+
codebaseId?: string;
|
|
25
|
+
/** 1–50, default 10. */
|
|
26
|
+
limit?: number;
|
|
27
|
+
/** Only these languages, e.g. ['markdown'] or ['typescript', 'python']. */
|
|
28
|
+
languages?: string[];
|
|
29
|
+
/** Only these chunk kinds, e.g. ['markdown'] for prose. */
|
|
30
|
+
contentTypes?: ContentType[];
|
|
31
|
+
/** Only files under this path, e.g. 'policies/'. */
|
|
32
|
+
pathPrefix?: string;
|
|
33
|
+
signal?: AbortSignal;
|
|
34
|
+
}
|
|
35
|
+
export interface SearchResult {
|
|
36
|
+
codebaseId: string;
|
|
37
|
+
codebaseName: string;
|
|
38
|
+
workspaceId: string;
|
|
39
|
+
filePath: string;
|
|
40
|
+
startLine: number;
|
|
41
|
+
endLine: number;
|
|
42
|
+
language: string;
|
|
43
|
+
contentType: ContentType;
|
|
44
|
+
/** Markdown heading trail, e.g. ['Refund policy', 'Who approves refunds']. */
|
|
45
|
+
headingPath: string[];
|
|
46
|
+
content: string;
|
|
47
|
+
/** Cosine similarity, higher is closer. */
|
|
48
|
+
score: number;
|
|
49
|
+
}
|
|
50
|
+
export declare class CodebaseIndexerError extends Error {
|
|
51
|
+
/** HTTP status; 0 when the API couldn't be reached. */
|
|
52
|
+
readonly status: number;
|
|
53
|
+
/** API error code, e.g. 'unauthorized', 'payment_required', 'spending_limit_reached'. */
|
|
54
|
+
readonly code: string;
|
|
55
|
+
constructor(message: string,
|
|
56
|
+
/** HTTP status; 0 when the API couldn't be reached. */
|
|
57
|
+
status: number,
|
|
58
|
+
/** API error code, e.g. 'unauthorized', 'payment_required', 'spending_limit_reached'. */
|
|
59
|
+
code: string);
|
|
60
|
+
}
|
|
61
|
+
export declare class CodebaseIndexer {
|
|
62
|
+
private readonly apiKey;
|
|
63
|
+
private readonly baseUrl;
|
|
64
|
+
private readonly fetcher;
|
|
65
|
+
private readonly maxRetries;
|
|
66
|
+
constructor(options?: CodebaseIndexerOptions);
|
|
67
|
+
/** Semantic search over the account's indexed code and documents, best match first. */
|
|
68
|
+
search(query: string, options?: SearchOptions): Promise<SearchResult[]>;
|
|
69
|
+
private request;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Renders results as numbered passages with their sources, ready to put in a prompt. Stops before
|
|
73
|
+
* `maxChars` (default 12,000) so the context stays within budget.
|
|
74
|
+
*/
|
|
75
|
+
export declare function formatContext(results: SearchResult[], options?: {
|
|
76
|
+
maxChars?: number;
|
|
77
|
+
}): string;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codebase Indexer SDK: search the code and documents indexed on your account from your own app,
|
|
3
|
+
* e.g. as the retrieval step of a RAG pipeline. No dependencies; runs anywhere `fetch` does (Node 18+,
|
|
4
|
+
* serverless, edge runtimes). Indexing stays with the CLI (`npx @froredion/codebase-indexer index`),
|
|
5
|
+
* because files are read and chunked where they live.
|
|
6
|
+
*
|
|
7
|
+
* const cbi = new CodebaseIndexer({ apiKey: process.env.CODEBASE_INDEXER_API_KEY });
|
|
8
|
+
* const results = await cbi.search('What is our refund policy?', { limit: 5 });
|
|
9
|
+
* const context = formatContext(results); // numbered passages with their sources, for a prompt
|
|
10
|
+
*/
|
|
11
|
+
export const DEFAULT_BASE_URL = 'https://api.codebaseindexer.com';
|
|
12
|
+
const SDK_VERSION = '0.1.0';
|
|
13
|
+
export class CodebaseIndexerError extends Error {
|
|
14
|
+
status;
|
|
15
|
+
code;
|
|
16
|
+
constructor(message,
|
|
17
|
+
/** HTTP status; 0 when the API couldn't be reached. */
|
|
18
|
+
status,
|
|
19
|
+
/** API error code, e.g. 'unauthorized', 'payment_required', 'spending_limit_reached'. */
|
|
20
|
+
code) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.status = status;
|
|
23
|
+
this.code = code;
|
|
24
|
+
this.name = 'CodebaseIndexerError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function envVar(name) {
|
|
28
|
+
const env = globalThis.process?.env;
|
|
29
|
+
return env?.[name] || undefined;
|
|
30
|
+
}
|
|
31
|
+
export class CodebaseIndexer {
|
|
32
|
+
apiKey;
|
|
33
|
+
baseUrl;
|
|
34
|
+
fetcher;
|
|
35
|
+
maxRetries;
|
|
36
|
+
constructor(options = {}) {
|
|
37
|
+
const apiKey = options.apiKey ?? envVar('CODEBASE_INDEXER_API_KEY') ?? envVar('CODEBASE_INDEXER_TOKEN');
|
|
38
|
+
if (!apiKey)
|
|
39
|
+
throw new CodebaseIndexerError('No API key: pass `apiKey` or set CODEBASE_INDEXER_API_KEY.', 0, 'unauthorized');
|
|
40
|
+
this.apiKey = apiKey;
|
|
41
|
+
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, '');
|
|
42
|
+
this.fetcher = options.fetch ?? ((input, init) => fetch(input, init));
|
|
43
|
+
this.maxRetries = options.maxRetries ?? 2;
|
|
44
|
+
}
|
|
45
|
+
/** Semantic search over the account's indexed code and documents, best match first. */
|
|
46
|
+
async search(query, options = {}) {
|
|
47
|
+
const scope = options.scope ?? (options.codebaseId ? 'codebase' : 'account');
|
|
48
|
+
if (scope === 'codebase' && !options.codebaseId)
|
|
49
|
+
throw new CodebaseIndexerError('scope "codebase" needs codebaseId', 400, 'validation_error');
|
|
50
|
+
const body = {
|
|
51
|
+
query,
|
|
52
|
+
scope,
|
|
53
|
+
codebase_id: options.codebaseId,
|
|
54
|
+
limit: options.limit ?? 10,
|
|
55
|
+
filters: { languages: options.languages, content_types: options.contentTypes, path_prefix: options.pathPrefix },
|
|
56
|
+
};
|
|
57
|
+
const response = await this.request('/v1/search', body, options.signal);
|
|
58
|
+
return response.results.map((r) => ({
|
|
59
|
+
codebaseId: r.codebase_id,
|
|
60
|
+
codebaseName: r.codebase_name,
|
|
61
|
+
workspaceId: r.workspace_id,
|
|
62
|
+
filePath: r.file_path,
|
|
63
|
+
startLine: r.start_line,
|
|
64
|
+
endLine: r.end_line,
|
|
65
|
+
language: r.language,
|
|
66
|
+
contentType: r.content_type,
|
|
67
|
+
headingPath: r.heading_path,
|
|
68
|
+
content: r.content,
|
|
69
|
+
score: r.score,
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
async request(path, body, signal) {
|
|
73
|
+
// One key per logical call: the API bills a retried request once.
|
|
74
|
+
const idempotencyKey = crypto.randomUUID();
|
|
75
|
+
let lastError;
|
|
76
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
77
|
+
if (attempt > 0)
|
|
78
|
+
await new Promise((r) => setTimeout(r, 400 * 2 ** (attempt - 1)));
|
|
79
|
+
let response;
|
|
80
|
+
try {
|
|
81
|
+
response = await this.fetcher(`${this.baseUrl}${path}`, {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
headers: {
|
|
84
|
+
authorization: `Bearer ${this.apiKey}`,
|
|
85
|
+
'content-type': 'application/json',
|
|
86
|
+
'idempotency-key': idempotencyKey,
|
|
87
|
+
'x-codebase-indexer-client': `sdk/${SDK_VERSION}`,
|
|
88
|
+
},
|
|
89
|
+
body: JSON.stringify(body),
|
|
90
|
+
signal,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
if (signal?.aborted)
|
|
95
|
+
throw error;
|
|
96
|
+
lastError = new CodebaseIndexerError(`Could not reach Codebase Indexer: ${error instanceof Error ? error.message : String(error)}`, 0, 'network_error');
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const json = (await response.json().catch(() => undefined));
|
|
100
|
+
if (response.ok && json)
|
|
101
|
+
return json;
|
|
102
|
+
lastError = new CodebaseIndexerError(json?.error?.message ?? `HTTP ${response.status}`, response.status, json?.error?.code ?? 'internal_error');
|
|
103
|
+
if (response.status < 500 && response.status !== 429)
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
throw lastError;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Renders results as numbered passages with their sources, ready to put in a prompt. Stops before
|
|
111
|
+
* `maxChars` (default 12,000) so the context stays within budget.
|
|
112
|
+
*/
|
|
113
|
+
export function formatContext(results, options = {}) {
|
|
114
|
+
const maxChars = options.maxChars ?? 12_000;
|
|
115
|
+
const parts = [];
|
|
116
|
+
let used = 0;
|
|
117
|
+
for (const [i, r] of results.entries()) {
|
|
118
|
+
const heading = r.headingPath.length ? ` — ${r.headingPath.join(' › ')}` : '';
|
|
119
|
+
const part = `[${i + 1}] ${r.codebaseName}/${r.filePath}:${r.startLine}-${r.endLine}${heading}\n${r.content.trim()}`;
|
|
120
|
+
if (used + part.length > maxChars && parts.length > 0)
|
|
121
|
+
break;
|
|
122
|
+
parts.push(part);
|
|
123
|
+
used += part.length + 2;
|
|
124
|
+
}
|
|
125
|
+
return parts.join('\n\n');
|
|
126
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@froredion/codebase-indexer-sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Search your Codebase Indexer code and documents from your own app: ranked passages with sources, ready for a RAG prompt.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"homepage": "https://codebaseindexer.com",
|
|
8
|
+
"keywords": ["rag", "retrieval", "semantic-search", "embeddings", "documentation", "code-search", "llm"],
|
|
9
|
+
"sideEffects": false,
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"main": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"files": ["dist/index.js", "dist/index.d.ts", "README.md"],
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=18"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc -p tsconfig.build.json",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"typecheck": "tsc --noEmit"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@codebase-indexer/shared": "workspace:*"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
}
|
|
33
|
+
}
|