@alfe.ai/openclaw-database 0.0.4 → 0.0.6

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/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ const require_plugin = require("./plugin2.cjs");
2
+ module.exports = require_plugin.plugin;
@@ -0,0 +1,2 @@
1
+ import plugin from "./plugin.cjs";
2
+ export { plugin as default };
@@ -0,0 +1,2 @@
1
+ const require_plugin = require("./plugin2.cjs");
2
+ module.exports = require_plugin.plugin;
@@ -0,0 +1,19 @@
1
+ //#region src/plugin.d.ts
2
+ interface PluginApi {
3
+ logger: {
4
+ info: (...args: unknown[]) => void;
5
+ warn: (...args: unknown[]) => void;
6
+ error: (...args: unknown[]) => void;
7
+ debug: (...args: unknown[]) => void;
8
+ };
9
+ registerTool(tool: unknown): void;
10
+ }
11
+ declare const plugin: {
12
+ id: string;
13
+ name: string;
14
+ version: string;
15
+ activate(api: PluginApi): void;
16
+ deactivate(api: PluginApi): Promise<void>;
17
+ };
18
+ //#endregion
19
+ export { plugin as default };
@@ -0,0 +1,529 @@
1
+ let mongodb = require("mongodb");
2
+ let _alfe_ai_config = require("@alfe.ai/config");
3
+ //#region src/tools.ts
4
+ const DEFAULT_DB = "org_default";
5
+ function str(required = true) {
6
+ return {
7
+ type: "string",
8
+ ...required ? {} : { default: "" }
9
+ };
10
+ }
11
+ function optStr() {
12
+ return {
13
+ type: "string",
14
+ default: ""
15
+ };
16
+ }
17
+ function registerTools(api, _client, _databases, audit, lazyInit) {
18
+ async function db(name) {
19
+ const { mongoClient, databases } = lazyInit ? await lazyInit() : {
20
+ mongoClient: _client ?? (() => {
21
+ throw new Error("MongoDB client not initialized");
22
+ })(),
23
+ databases: _databases
24
+ };
25
+ const dbName = name ?? DEFAULT_DB;
26
+ if (!databases.includes(dbName)) throw new Error(`Access denied: database "${dbName}" is not in the allowed list`);
27
+ return mongoClient.db(dbName);
28
+ }
29
+ api.registerTool({
30
+ name: "db_find",
31
+ description: "Find documents matching a filter",
32
+ label: "db_find",
33
+ parameters: {
34
+ type: "object",
35
+ properties: {
36
+ database: optStr(),
37
+ collection: str(),
38
+ filter: {
39
+ type: "object",
40
+ default: {}
41
+ },
42
+ sort: {
43
+ type: "object",
44
+ default: {}
45
+ },
46
+ limit: {
47
+ type: "number",
48
+ default: 20
49
+ },
50
+ skip: {
51
+ type: "number",
52
+ default: 0
53
+ },
54
+ projection: {
55
+ type: "object",
56
+ default: {}
57
+ }
58
+ },
59
+ required: ["collection"]
60
+ },
61
+ execute: async (_id, p) => {
62
+ const docs = await (await db(p.database)).collection(p.collection).find(p.filter).sort(p.sort).project(p.projection).skip(p.skip || 0).limit(p.limit || 20).toArray();
63
+ return {
64
+ documents: docs,
65
+ count: docs.length
66
+ };
67
+ }
68
+ });
69
+ api.registerTool({
70
+ name: "db_find_one",
71
+ description: "Find a single document matching a filter",
72
+ label: "db_find_one",
73
+ parameters: {
74
+ type: "object",
75
+ properties: {
76
+ database: optStr(),
77
+ collection: str(),
78
+ filter: {
79
+ type: "object",
80
+ default: {}
81
+ },
82
+ projection: {
83
+ type: "object",
84
+ default: {}
85
+ }
86
+ },
87
+ required: ["collection"]
88
+ },
89
+ execute: async (_id, p) => {
90
+ return await (await db(p.database)).collection(p.collection).findOne(p.filter, { projection: p.projection }) ?? { _not_found: true };
91
+ }
92
+ });
93
+ api.registerTool({
94
+ name: "db_insert",
95
+ description: "Insert one or more documents",
96
+ label: "db_insert",
97
+ parameters: {
98
+ type: "object",
99
+ properties: {
100
+ database: optStr(),
101
+ collection: str(),
102
+ documents: {
103
+ type: "array",
104
+ items: { type: "object" }
105
+ }
106
+ },
107
+ required: ["collection", "documents"]
108
+ },
109
+ execute: async (_id, p) => {
110
+ const docs = p.documents;
111
+ const result = await (await db(p.database)).collection(p.collection).insertMany(docs);
112
+ audit({
113
+ database: p.database || DEFAULT_DB,
114
+ collection: p.collection,
115
+ operation: "insert",
116
+ summary: `Inserted ${String(docs.length)} document(s)`
117
+ });
118
+ return {
119
+ insertedCount: result.insertedCount,
120
+ insertedIds: Object.values(result.insertedIds).map(String)
121
+ };
122
+ }
123
+ });
124
+ api.registerTool({
125
+ name: "db_update",
126
+ description: "Update documents matching a filter",
127
+ label: "db_update",
128
+ parameters: {
129
+ type: "object",
130
+ properties: {
131
+ database: optStr(),
132
+ collection: str(),
133
+ filter: { type: "object" },
134
+ update: { type: "object" },
135
+ upsert: {
136
+ type: "boolean",
137
+ default: false
138
+ }
139
+ },
140
+ required: [
141
+ "collection",
142
+ "filter",
143
+ "update"
144
+ ]
145
+ },
146
+ execute: async (_id, p) => {
147
+ const result = await (await db(p.database)).collection(p.collection).updateMany(p.filter, p.update, { upsert: p.upsert || false });
148
+ audit({
149
+ database: p.database || DEFAULT_DB,
150
+ collection: p.collection,
151
+ operation: "update",
152
+ summary: `Modified ${String(result.modifiedCount)}`
153
+ });
154
+ return {
155
+ matchedCount: result.matchedCount,
156
+ modifiedCount: result.modifiedCount,
157
+ upsertedId: result.upsertedId ? String(result.upsertedId) : null
158
+ };
159
+ }
160
+ });
161
+ api.registerTool({
162
+ name: "db_delete",
163
+ description: "Delete documents matching a filter",
164
+ label: "db_delete",
165
+ parameters: {
166
+ type: "object",
167
+ properties: {
168
+ database: optStr(),
169
+ collection: str(),
170
+ filter: { type: "object" }
171
+ },
172
+ required: ["collection", "filter"]
173
+ },
174
+ execute: async (_id, p) => {
175
+ const result = await (await db(p.database)).collection(p.collection).deleteMany(p.filter);
176
+ audit({
177
+ database: p.database || DEFAULT_DB,
178
+ collection: p.collection,
179
+ operation: "delete",
180
+ summary: `Deleted ${String(result.deletedCount)}`
181
+ });
182
+ return { deletedCount: result.deletedCount };
183
+ }
184
+ });
185
+ api.registerTool({
186
+ name: "db_count",
187
+ description: "Count documents matching a filter",
188
+ label: "db_count",
189
+ parameters: {
190
+ type: "object",
191
+ properties: {
192
+ database: optStr(),
193
+ collection: str(),
194
+ filter: {
195
+ type: "object",
196
+ default: {}
197
+ }
198
+ },
199
+ required: ["collection"]
200
+ },
201
+ execute: async (_id, p) => {
202
+ return { count: await (await db(p.database)).collection(p.collection).countDocuments(p.filter) };
203
+ }
204
+ });
205
+ api.registerTool({
206
+ name: "db_aggregate",
207
+ description: "Run an aggregation pipeline",
208
+ label: "db_aggregate",
209
+ parameters: {
210
+ type: "object",
211
+ properties: {
212
+ database: optStr(),
213
+ collection: str(),
214
+ pipeline: {
215
+ type: "array",
216
+ items: { type: "object" }
217
+ }
218
+ },
219
+ required: ["collection", "pipeline"]
220
+ },
221
+ execute: async (_id, p) => {
222
+ const results = await (await db(p.database)).collection(p.collection).aggregate(p.pipeline).toArray();
223
+ return {
224
+ results,
225
+ count: results.length
226
+ };
227
+ }
228
+ });
229
+ api.registerTool({
230
+ name: "db_list_databases",
231
+ description: "List databases the agent can access",
232
+ label: "db_list_databases",
233
+ parameters: {
234
+ type: "object",
235
+ properties: {}
236
+ },
237
+ execute: async () => {
238
+ const { databases } = lazyInit ? await lazyInit() : { databases: _databases };
239
+ return { databases };
240
+ }
241
+ });
242
+ api.registerTool({
243
+ name: "db_list_collections",
244
+ description: "List collections in a database",
245
+ label: "db_list_collections",
246
+ parameters: {
247
+ type: "object",
248
+ properties: { database: optStr() }
249
+ },
250
+ execute: async (_id, p) => {
251
+ return { collections: (await (await db(p.database)).listCollections().toArray()).map((c) => ({
252
+ name: c.name,
253
+ type: c.type
254
+ })) };
255
+ }
256
+ });
257
+ api.registerTool({
258
+ name: "db_create_collection",
259
+ description: "Create a new collection",
260
+ label: "db_create_collection",
261
+ parameters: {
262
+ type: "object",
263
+ properties: {
264
+ database: optStr(),
265
+ collection: str()
266
+ },
267
+ required: ["collection"]
268
+ },
269
+ execute: async (_id, p) => {
270
+ await (await db(p.database)).createCollection(p.collection);
271
+ audit({
272
+ database: p.database || DEFAULT_DB,
273
+ collection: p.collection,
274
+ operation: "create_collection"
275
+ });
276
+ return { success: true };
277
+ }
278
+ });
279
+ api.registerTool({
280
+ name: "db_drop_collection",
281
+ description: "Drop a collection",
282
+ label: "db_drop_collection",
283
+ parameters: {
284
+ type: "object",
285
+ properties: {
286
+ database: optStr(),
287
+ collection: str()
288
+ },
289
+ required: ["collection"]
290
+ },
291
+ execute: async (_id, p) => {
292
+ await (await db(p.database)).dropCollection(p.collection);
293
+ audit({
294
+ database: p.database || DEFAULT_DB,
295
+ collection: p.collection,
296
+ operation: "drop_collection"
297
+ });
298
+ return { success: true };
299
+ }
300
+ });
301
+ api.registerTool({
302
+ name: "db_collection_schema",
303
+ description: "Inspect a collection's schema by sampling documents",
304
+ label: "db_collection_schema",
305
+ parameters: {
306
+ type: "object",
307
+ properties: {
308
+ database: optStr(),
309
+ collection: str()
310
+ },
311
+ required: ["collection"]
312
+ },
313
+ execute: async (_id, p) => {
314
+ const sample = await (await db(p.database)).collection(p.collection).find().limit(100).toArray();
315
+ const fieldTypes = /* @__PURE__ */ new Map();
316
+ for (const doc of sample) for (const [key, value] of Object.entries(doc)) {
317
+ const types = fieldTypes.get(key) ?? /* @__PURE__ */ new Set();
318
+ types.add(value === null ? "null" : typeof value);
319
+ fieldTypes.set(key, types);
320
+ }
321
+ return {
322
+ sampleSize: sample.length,
323
+ fields: [...fieldTypes.entries()].map(([name, types]) => ({
324
+ name,
325
+ types: [...types]
326
+ }))
327
+ };
328
+ }
329
+ });
330
+ api.registerTool({
331
+ name: "db_collection_stats",
332
+ description: "Get collection doc count, size, and indexes",
333
+ label: "db_collection_stats",
334
+ parameters: {
335
+ type: "object",
336
+ properties: {
337
+ database: optStr(),
338
+ collection: str()
339
+ },
340
+ required: ["collection"]
341
+ },
342
+ execute: async (_id, p) => {
343
+ const coll = (await db(p.database)).collection(p.collection);
344
+ const count = await coll.countDocuments();
345
+ const indexes = await coll.indexes();
346
+ return {
347
+ count,
348
+ indexCount: indexes.length,
349
+ indexes: indexes.map((i) => ({
350
+ name: i.name,
351
+ key: i.key
352
+ }))
353
+ };
354
+ }
355
+ });
356
+ api.registerTool({
357
+ name: "db_create_index",
358
+ description: "Create an index on a collection",
359
+ label: "db_create_index",
360
+ parameters: {
361
+ type: "object",
362
+ properties: {
363
+ database: optStr(),
364
+ collection: str(),
365
+ keys: { type: "object" },
366
+ options: {
367
+ type: "object",
368
+ default: {}
369
+ }
370
+ },
371
+ required: ["collection", "keys"]
372
+ },
373
+ execute: async (_id, p) => {
374
+ const indexName = await (await db(p.database)).collection(p.collection).createIndex(p.keys, p.options);
375
+ audit({
376
+ database: p.database || DEFAULT_DB,
377
+ collection: p.collection,
378
+ operation: "create_index",
379
+ summary: indexName
380
+ });
381
+ return { indexName };
382
+ }
383
+ });
384
+ api.registerTool({
385
+ name: "db_list_indexes",
386
+ description: "List indexes on a collection",
387
+ label: "db_list_indexes",
388
+ parameters: {
389
+ type: "object",
390
+ properties: {
391
+ database: optStr(),
392
+ collection: str()
393
+ },
394
+ required: ["collection"]
395
+ },
396
+ execute: async (_id, p) => {
397
+ return { indexes: (await (await db(p.database)).collection(p.collection).indexes()).map((i) => ({
398
+ name: i.name,
399
+ key: i.key,
400
+ unique: i.unique
401
+ })) };
402
+ }
403
+ });
404
+ api.registerTool({
405
+ name: "db_drop_index",
406
+ description: "Drop an index by name",
407
+ label: "db_drop_index",
408
+ parameters: {
409
+ type: "object",
410
+ properties: {
411
+ database: optStr(),
412
+ collection: str(),
413
+ indexName: str()
414
+ },
415
+ required: ["collection", "indexName"]
416
+ },
417
+ execute: async (_id, p) => {
418
+ await (await db(p.database)).collection(p.collection).dropIndex(p.indexName);
419
+ audit({
420
+ database: p.database || DEFAULT_DB,
421
+ collection: p.collection,
422
+ operation: "drop_index",
423
+ summary: p.indexName
424
+ });
425
+ return { success: true };
426
+ }
427
+ });
428
+ }
429
+ //#endregion
430
+ //#region src/plugin.ts
431
+ let mongoClient = null;
432
+ let initPromise = null;
433
+ async function fetchCredentials(apiUrl, apiKey) {
434
+ const res = await fetch(`${apiUrl}/agents/database/register`, {
435
+ method: "POST",
436
+ headers: {
437
+ "Content-Type": "application/json",
438
+ Authorization: `Bearer ${apiKey}`
439
+ }
440
+ });
441
+ if (!res.ok) throw new Error(`Failed to register database credentials: ${String(res.status)} ${res.statusText}`);
442
+ return (await res.json()).data;
443
+ }
444
+ /**
445
+ * Lazy initializer — fetches credentials and connects to MongoDB on first use.
446
+ * Returns a shared promise so concurrent calls don't duplicate work.
447
+ */
448
+ function ensureInitialized(apiUrl, apiKey) {
449
+ if (initPromise) return initPromise;
450
+ initPromise = (async () => {
451
+ const credentials = await fetchCredentials(apiUrl, apiKey);
452
+ if (!credentials.connectionString) throw new Error("No connection string returned — cluster may still be provisioning");
453
+ const url = new URL(credentials.connectionString);
454
+ url.username = credentials.username;
455
+ url.password = credentials.password;
456
+ const client = new mongodb.MongoClient(url.toString(), {
457
+ maxPoolSize: 3,
458
+ minPoolSize: 1,
459
+ serverSelectionTimeoutMS: 5e3
460
+ });
461
+ await client.connect();
462
+ mongoClient = client;
463
+ return {
464
+ mongoClient: client,
465
+ databases: credentials.databases,
466
+ apiUrl,
467
+ apiKey
468
+ };
469
+ })();
470
+ initPromise.catch(() => {
471
+ initPromise = null;
472
+ });
473
+ return initPromise;
474
+ }
475
+ function reportAudit(apiUrl, apiKey, entry) {
476
+ fetch(`${apiUrl}/agents/database/audit`, {
477
+ method: "POST",
478
+ headers: {
479
+ "Content-Type": "application/json",
480
+ Authorization: `Bearer ${apiKey}`
481
+ },
482
+ body: JSON.stringify(entry)
483
+ }).catch(() => {});
484
+ }
485
+ const plugin = {
486
+ id: "@alfe.ai/openclaw-database",
487
+ name: "Database",
488
+ version: "0.0.1",
489
+ activate(api) {
490
+ const log = api.logger;
491
+ if (!("runtime" in api)) {
492
+ log.debug("Management command context — skipping database init");
493
+ return;
494
+ }
495
+ log.info("Database plugin activating...");
496
+ let apiUrl;
497
+ let apiKey;
498
+ try {
499
+ const config = (0, _alfe_ai_config.resolveConfig)();
500
+ apiUrl = config.apiUrl;
501
+ apiKey = config.apiKey;
502
+ } catch (err) {
503
+ log.error(`Database plugin: failed to resolve config — ${err instanceof Error ? err.message : String(err)}`);
504
+ return;
505
+ }
506
+ ensureInitialized(apiUrl, apiKey).then(({ databases }) => {
507
+ log.info(`Database plugin connected — ${String(databases.length)} databases available`);
508
+ }).catch((err) => {
509
+ log.warn(`Database plugin: background init failed (will retry on first tool use) — ${err instanceof Error ? err.message : String(err)}`);
510
+ });
511
+ registerTools(api, null, [], (entry) => {
512
+ reportAudit(apiUrl, apiKey, entry);
513
+ }, () => ensureInitialized(apiUrl, apiKey));
514
+ },
515
+ async deactivate(api) {
516
+ api.logger.info("Database plugin deactivating...");
517
+ if (mongoClient) {
518
+ await mongoClient.close();
519
+ mongoClient = null;
520
+ }
521
+ }
522
+ };
523
+ //#endregion
524
+ Object.defineProperty(exports, "plugin", {
525
+ enumerable: true,
526
+ get: function() {
527
+ return plugin;
528
+ }
529
+ });
package/dist/plugin2.js CHANGED
@@ -488,6 +488,10 @@ const plugin = {
488
488
  version: "0.0.1",
489
489
  activate(api) {
490
490
  const log = api.logger;
491
+ if (!("runtime" in api)) {
492
+ log.debug("Management command context — skipping database init");
493
+ return;
494
+ }
491
495
  log.info("Database plugin activating...");
492
496
  let apiUrl;
493
497
  let apiKey;
package/package.json CHANGED
@@ -1,18 +1,20 @@
1
1
  {
2
2
  "name": "@alfe.ai/openclaw-database",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "OpenClaw database plugin — MongoDB access for agents via MCP tools",
5
5
  "type": "module",
6
6
  "main": "./dist/plugin.js",
7
7
  "types": "./dist/index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
- "import": "./dist/index.js",
11
- "types": "./dist/index.d.ts"
10
+ "types": "./dist/index.d.ts",
11
+ "require": "./dist/index.cjs",
12
+ "import": "./dist/index.js"
12
13
  },
13
14
  "./plugin": {
14
- "import": "./dist/plugin.js",
15
- "types": "./dist/plugin.d.ts"
15
+ "types": "./dist/plugin.d.ts",
16
+ "require": "./dist/plugin.cjs",
17
+ "import": "./dist/plugin.js"
16
18
  }
17
19
  },
18
20
  "openclaw": {
@@ -26,7 +28,7 @@
26
28
  ],
27
29
  "dependencies": {
28
30
  "mongodb": "^6.12.0",
29
- "@alfe.ai/config": "0.0.6"
31
+ "@alfe.ai/config": "0.0.7"
30
32
  },
31
33
  "license": "UNLICENSED",
32
34
  "scripts": {