@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 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
+ };