@bike4mind/cli 1.2.0 → 2.0.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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  //#region package.json
3
- var version = "1.2.0";
3
+ var version = "2.0.0";
4
4
  //#endregion
5
5
  export { version as t };
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { N as PROMPT_TEXT_MAX, _ as AGENT_QUEST_ID, h as requireApiUrl, m as parseApiUrl, n as logger, t as ConfigStore, v as AGENT_QUEST_MANIFEST, y as AGENT_QUEST_MCP_URI } from "./ConfigStore-qV7NrCgZ.mjs";
3
- import { t as ApiClient } from "./ApiClient-D0fQ2FT6.mjs";
2
+ import { R as PROMPT_TEXT_MAX, Y as generatedAudioResponseSchema, _ as requireApiUrl, b as AGENT_QUEST_MANIFEST, dt as ttsBase64ResponseSchema, ft as ttsRequestSchema, g as parseApiUrl, k as DEFAULT_TTS_PROVIDER, n as logger, pt as ttsResponseTooLargeSchema, t as ConfigStore, ut as supportedVoiceGenerationVendor, x as AGENT_QUEST_MCP_URI, y as AGENT_QUEST_ID } from "./ConfigStore-DIs-K9oD.mjs";
3
+ import { n as NotAuthenticatedError, r as isProviderKeyFailure, t as ApiClient } from "./ApiClient-BTI0-m0g.mjs";
4
4
  import { z as z$1 } from "zod";
5
5
  import { isAxiosError } from "axios";
6
6
  import { Writable } from "node:stream";
