@larkup/cli 0.1.14
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 +58 -0
- package/dist/chroma-GIDEKUIH.js +276 -0
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/index.js +3060 -0
- package/dist/lancedb-E2JLQERV.js +163 -0
- package/dist/pinecone-YZHFLCKI.js +315 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Larkup CLI
|
|
2
|
+
|
|
3
|
+
The official Command Line Interface for **Larkup**.
|
|
4
|
+
Build, index, and serve a Retrieval-Augmented Generation (RAG) pipeline directly from your terminal.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
You can install the CLI globally via npm or run it via npx:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install -g @larkup/cli
|
|
12
|
+
# or
|
|
13
|
+
npx @larkup/cli
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
The CLI provides a set of commands to manage your RAG workspaces, configure vector stores, ingest documents, and query your data.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
larkup <command> [options]
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Core Commands
|
|
25
|
+
|
|
26
|
+
- **`init [name]`**: Create a new RAG server (workspace project).
|
|
27
|
+
- **`config`**: Configure vector stores and embedding models for your active workspace.
|
|
28
|
+
- **`add-doc`**: Ingest documents (local files, URLs, or text) into your workspace.
|
|
29
|
+
- **`index`**: Process, chunk, and index your ingested documents into the configured vector database.
|
|
30
|
+
- **`serve`**: Spin up the generated RAG server locally for fast testing and API integration.
|
|
31
|
+
- **`query`**: Test retrieval quality by querying your indexed data directly from the terminal.
|
|
32
|
+
- **`chat`**: Start an interactive chat session with your RAG pipeline in the terminal.
|
|
33
|
+
- **`generate`**: Generate server configurations or boilerplate code.
|
|
34
|
+
- **`settings`**: View or update your global settings and environment variables.
|
|
35
|
+
|
|
36
|
+
### Managing Workspaces (Servers)
|
|
37
|
+
|
|
38
|
+
- **`use`**: Switch your active server/workspace context.
|
|
39
|
+
|
|
40
|
+
## Example Workflow
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# 1. Initialize a new project
|
|
44
|
+
larkup init my-rag-server
|
|
45
|
+
|
|
46
|
+
# 2. Add documents to the project
|
|
47
|
+
larkup add-doc ./data/knowledge-base.txt
|
|
48
|
+
|
|
49
|
+
# 3. Index the documents into the vector store
|
|
50
|
+
larkup index
|
|
51
|
+
|
|
52
|
+
# 4. Start an interactive chat session with your data
|
|
53
|
+
larkup chat
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Documentation
|
|
57
|
+
|
|
58
|
+
For more detailed guides and information, please refer to the [Larkup Documentation](https://larkup.de/docs) or visit our [Web UI](https://larkup.de).
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import "./chunk-3RG5ZIWI.js";
|
|
2
|
+
|
|
3
|
+
// ../../packages/vector-stores/src/adapters/chroma.ts
|
|
4
|
+
var RRF_K = 60;
|
|
5
|
+
var ChromaAdapter = class {
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.config = config;
|
|
8
|
+
this.collectionName = config.collectionName?.trim() || "documents";
|
|
9
|
+
}
|
|
10
|
+
config;
|
|
11
|
+
client = null;
|
|
12
|
+
collection = null;
|
|
13
|
+
collectionName;
|
|
14
|
+
/**
|
|
15
|
+
* Lazily connect to the Chroma server or cloud.
|
|
16
|
+
* The `chromadb` package is dynamically imported to avoid module-eval
|
|
17
|
+
* failures when the package isn't installed.
|
|
18
|
+
*/
|
|
19
|
+
async getClient() {
|
|
20
|
+
if (this.client) return this.client;
|
|
21
|
+
let chromadb;
|
|
22
|
+
try {
|
|
23
|
+
chromadb = await import("chromadb");
|
|
24
|
+
} catch {
|
|
25
|
+
throw new Error(
|
|
26
|
+
'The "chromadb" package is not installed. Please install it first via the Configure page or run: pnpm add chromadb --filter @larkup/vector-stores'
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
if (this.config.mode === "cloud") {
|
|
30
|
+
if (!this.config.apiKey) {
|
|
31
|
+
throw new Error("Chroma Cloud requires an API key.");
|
|
32
|
+
}
|
|
33
|
+
const CloudClientClass = chromadb.CloudClient ?? chromadb.default?.CloudClient;
|
|
34
|
+
if (!CloudClientClass) {
|
|
35
|
+
throw new Error("Could not find CloudClient in the chromadb package.");
|
|
36
|
+
}
|
|
37
|
+
this.client = new CloudClientClass({
|
|
38
|
+
apiKey: this.config.apiKey,
|
|
39
|
+
tenant: this.config.tenant || "default_tenant",
|
|
40
|
+
database: this.config.database || "default_database"
|
|
41
|
+
});
|
|
42
|
+
} else {
|
|
43
|
+
const host = this.config.host?.trim() || "http://localhost:8000";
|
|
44
|
+
const ClientClass = chromadb.ChromaClient ?? chromadb.default?.ChromaClient;
|
|
45
|
+
if (!ClientClass) {
|
|
46
|
+
throw new Error("Could not find ChromaClient in the chromadb package.");
|
|
47
|
+
}
|
|
48
|
+
const opts = { path: host };
|
|
49
|
+
if (this.config.authToken) {
|
|
50
|
+
opts.auth = {
|
|
51
|
+
provider: "token",
|
|
52
|
+
credentials: this.config.authToken
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
this.client = new ClientClass(opts);
|
|
56
|
+
}
|
|
57
|
+
return this.client;
|
|
58
|
+
}
|
|
59
|
+
async getCollection() {
|
|
60
|
+
if (this.collection) return this.collection;
|
|
61
|
+
const client = await this.getClient();
|
|
62
|
+
this.collection = await client.getOrCreateCollection({
|
|
63
|
+
name: this.collectionName,
|
|
64
|
+
metadata: { "hnsw:space": "cosine" }
|
|
65
|
+
});
|
|
66
|
+
return this.collection;
|
|
67
|
+
}
|
|
68
|
+
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
69
|
+
async init(_dimensions) {
|
|
70
|
+
await this.getCollection();
|
|
71
|
+
}
|
|
72
|
+
async reset() {
|
|
73
|
+
const client = await this.getClient();
|
|
74
|
+
try {
|
|
75
|
+
await client.deleteCollection({ name: this.collectionName });
|
|
76
|
+
} catch {
|
|
77
|
+
}
|
|
78
|
+
this.collection = null;
|
|
79
|
+
}
|
|
80
|
+
// ── Upsert ─────────────────────────────────────────────────────────────────
|
|
81
|
+
async upsert(records) {
|
|
82
|
+
if (records.length === 0) return;
|
|
83
|
+
const collection = await this.getCollection();
|
|
84
|
+
const ids = [];
|
|
85
|
+
const embeddings = [];
|
|
86
|
+
const documents = [];
|
|
87
|
+
const metadatas = [];
|
|
88
|
+
for (const r of records) {
|
|
89
|
+
ids.push(r.id);
|
|
90
|
+
embeddings.push(r.vector);
|
|
91
|
+
documents.push(r.text);
|
|
92
|
+
metadatas.push(this.buildMetadata(r));
|
|
93
|
+
}
|
|
94
|
+
const BATCH = 4096;
|
|
95
|
+
for (let i = 0; i < ids.length; i += BATCH) {
|
|
96
|
+
await collection.upsert({
|
|
97
|
+
ids: ids.slice(i, i + BATCH),
|
|
98
|
+
embeddings: embeddings.slice(i, i + BATCH),
|
|
99
|
+
documents: documents.slice(i, i + BATCH),
|
|
100
|
+
metadatas: metadatas.slice(i, i + BATCH)
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
buildMetadata(r) {
|
|
105
|
+
const meta = {
|
|
106
|
+
title: r.title,
|
|
107
|
+
source: r.source,
|
|
108
|
+
documentId: r.documentId,
|
|
109
|
+
chunkIndex: r.chunkIndex
|
|
110
|
+
};
|
|
111
|
+
if (r.url) meta.url = r.url;
|
|
112
|
+
if (r.metadata) {
|
|
113
|
+
for (const [k, v] of Object.entries(r.metadata)) {
|
|
114
|
+
if (v === null || v === void 0) continue;
|
|
115
|
+
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
|
|
116
|
+
meta[k] = v;
|
|
117
|
+
} else {
|
|
118
|
+
meta[k] = JSON.stringify(v);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return meta;
|
|
123
|
+
}
|
|
124
|
+
// ── Count ──────────────────────────────────────────────────────────────────
|
|
125
|
+
async count() {
|
|
126
|
+
try {
|
|
127
|
+
const collection = await this.getCollection();
|
|
128
|
+
return await collection.count();
|
|
129
|
+
} catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// ── Query ──────────────────────────────────────────────────────────────────
|
|
134
|
+
async query(vector, topK, queryText) {
|
|
135
|
+
const collection = await this.getCollection();
|
|
136
|
+
const isSemantic = this.config.indexType === "semantic";
|
|
137
|
+
const isLexical = this.config.indexType === "lexical";
|
|
138
|
+
if (isSemantic || !queryText?.trim()) {
|
|
139
|
+
return this.denseQuery(collection, vector, topK);
|
|
140
|
+
}
|
|
141
|
+
if (isLexical) {
|
|
142
|
+
return this.documentQuery(collection, queryText, topK);
|
|
143
|
+
}
|
|
144
|
+
const denseHits = await this.denseQuery(collection, vector, topK);
|
|
145
|
+
try {
|
|
146
|
+
const textHits = await this.documentQuery(collection, queryText, topK);
|
|
147
|
+
if (textHits.length > 0) {
|
|
148
|
+
return rrfMerge(denseHits, textHits, topK);
|
|
149
|
+
}
|
|
150
|
+
} catch {
|
|
151
|
+
}
|
|
152
|
+
return denseHits;
|
|
153
|
+
}
|
|
154
|
+
async denseQuery(collection, vector, topK) {
|
|
155
|
+
const result = await collection.query({
|
|
156
|
+
queryEmbeddings: [vector],
|
|
157
|
+
nResults: topK,
|
|
158
|
+
include: ["documents", "metadatas", "distances"]
|
|
159
|
+
});
|
|
160
|
+
return this.parseQueryResult(result);
|
|
161
|
+
}
|
|
162
|
+
async documentQuery(collection, queryText, topK) {
|
|
163
|
+
const result = await collection.get({
|
|
164
|
+
whereDocument: { "$contains": queryText },
|
|
165
|
+
limit: topK,
|
|
166
|
+
include: ["documents", "metadatas"]
|
|
167
|
+
});
|
|
168
|
+
return this.parseGetResult(result);
|
|
169
|
+
}
|
|
170
|
+
parseGetResult(result) {
|
|
171
|
+
if (!result.ids || result.ids.length === 0) return [];
|
|
172
|
+
const ids = result.ids;
|
|
173
|
+
const documents = result.documents ?? [];
|
|
174
|
+
const metadatas = result.metadatas ?? [];
|
|
175
|
+
return ids.map((id, i) => {
|
|
176
|
+
const meta = metadatas[i] ?? {};
|
|
177
|
+
const score = 1;
|
|
178
|
+
const {
|
|
179
|
+
title,
|
|
180
|
+
url,
|
|
181
|
+
source,
|
|
182
|
+
documentId,
|
|
183
|
+
chunkIndex,
|
|
184
|
+
...customMetadata
|
|
185
|
+
} = meta;
|
|
186
|
+
return {
|
|
187
|
+
id,
|
|
188
|
+
score,
|
|
189
|
+
text: documents[i] ?? "",
|
|
190
|
+
title: title ?? "Untitled",
|
|
191
|
+
url: url || void 0,
|
|
192
|
+
documentId: documentId ?? "",
|
|
193
|
+
metadata: Object.keys(customMetadata).length > 0 ? customMetadata : void 0
|
|
194
|
+
};
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
parseQueryResult(result) {
|
|
198
|
+
if (!result.ids?.[0]) return [];
|
|
199
|
+
const ids = result.ids[0];
|
|
200
|
+
const documents = result.documents?.[0] ?? [];
|
|
201
|
+
const metadatas = result.metadatas?.[0] ?? [];
|
|
202
|
+
const distances = result.distances?.[0] ?? [];
|
|
203
|
+
return ids.map((id, i) => {
|
|
204
|
+
const meta = metadatas[i] ?? {};
|
|
205
|
+
const distance = distances[i] ?? 0;
|
|
206
|
+
const score = 1 / (1 + distance);
|
|
207
|
+
const {
|
|
208
|
+
title,
|
|
209
|
+
url,
|
|
210
|
+
source,
|
|
211
|
+
documentId,
|
|
212
|
+
chunkIndex,
|
|
213
|
+
...customMetadata
|
|
214
|
+
} = meta;
|
|
215
|
+
return {
|
|
216
|
+
id,
|
|
217
|
+
score,
|
|
218
|
+
text: documents[i] ?? "",
|
|
219
|
+
title: title ?? "Untitled",
|
|
220
|
+
url: url || void 0,
|
|
221
|
+
documentId: documentId ?? "",
|
|
222
|
+
metadata: Object.keys(customMetadata).length > 0 ? customMetadata : void 0
|
|
223
|
+
};
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
// ── Connection test ────────────────────────────────────────────────────────
|
|
227
|
+
async testConnection(_dimensions) {
|
|
228
|
+
let client;
|
|
229
|
+
try {
|
|
230
|
+
client = await this.getClient();
|
|
231
|
+
} catch (err) {
|
|
232
|
+
throw new Error(`Failed to connect to Chroma: ${err.message}`);
|
|
233
|
+
}
|
|
234
|
+
try {
|
|
235
|
+
await client.heartbeat();
|
|
236
|
+
} catch (err) {
|
|
237
|
+
const msg = err.message ?? "";
|
|
238
|
+
if (msg.includes("401") || msg.includes("Unauthorized") || msg.includes("403") || msg.includes("Forbidden")) {
|
|
239
|
+
throw new Error(
|
|
240
|
+
"Invalid Chroma credentials. Please check your auth token or API key."
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
if (msg.includes("ECONNREFUSED") || msg.includes("fetch failed")) {
|
|
244
|
+
throw new Error(
|
|
245
|
+
"Could not reach the Chroma server. Is it running and accessible at the configured URL?"
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
throw new Error(`Failed to reach Chroma server: ${msg}`);
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
await client.listCollections();
|
|
252
|
+
} catch (err) {
|
|
253
|
+
throw new Error(
|
|
254
|
+
`Connected to Chroma but failed to access the database or tenant. Please verify your tenant and database names. Error: ${err.message}`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
function rrfMerge(denseHits, sparseHits, topK) {
|
|
260
|
+
const scores = /* @__PURE__ */ new Map();
|
|
261
|
+
const addList = (hits) => hits.forEach((hit, rank) => {
|
|
262
|
+
const contrib = 1 / (RRF_K + rank + 1);
|
|
263
|
+
const prev = scores.get(hit.id);
|
|
264
|
+
if (prev) {
|
|
265
|
+
prev.score += contrib;
|
|
266
|
+
} else {
|
|
267
|
+
scores.set(hit.id, { hit, score: contrib });
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
addList(denseHits);
|
|
271
|
+
addList(sparseHits);
|
|
272
|
+
return [...scores.values()].sort((a, b) => b.score - a.score).slice(0, topK).map(({ hit, score }) => ({ ...hit, score }));
|
|
273
|
+
}
|
|
274
|
+
export {
|
|
275
|
+
ChromaAdapter
|
|
276
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
__require
|
|
10
|
+
};
|