@@ -46,12 +46,27 @@ var B4mApiClient = class {
46
46
  async createNotebook(args) {
47
47
  return this.client.post("/api/sessions/create", {
48
48
  ...args.name ? { name: args.name } : {},
49
- ...args.projectId ? { projectId: args.projectId } : {}
49
+ ...args.projectId ? { projectId: args.projectId } : {},
50
+ ...args.dataLakeId ? { dataLakeId: args.dataLakeId } : {}
50
51
  });
51
52
  }
53
+ /**
54
+ * GET /api/v1/data-lakes is cursor-paginated (flat `limit`/`cursor` params,
55
+ * `{ data, next_cursor }` body), so `toList` does not apply.
56
+ */
57
+ async listDataLakes(args) {
58
+ const result = await this.client.get("/api/v1/data-lakes", { params: {
59
+ limit: args.limit,
60
+ ...args.cursor ? { cursor: args.cursor } : {}
61
+ } });
62
+ return {
63
+ data: result.data ?? [],
64
+ nextCursor: result.next_cursor ?? null
65
+ };
66
+ }
52
67
  async sendChat(args) {
53
68
  return this.client.post("/api/chat", {
54
- ...args.notebookId ? { sessionId: args.notebookId } : {},
69
+ ...args.notebookId ? { sessionId: args.notebookId } : { newConversation: true },
55
70
  message: args.message,
56
71
  ...args.model ? { model: args.model } : {},
57
72
  ...args.systemPrompt ? { systemPrompt: args.systemPrompt } : {},
@@ -81,36 +96,72 @@ var B4mApiClient = class {
81
96
  async getFile(fileId) {
82
97
  return this.client.get(`/api/files/${encodeURIComponent(fileId)}`);
83
98
  }
84
- /**
85
- * Generate a sound effect. Unlike the JSON routes, this one streams raw audio
86
- * bytes, so it goes through the axios instance directly to read the response
87
- * headers (content type + the persisted-FabFile side channel). On failure the
88
- * error body arrives as bytes; {@link decodeArrayBufferErrorBody} restores the
89
- * JSON shape so {@link mapApiError} can surface the server's message.
90
- */
99
+ /** Generate a sound effect; see {@link postGeneratedAudio} for the normalized response. */
91
100
  async generateSoundEffect(args) {
101
+ return this.postGeneratedAudio("/api/ai/sound-effects", {
102
+ provider: args.provider,
103
+ text: args.text,
104
+ ...args.durationSeconds !== void 0 ? { durationSeconds: args.durationSeconds } : {},
105
+ ...args.promptInfluence !== void 0 ? { promptInfluence: args.promptInfluence } : {},
106
+ ...args.format ? { format: args.format } : {}
107
+ }, generatedAudioResponseSchema);
108
+ }
109
+ async synthesizeSpeech(args) {
92
110
  try {
93
- const response = await this.client.getAxiosInstance().post("/api/ai/sound-effects", {
94
- provider: args.provider,
95
- text: args.text,
96
- ...args.durationSeconds !== void 0 ? { durationSeconds: args.durationSeconds } : {},
97
- ...args.promptInfluence !== void 0 ? { promptInfluence: args.promptInfluence } : {},
98
- ...args.format ? { format: args.format } : {}
99
- }, { responseType: "arraybuffer" });
100
- const headerString = (value) => typeof value === "string" ? value : void 0;
101
- const saved = String(response.headers["x-b4m-audio-saved"] ?? "") === "true";
102
111
  return {
103
- audio: Buffer.from(response.data),
104
- contentType: String(response.headers["content-type"] ?? "application/octet-stream"),
105
- saved,
106
- fabFileId: saved ? headerString(response.headers["x-b4m-audio-fab-file-id"]) : void 0,
107
- fileName: saved ? headerString(response.headers["x-b4m-audio-file-name"]) : void 0,
108
- fileUrl: saved ? headerString(response.headers["x-b4m-audio-file-url"]) : void 0
112
+ kind: "audio",
113
+ data: await this.postGeneratedAudio("/api/ai/tts", args, ttsBase64ResponseSchema)
109
114
  };
110
115
  } catch (error) {
111
- throw decodeArrayBufferErrorBody(error);
116
+ if (isAxiosError(error) && error.response?.status === 413) {
117
+ const oversized = ttsResponseTooLargeSchema.safeParse(error.response.data);
118
+ if (oversized.success && oversized.data.saved && oversized.data.fabFileId) {
119
+ const fallbackFrom = supportedVoiceGenerationVendor.safeParse(error.response.headers?.["x-b4m-tts-provider-fallback-from"]);
120
+ return {
121
+ kind: "saved-too-large",
122
+ data: {
123
+ ...oversized.data,
124
+ fabFileId: oversized.data.fabFileId
125
+ },
126
+ ...fallbackFrom.success ? { fallbackFrom: fallbackFrom.data } : {}
127
+ };
128
+ }
129
+ }
130
+ throw error;
112
131
  }
113
132
  }
133
+ /**
134
+ * POST a generated-audio request with `encoding: 'base64'` and normalize any
135
+ * server's answer into the JSON shape `schema` describes. Base64 (never binary)
136
+ * because an oversized result is a 303 in binary mode, and axios drops the
137
+ * X-B4M headers when following it. A server predating the `encoding` field
138
+ * ignores it and streams raw bytes with the save result in X-B4M-Audio-* headers;
139
+ * those are rebuilt into the inline variant. The request is arraybuffer-typed, so
140
+ * a failure body arrives as bytes; {@link decodeArrayBufferErrorBody} restores
141
+ * its JSON shape for {@link mapApiError}.
142
+ */
143
+ async postGeneratedAudio(path, body, schema) {
144
+ const response = await this.client.getAxiosInstance().post(path, {
145
+ ...body,
146
+ encoding: "base64"
147
+ }, { responseType: "arraybuffer" }).catch((error) => {
148
+ throw decodeArrayBufferErrorBody(error);
149
+ });
150
+ const contentType = String(response.headers["content-type"] ?? "application/octet-stream");
151
+ const bytes = Buffer.from(response.data);
152
+ if (/json/i.test(contentType)) return schema.parse(JSON.parse(bytes.toString("utf8")));
153
+ const headerString = (value) => typeof value === "string" ? value : void 0;
154
+ const saved = String(response.headers["x-b4m-audio-saved"] ?? "") === "true";
155
+ return schema.parse({
156
+ delivery: "inline",
157
+ audio: bytes.toString("base64"),
158
+ contentType,
159
+ saved,
160
+ fabFileId: saved ? headerString(response.headers["x-b4m-audio-fab-file-id"]) : void 0,
161
+ fileName: saved ? headerString(response.headers["x-b4m-audio-file-name"]) : void 0,
162
+ fileUrl: saved ? headerString(response.headers["x-b4m-audio-file-url"]) : void 0
163
+ });
164
+ }
114
165
  async listProjects(args) {
115
166
  const result = await this.client.get("/api/projects", { params: {
116
167
  ...args.search ? { search: args.search } : {},
@@ -152,10 +203,16 @@ var B4mApiClient = class {
152
203
  * gap that may not be the cause.
153
204
  */
154
205
  function mapApiError(error, baseURL, scope) {
206
+ if (error instanceof NotAuthenticatedError) return "not authenticated: no credential configured (set B4M_API_KEY or run `b4m login`)";
155
207
  if (isAxiosError(error)) {
156
208
  const status = error.response?.status;
157
- if (status === 401) return "authentication failed (run `b4m login` or set B4M_API_KEY)";
209
+ if (status === 401) {
210
+ const providerKeyMessage = providerKeyFailureMessage(error.response?.data);
211
+ if (providerKeyMessage) return providerKeyMessage;
212
+ return "authentication failed (run `b4m login` or set B4M_API_KEY)";
213
+ }
158
214
  if (status === 403) {
215
+ if ((error.response?.data)?.code === "FEATURE_DISABLED") return "feature disabled on this Bike4Mind instance (ask an admin to enable it)";
159
216
  const base = "API key forbidden: check the key's scopes and account access";
160
217
  return scope ? `${base} (recommended scope: ${scope})` : base;
161
218
  }
@@ -215,6 +272,10 @@ function extractServerMessage(data) {
215
272
  if (typeof record.message === "string") return record.message;
216
273
  }
217
274
  }
275
+ function providerKeyFailureMessage(data) {
276
+ if (!isProviderKeyFailure(data)) return void 0;
277
+ return `${extractServerMessage(data) ?? "the AI provider could not be used"} (configure or fix the provider API key in Bike4Mind; this is not a Bike4Mind login problem)`;
278
+ }
218
279
  //#endregion
219
280
  //#region src/mcp/tools.ts
220
281
  const TOOL_META = [
@@ -233,13 +294,13 @@ const TOOL_META = [
233
294
  {
234
295
  name: "create_notebook",
235
296
  title: "Create notebook",
236
- description: "Create a new notebook, optionally inside a project. Defaults the name to \"New Notebook\" when omitted.",
297
+ description: "Create a new notebook, optionally inside a project or grounded in a data lake (dataLakeId, see list_lakes). Defaults the name to \"New Notebook\" when omitted.",
237
298
  scope: "notebooks:write"
238
299
  },
239
300
  {
240
301
  name: "send_message",
241
302
  title: "Send message",
242
- description: "Send a chat message and wait for the assistant reply.",
303
+ description: "Send a chat message and wait for the assistant reply; returns the cited sources (citables) the answer was grounded in.",
243
304
  scope: "ai:chat"
244
305
  },
245
306
  {
@@ -248,6 +309,12 @@ const TOOL_META = [
248
309
  description: "Semantic search across the caller's notebooks.",
249
310
  scope: "notebooks:read"
250
311
  },
312
+ {
313
+ name: "list_lakes",
314
+ title: "List data lakes",
315
+ description: "List the data lakes the caller can reach. Pass a lake's id as dataLakeId to create_notebook to ground a notebook in it.",
316
+ scope: "datalake:read"
317
+ },
251
318
  {
252
319
  name: "list_files",
253
320
  title: "List files",
@@ -265,6 +332,12 @@ const TOOL_META = [
265
332
  title: "Generate sound effect",
266
333
  description: "Generate a sound effect from a text description. Returns the saved audio file (with a signed download URL) when the caller keeps generated audio, otherwise the audio inline.",
267
334
  scope: "ai:generate"
335
+ },
336
+ {
337
+ name: "text_to_speech",
338
+ title: "Text to speech",
339
+ description: "Synthesize speech from text. Returns a saved audio file with a signed download URL when available, otherwise audio inline.",
340
+ scope: "ai:generate"
268
341
  }
269
342
  ];
270
343
  TOOL_META.map((t) => t.name);
@@ -276,11 +349,12 @@ const listNotebooksShape = {
276
349
  const getNotebookShape = { notebookId: z$1.string().describe("The notebook (session) id") };
277
350
  const createNotebookShape = {
278
351
  name: z$1.string().optional().describe("Name for the new notebook"),
279
- projectId: z$1.string().optional().describe("Project to create the notebook in")
352
+ projectId: z$1.string().optional().describe("Project to create the notebook in"),
353
+ dataLakeId: z$1.string().optional().describe("Data lake id or slug to ground the notebook in (see list_lakes); seeds the lake's retrieval defaults")
280
354
  };
281
355
  const sendMessageShape = {
282
356
  message: z$1.string().describe("The message to send"),
283
- notebookId: z$1.string().optional().describe("Notebook to send to; defaults to the most recent"),
357
+ notebookId: z$1.string().optional().describe("Notebook to send to; omit to start a new notebook (its id is returned as notebookId, pass it back to continue the thread)"),
284
358
  model: z$1.string().optional().describe("Model id to use; defaults to the instance default"),
285
359
  systemPrompt: z$1.string().max(PROMPT_TEXT_MAX).optional().describe("Caller-supplied system-prompt text for this message only; refines but never overrides other guidance")
286
360
  };
@@ -289,6 +363,10 @@ const searchKnowledgeBaseShape = {
289
363
  limit: z$1.number().int().min(1).max(100).default(10).describe("Maximum results to return"),
290
364
  minSimilarity: z$1.number().min(0).max(1).optional().describe("Minimum cosine similarity threshold")
291
365
  };
366
+ const listLakesShape = {
367
+ limit: z$1.number().int().min(1).max(100).default(25).describe("Maximum lakes to return"),
368
+ cursor: z$1.string().optional().describe("Pass back nextCursor from the previous page")
369
+ };
292
370
  const listFilesShape = {
293
371
  search: z$1.string().optional().describe("Filter files by name/content"),
294
372
  limit: z$1.number().int().min(1).max(100).default(25).describe("Maximum files to return"),
@@ -302,6 +380,12 @@ const generateSoundEffectShape = {
302
380
  promptInfluence: z$1.number().min(0).max(1).optional().describe("How strictly to follow the prompt (0 = loose, 1 = strict)"),
303
381
  format: z$1.string().optional().describe("Provider output encoding token, e.g. mp3_44100_128")
304
382
  };
383
+ const textToSpeechShape = {
384
+ ...ttsRequestSchema.omit({ encoding: true }).shape,
385
+ text: ttsRequestSchema.shape.text.describe("Text to speak"),
386
+ provider: ttsRequestSchema.shape.provider.describe(`Speech provider; defaults to ${DEFAULT_TTS_PROVIDER}`),
387
+ preview: ttsRequestSchema.shape.preview.describe("Skip saving a copy to the file browser")
388
+ };
305
389
  function notebookSummary(n) {
306
390
  return {
307
391
  id: n.id,
@@ -311,6 +395,17 @@ function notebookSummary(n) {
311
395
  updatedAt: n.updatedAt ?? n.lastUpdated
312
396
  };
313
397
  }
398
+ function lakeSummary(l) {
399
+ return {
400
+ id: l.id,
401
+ name: l.name,
402
+ slug: l.slug,
403
+ description: l.description ?? void 0,
404
+ builtIn: l.built_in,
405
+ status: l.status,
406
+ fileCount: l.file_count
407
+ };
408
+ }
314
409
  async function listNotebooks(client, args) {
315
410
  const { data, hasMore } = await client.listNotebooks(args);
316
411
  return {
@@ -330,23 +425,38 @@ async function createNotebook(client, args) {
330
425
  async function sendMessage(client, args) {
331
426
  const res = await client.sendChat(args);
332
427
  const questId = res.id;
333
- let notebookId = args.notebookId;
334
- if (!notebookId) try {
335
- notebookId = (await client.getQuest(questId)).sessionId;
428
+ let quest;
429
+ try {
430
+ quest = await client.getQuest(questId);
336
431
  } catch {
337
- notebookId = void 0;
432
+ quest = void 0;
338
433
  }
339
- const reply = res.responses && res.responses.length > 0 ? res.responses.join("\n\n") : res.response ?? "";
434
+ const notebookId = args.notebookId ?? res.sessionId ?? quest?.sessionId;
435
+ const citables = quest ? (quest.promptMeta?.citables ?? []).map((c) => ({
436
+ id: c.id,
437
+ type: c.type,
438
+ title: c.title,
439
+ url: c.url,
440
+ description: c.description
441
+ })) : void 0;
340
442
  return {
341
443
  notebookId,
342
444
  questId,
343
- reply,
344
- model: res.model
445
+ reply: res.responses && res.responses.length > 0 ? res.responses.join("\n\n") : res.response ?? "",
446
+ model: res.model,
447
+ citables
345
448
  };
346
449
  }
347
450
  async function searchKnowledgeBase(client, args) {
348
451
  return { results: await client.searchKnowledgeBase(args) };
349
452
  }
453
+ async function listLakes(client, args) {
454
+ const { data, nextCursor } = await client.listDataLakes(args);
455
+ return {
456
+ lakes: data.map(lakeSummary),
457
+ nextCursor
458
+ };
459
+ }
350
460
  async function listFiles(client, args) {
351
461
  const { data, hasMore } = await client.listFiles(args);
352
462
  return {
@@ -358,26 +468,7 @@ async function getFile(client, args) {
358
468
  return client.getFile(args.fileId);
359
469
  }
360
470
  async function generateSoundEffect(client, args) {
361
- const { audio, contentType, saved, fabFileId, fileName, fileUrl } = await client.generateSoundEffect(args);
362
- const base = {
363
- provider: args.provider,
364
- contentType,
365
- byteLength: audio.length
366
- };
367
- if (saved && fabFileId && fileUrl) return {
368
- ...base,
369
- saved: true,
370
- file: {
371
- id: fabFileId,
372
- fileName,
373
- fileUrl
374
- }
375
- };
376
- return {
377
- ...base,
378
- saved: false,
379
- audioBase64: audio.toString("base64")
380
- };
471
+ return generatedAudioResult(await client.generateSoundEffect(args), { provider: args.provider });
381
472
  }
382
473
  function toResult(value) {
383
474
  const structuredContent = value && typeof value === "object" && !Array.isArray(value) ? value : { result: value };
@@ -399,21 +490,57 @@ function errorResult(message) {
399
490
  };
400
491
  }
401
492
  /**
402
- * Render a {@link SoundEffectOutcome} as an MCP result. A persisted file becomes
403
- * a JSON metadata result (carrying the signed URL), exactly like get_file. When
404
- * the audio was not persisted, it is returned inline as an `audio` content block
405
- * so the bytes are not lost; the base64 is kept out of structuredContent to avoid
406
- * duplicating a potentially large payload.
493
+ * Render a generated-audio response as an MCP result, shared by every audio tool.
494
+ * Oversized audio is reported by its signed URL; a saved copy with a usable URL is
495
+ * reported as a file (like get_file) with no inline bytes. Otherwise the audio rides
496
+ * inline as an `audio` block so a billed result is never lost, with any saved copy
497
+ * still named by id and a skipped save explained. The route forwards the signed URL
498
+ * it minted at upload, so no getFile re-fetch is needed (that fails closed until
499
+ * the async moderation scan runs).
407
500
  */
408
- function soundEffectResult(outcome) {
409
- if (outcome.saved) return toResult({
501
+ function generatedAudioResult(response, endpointMetadata) {
502
+ const metadata = {
503
+ ...endpointMetadata,
504
+ contentType: response.contentType
505
+ };
506
+ const saveSkippedReason = response.saveSkippedReason ? { saveSkippedReason: response.saveSkippedReason } : {};
507
+ const file = response.fabFileId ? {
508
+ id: response.fabFileId,
509
+ ...response.fileName ? { fileName: response.fileName } : {},
510
+ ...response.fileUrl ? { fileUrl: response.fileUrl } : {}
511
+ } : void 0;
512
+ if (response.delivery === "url") return toResult({
513
+ ...metadata,
514
+ byteLength: response.bytes,
515
+ url: response.url,
516
+ saved: response.saved === true,
517
+ ...response.saved && file ? { file } : {},
518
+ ...saveSkippedReason
519
+ });
520
+ const byteLength = Buffer.from(response.audio, "base64").length;
521
+ if (response.saved && file && file.fileUrl) return toResult({
522
+ ...metadata,
523
+ byteLength,
410
524
  saved: true,
411
- provider: outcome.provider,
412
- contentType: outcome.contentType,
413
- byteLength: outcome.byteLength,
414
- file: outcome.file
525
+ file
415
526
  });
416
- const { audioBase64, ...meta } = outcome;
527
+ return inlineAudioResult(response.saved && file ? {
528
+ ...metadata,
529
+ byteLength,
530
+ saved: true,
531
+ file
532
+ } : {
533
+ ...metadata,
534
+ byteLength,
535
+ saved: false,
536
+ ...saveSkippedReason
537
+ }, response.audio, response.contentType);
538
+ }
539
+ /**
540
+ * Metadata as JSON text plus the audio as an MCP `audio` block. The base64 stays
541
+ * out of structuredContent so a potentially large payload is not duplicated.
542
+ */
543
+ function inlineAudioResult(meta, audioBase64, mimeType) {
417
544
  return {
418
545
  content: [{
419
546
  type: "text",
@@ -421,11 +548,32 @@ function soundEffectResult(outcome) {
421
548
  }, {
422
549
  type: "audio",
423
550
  data: audioBase64,
424
- mimeType: outcome.contentType
551
+ mimeType
425
552
  }],
426
553
  structuredContent: meta
427
554
  };
428
555
  }
556
+ async function textToSpeech(client, args) {
557
+ const response = await client.synthesizeSpeech(args);
558
+ if (response.kind === "saved-too-large") {
559
+ const { provider, fabFileId, fileUrl } = response.data;
560
+ return toResult({
561
+ saved: true,
562
+ provider,
563
+ ...response.fallbackFrom ? { fallbackFrom: response.fallbackFrom } : {},
564
+ file: {
565
+ id: fabFileId,
566
+ ...fileUrl ? { fileUrl } : {}
567
+ }
568
+ });
569
+ }
570
+ const result = response.data;
571
+ return generatedAudioResult(result, {
572
+ provider: result.provider ?? args.provider ?? "openai",
573
+ ...result.fallbackFrom ? { fallbackFrom: result.fallbackFrom } : {},
574
+ format: result.format
575
+ });
576
+ }
429
577
  /**
430
578
  * Register the Bike4Mind MCP tools on `server`. Each handler is wrapped so an
431
579
  * API failure becomes a structured `isError` result carrying a friendly message
@@ -466,6 +614,11 @@ function registerTools(server, client) {
466
614
  description: meta("search_knowledge_base").description,
467
615
  inputSchema: searchKnowledgeBaseShape
468
616
  }, (args) => run("notebooks:read", () => searchKnowledgeBase(client, args)));
617
+ server.registerTool("list_lakes", {
618
+ title: meta("list_lakes").title,
619
+ description: meta("list_lakes").description,
620
+ inputSchema: listLakesShape
621
+ }, (args) => run("datalake:read", () => listLakes(client, args)));
469
622
  server.registerTool("list_files", {
470
623
  title: meta("list_files").title,
471
624
  description: meta("list_files").description,
@@ -482,7 +635,18 @@ function registerTools(server, client) {
482
635
  inputSchema: generateSoundEffectShape
483
636
  }, async (args) => {
484
637
  try {
485
- return soundEffectResult(await generateSoundEffect(client, args));
638
+ return await generateSoundEffect(client, args);
639
+ } catch (err) {
640
+ return errorResult(mapApiError(err, baseURL, "ai:generate"));
641
+ }
642
+ });
643
+ server.registerTool("text_to_speech", {
644
+ title: meta("text_to_speech").title,
645
+ description: meta("text_to_speech").description,
646
+ inputSchema: textToSpeechShape
647
+ }, async (args) => {
648
+ try {
649
+ return await textToSpeech(client, args);
486
650
  } catch (err) {
487
651
  return errorResult(mapApiError(err, baseURL, "ai:generate"));
488
652
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bike4mind/cli",
3
- "version": "1.2.0",
3
+ "version": "2.0.0",
4
4
  "mcpName": "io.github.bike4mind/bike4mind",
5
5
  "type": "module",
6
6
  "description": "Interactive CLI tool for Bike4Mind with ReAct agents",
@@ -55,7 +55,7 @@
55
55
  "@opensearch-project/opensearch": "2.11.0",
56
56
  "@smithy/node-http-handler": "^4.9.3",
57
57
  "async-mutex": "^0.5.0",
58
- "axios": "1.18.0",
58
+ "axios": "1.20.0",
59
59
  "bcryptjs": "^3.0.3",
60
60
  "better-sqlite3": "^13.0.3",
61
61
  "cheerio": "1.2.0",
@@ -124,16 +124,16 @@
124
124
  "tsdown": "^0.22.3",
125
125
  "tsx": "^4.23.0",
126
126
  "typescript": "^5.9.3",
127
- "vitest": "^4.1.10",
128
- "@bike4mind/agents": "1.0.14",
129
- "@bike4mind/common": "12.0.0",
127
+ "vitest": "^4.1.11",
128
+ "@bike4mind/agents": "1.1.1",
129
+ "@bike4mind/common": "13.0.0",
130
130
  "@bike4mind/hearth": "0.3.0",
131
- "@bike4mind/llm-adapters": "0.15.8",
132
- "@bike4mind/mcp": "2.0.14",
133
- "@bike4mind/services": "14.0.0",
134
- "@bike4mind/observability": "0.2.2",
135
- "@bike4mind/utils": "6.3.0",
136
- "@bike4mind/fab-pipeline": "1.5.0"
131
+ "@bike4mind/fab-pipeline": "1.5.3",
132
+ "@bike4mind/llm-adapters": "0.16.1",
133
+ "@bike4mind/mcp": "2.0.17",
134
+ "@bike4mind/observability": "0.2.3",
135
+ "@bike4mind/utils": "6.5.1",
136
+ "@bike4mind/services": "15.0.0"
137
137
  },
138
138
  "optionalDependencies": {
139
139
  "@vscode/ripgrep": "^1.18.0"