@bike4mind/cli 0.2.25-cli-file-read-offset.18530 → 0.2.25-cli-file-read-offset.18541

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 CHANGED
@@ -183,6 +183,7 @@ export SERPER_API_KEY="your-key-here"
183
183
  - ✅ `current_datetime` - No API key needed
184
184
  - ✅ `prompt_enhancement` - No API key needed
185
185
  - ✅ `bash_execute` - No API key needed
186
+ - ✅ `recent_changes` - No API key needed (git-based file search by modification time)
186
187
  - 🔑 `weather_info` - Requires `toolApiKeys.openweather`
187
188
  - 🔑 `web_search` - Requires `toolApiKeys.serper`
188
189
  - 🔑 `deep_research` - Requires `toolApiKeys.serper`
@@ -264,6 +265,36 @@ You can also run any MCP server via npx or custom executables:
264
265
 
265
266
  **Note:** Internal MCP servers must be built and available in the `b4m-core/packages/mcp/dist/src/` directory. The CLI will automatically find them if you're running from the monorepo. For Docker-based servers, ensure Docker is installed and the image is accessible.
266
267
 
268
+ ## Git-Aware Code Search
269
+
270
+ The CLI includes a `recent_changes` tool that uses git history to find recently modified files. This significantly speeds up debugging by narrowing the search space to recently changed code.
271
+
272
+ ### Use Cases
273
+
274
+ - **Recent bug debugging:** "I just broke something, can you help fix it?"
275
+ - **Understanding feature development:** "What did we change for the new dashboard?"
276
+ - **Finding active development areas:** "What are we actively working on?"
277
+ - **Code review prep:** "What changed since last release?"
278
+
279
+ ### Parameters
280
+
281
+ - `since` - Time range to search (default: "7 days ago")
282
+ - Examples: "2 hours ago", "3 days ago", "2025-01-01"
283
+ - `path` - Filter to specific directory (default: entire repo)
284
+ - Examples: "src/components", "apps/client", "**/*.test.ts"
285
+ - `limit` - Maximum files to return (default: 50)
286
+ - `include_stats` - Show lines added/removed (default: false)
287
+
288
+ ### How It Works
289
+
290
+ The tool uses `git log` to track file modifications and returns:
291
+ - Files sorted by activity (most commits first)
292
+ - Commit messages for context
293
+ - Optional statistics showing lines changed
294
+ - Filtered results based on time and path
295
+
296
+ **Performance benefit:** Instead of searching 50+ files (5-10 minutes), find the 3 relevant files in ~30 seconds.
297
+
267
298
  ## Context Files
268
299
 
269
300
  The CLI supports loading project-specific instructions from context files, similar to CLAUDE.md in Claude Code. These files provide persistent instructions that are automatically included in the agent's system prompt.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  CurationArtifactType
4
- } from "./chunk-7L7YVDTP.js";
4
+ } from "./chunk-CRG4F6FD.js";
5
5
 
6
6
  // ../../b4m-core/packages/services/dist/src/notebookCurationService/artifactExtractor.js
7
7
  var ARTIFACT_TAG_REGEX = /<artifact\s+(.*?)>([\s\S]*?)<\/artifact>/gi;
@@ -303,17 +303,8 @@ var b4mLLMTools = z5.enum([
303
303
  "sunrise_sunset",
304
304
  "iss_tracker",
305
305
  "planet_visibility",
306
- // File operation tools
307
- "file_read",
308
- "create_file",
309
- "edit_local_file",
310
- "glob_files",
311
- "grep_search",
312
- "delete_file",
313
306
  // Knowledge base search
314
- "search_knowledge_base",
315
- // Shell execution
316
- "bash_execute"
307
+ "search_knowledge_base"
317
308
  ]);
318
309
  var B4MLLMToolsList = b4mLLMTools.options.map((tool) => tool);
319
310
  var RechartsChartTypeSchema = z5.enum([
@@ -6,12 +6,12 @@ import {
6
6
  getSettingsByNames,
7
7
  obfuscateApiKey,
8
8
  secureParameters
9
- } from "./chunk-6ZSQC2VF.js";
9
+ } from "./chunk-KGERFIL4.js";
10
10
  import {
11
11
  ApiKeyType,
12
12
  MementoTier,
13
13
  isSupportedEmbeddingModel
14
- } from "./chunk-7L7YVDTP.js";
14
+ } from "./chunk-CRG4F6FD.js";
15
15
 
16
16
  // ../../b4m-core/packages/services/dist/src/apiKeyService/get.js
17
17
  import { z } from "zod";
@@ -15,7 +15,7 @@ import {
15
15
  dayjsConfig_default,
16
16
  extractSnippetMeta,
17
17
  settingsMap
18
- } from "./chunk-7L7YVDTP.js";
18
+ } from "./chunk-CRG4F6FD.js";
19
19
  import {
20
20
  Logger
21
21
  } from "./chunk-OCYRD7D6.js";
@@ -7,11 +7,11 @@ import {
7
7
  getSettingsMap,
8
8
  getSettingsValue,
9
9
  secureParameters
10
- } from "./chunk-6ZSQC2VF.js";
10
+ } from "./chunk-KGERFIL4.js";
11
11
  import {
12
12
  KnowledgeType,
13
13
  SupportedFabFileMimeTypes
14
- } from "./chunk-7L7YVDTP.js";
14
+ } from "./chunk-CRG4F6FD.js";
15
15
 
16
16
  // ../../b4m-core/packages/services/dist/src/fabFileService/create.js
17
17
  import { z } from "zod";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  BadRequestError,
4
4
  secureParameters
5
- } from "./chunk-6ZSQC2VF.js";
5
+ } from "./chunk-KGERFIL4.js";
6
6
  import {
7
7
  CompletionApiUsageTransaction,
8
8
  GenericCreditDeductTransaction,
@@ -12,7 +12,7 @@ import {
12
12
  TextGenerationUsageTransaction,
13
13
  TransferCreditTransaction,
14
14
  VideoGenerationUsageTransaction
15
- } from "./chunk-7L7YVDTP.js";
15
+ } from "./chunk-CRG4F6FD.js";
16
16
 
17
17
  // ../../b4m-core/packages/services/dist/src/creditService/subtractCredits.js
18
18
  import { z } from "zod";
@@ -2,9 +2,9 @@
2
2
  import {
3
3
  createFabFile,
4
4
  createFabFileSchema
5
- } from "./chunk-U4GFPBS7.js";
6
- import "./chunk-6ZSQC2VF.js";
7
- import "./chunk-7L7YVDTP.js";
5
+ } from "./chunk-QDD3PG2I.js";
6
+ import "./chunk-KGERFIL4.js";
7
+ import "./chunk-CRG4F6FD.js";
8
8
  import "./chunk-OCYRD7D6.js";
9
9
  export {
10
10
  createFabFile,
package/dist/index.js CHANGED
@@ -5,15 +5,15 @@ import {
5
5
  getEffectiveApiKey,
6
6
  getOpenWeatherKey,
7
7
  getSerperKey
8
- } from "./chunk-AHVXP2NS.js";
8
+ } from "./chunk-ETQCFCFT.js";
9
9
  import {
10
10
  ConfigStore
11
11
  } from "./chunk-23T2XGSZ.js";
12
12
  import {
13
13
  useCliStore
14
14
  } from "./chunk-EIDW3VBS.js";
15
- import "./chunk-LPM3TSSU.js";
16
- import "./chunk-U4GFPBS7.js";
15
+ import "./chunk-TNLRJZRH.js";
16
+ import "./chunk-QDD3PG2I.js";
17
17
  import {
18
18
  BFLImageService,
19
19
  BaseStorage,
@@ -25,7 +25,7 @@ import {
25
25
  OpenAIBackend,
26
26
  OpenAIImageService,
27
27
  XAIImageService
28
- } from "./chunk-6ZSQC2VF.js";
28
+ } from "./chunk-KGERFIL4.js";
29
29
  import {
30
30
  AiEvents,
31
31
  ApiKeyEvents,
@@ -81,7 +81,7 @@ import {
81
81
  XAI_IMAGE_MODELS,
82
82
  b4mLLMTools,
83
83
  getMcpProviderMetadata
84
- } from "./chunk-7L7YVDTP.js";
84
+ } from "./chunk-CRG4F6FD.js";
85
85
  import {
86
86
  Logger
87
87
  } from "./chunk-OCYRD7D6.js";
@@ -976,7 +976,7 @@ function InputPrompt({
976
976
  setFileSelectedIndex(0);
977
977
  }, [filteredFiles]);
978
978
  useInput2(
979
- (_input, key) => {
979
+ (input, key) => {
980
980
  if (fileAutocomplete?.active && filteredFiles.length > 0) {
981
981
  if (key.upArrow) {
982
982
  setFileSelectedIndex((prev) => prev > 0 ? prev - 1 : filteredFiles.length - 1);
@@ -1981,7 +1981,7 @@ import SelectInput4 from "ink-select-input";
1981
1981
  function SessionSelector({ sessions, currentSession, onSelect, onCancel }) {
1982
1982
  const [step, setStep] = useState6("selection");
1983
1983
  const [selectedSession, setSelectedSession] = useState6(null);
1984
- useInput7((input, key) => {
1984
+ useInput7((_input, key) => {
1985
1985
  if (key.escape) {
1986
1986
  if (step === "confirmation") {
1987
1987
  setStep("selection");
@@ -1992,12 +1992,29 @@ function SessionSelector({ sessions, currentSession, onSelect, onCancel }) {
1992
1992
  }
1993
1993
  });
1994
1994
  const hasUnsavedWork = currentSession && currentSession.messages.length > 0;
1995
+ const formatTimeAgo = (timestamp) => {
1996
+ const now = /* @__PURE__ */ new Date();
1997
+ const then = new Date(timestamp);
1998
+ const diffMs = now.getTime() - then.getTime();
1999
+ const diffMins = Math.floor(diffMs / 6e4);
2000
+ const diffHours = Math.floor(diffMs / 36e5);
2001
+ const diffDays = Math.floor(diffMs / 864e5);
2002
+ if (diffMins < 60) {
2003
+ return `${diffMins}m`;
2004
+ } else if (diffHours < 24) {
2005
+ return `${diffHours}h`;
2006
+ } else {
2007
+ return `${diffDays}d`;
2008
+ }
2009
+ };
1995
2010
  const items = sessions.map((session, index) => {
1996
- const messageCount = session.messages.length;
1997
- const lastUpdated = new Date(session.updatedAt).toLocaleString();
2011
+ const userMessages = session.messages.filter((msg) => msg.role === "user");
2012
+ const lastUserMessage = userMessages[userMessages.length - 1];
2013
+ const preview = lastUserMessage ? lastUserMessage.content.slice(0, 50).replace(/\n/g, " ") + (lastUserMessage.content.length > 50 ? "..." : "") : "No messages";
2014
+ const timeAgo = formatTimeAgo(session.updatedAt);
1998
2015
  return {
1999
- label: `[${index + 1}] ${session.name} (${messageCount} msg, ${session.model})
2000
- Last updated: ${lastUpdated}`,
2016
+ key: session.id,
2017
+ label: `[${index + 1}] ${preview} (${timeAgo})`,
2001
2018
  value: session
2002
2019
  };
2003
2020
  });
@@ -2237,6 +2254,9 @@ var SessionStore = class {
2237
2254
  * Save a session to disk
2238
2255
  */
2239
2256
  async save(session) {
2257
+ if (session.messages.length === 0) {
2258
+ throw new Error("Cannot save session with no messages");
2259
+ }
2240
2260
  await this.init();
2241
2261
  const filePath = path3.join(this.basePath, `${session.id}.json`);
2242
2262
  try {
@@ -2279,13 +2299,14 @@ var SessionStore = class {
2279
2299
  }
2280
2300
  /**
2281
2301
  * List all saved sessions
2302
+ * @param limit - Optional limit on number of sessions to return (returns most recent)
2282
2303
  */
2283
- async list() {
2304
+ async list(limit) {
2284
2305
  await this.init();
2285
2306
  try {
2286
2307
  const files = await fs3.readdir(this.basePath);
2287
2308
  const jsonFiles = files.filter((f) => f.endsWith(".json"));
2288
- const sessions = await Promise.all(
2309
+ const sessionsWithFiles = await Promise.all(
2289
2310
  jsonFiles.map(async (file) => {
2290
2311
  const filePath = path3.join(this.basePath, file);
2291
2312
  const data = await fs3.readFile(filePath, "utf-8");
@@ -2296,10 +2317,23 @@ var SessionStore = class {
2296
2317
  }
2297
2318
  return msg;
2298
2319
  });
2299
- return session;
2320
+ return { session, filePath };
2300
2321
  })
2301
2322
  );
2302
- return sessions.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
2323
+ const validSessions = [];
2324
+ for (const { session, filePath } of sessionsWithFiles) {
2325
+ if (session.messages.length === 0) {
2326
+ try {
2327
+ await fs3.unlink(filePath);
2328
+ } catch (error) {
2329
+ console.error(`Failed to delete empty session ${session.id}:`, error);
2330
+ }
2331
+ } else {
2332
+ validSessions.push(session);
2333
+ }
2334
+ }
2335
+ const sorted = validSessions.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
2336
+ return limit ? sorted.slice(0, limit) : sorted;
2303
2337
  } catch (error) {
2304
2338
  console.error("Failed to list sessions:", error);
2305
2339
  return [];
@@ -10178,8 +10212,343 @@ var editLocalFileTool = {
10178
10212
  })
10179
10213
  };
10180
10214
 
10215
+ // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/recentChanges/index.js
10216
+ import { spawn as spawn2 } from "child_process";
10217
+ var DEFAULT_SINCE = "7 days ago";
10218
+ var DEFAULT_LIMIT = 50;
10219
+ var DEFAULT_INCLUDE_STATS = false;
10220
+ function parseStatusLine(line) {
10221
+ const results = [];
10222
+ if (line.length < 4)
10223
+ return results;
10224
+ const indexStatus = line[0];
10225
+ const workTreeStatus = line[1];
10226
+ const filePath = line.slice(3);
10227
+ const typeMap = {
10228
+ M: "modified",
10229
+ A: "added",
10230
+ D: "deleted",
10231
+ R: "renamed",
10232
+ C: "copied"
10233
+ };
10234
+ if (indexStatus !== " " && indexStatus !== "?") {
10235
+ results.push({
10236
+ path: filePath,
10237
+ status: "staged",
10238
+ type: typeMap[indexStatus] || "modified"
10239
+ });
10240
+ }
10241
+ if (workTreeStatus !== " " && workTreeStatus !== "?") {
10242
+ results.push({
10243
+ path: filePath,
10244
+ status: "unstaged",
10245
+ type: typeMap[workTreeStatus] || "modified"
10246
+ });
10247
+ }
10248
+ if (indexStatus === "?" && workTreeStatus === "?") {
10249
+ results.push({
10250
+ path: filePath,
10251
+ status: "untracked",
10252
+ type: "added"
10253
+ });
10254
+ }
10255
+ return results;
10256
+ }
10257
+ async function getWorkingTreeChanges(filterPath) {
10258
+ const args = ["status", "--porcelain"];
10259
+ if (filterPath) {
10260
+ args.push("--", filterPath);
10261
+ }
10262
+ return new Promise((resolve3) => {
10263
+ const proc = spawn2("git", args, {
10264
+ cwd: process.cwd(),
10265
+ stdio: ["ignore", "pipe", "pipe"]
10266
+ });
10267
+ let stdout = "";
10268
+ proc.stdout.on("data", (data) => {
10269
+ stdout += data.toString();
10270
+ });
10271
+ proc.on("close", (exitCode) => {
10272
+ if (exitCode !== 0) {
10273
+ resolve3([]);
10274
+ return;
10275
+ }
10276
+ const files = stdout.split("\n").filter((line) => line.length > 0).flatMap(parseStatusLine);
10277
+ resolve3(files);
10278
+ });
10279
+ proc.on("error", () => {
10280
+ resolve3([]);
10281
+ });
10282
+ });
10283
+ }
10284
+ async function getRecentChanges(params) {
10285
+ const { since = DEFAULT_SINCE, path: filterPath, limit = DEFAULT_LIMIT, include_stats = DEFAULT_INCLUDE_STATS } = params;
10286
+ const args = ["log", `--since=${since}`, "--name-only", "--pretty=format:"];
10287
+ if (filterPath) {
10288
+ args.push("--", filterPath);
10289
+ }
10290
+ return new Promise((resolve3) => {
10291
+ const proc = spawn2("git", args, {
10292
+ cwd: process.cwd(),
10293
+ stdio: ["ignore", "pipe", "pipe"]
10294
+ });
10295
+ let stdout = "";
10296
+ let stderr = "";
10297
+ proc.stdout.on("data", (data) => {
10298
+ stdout += data.toString();
10299
+ });
10300
+ proc.stderr.on("data", (data) => {
10301
+ stderr += data.toString();
10302
+ });
10303
+ proc.on("close", async (exitCode) => {
10304
+ const workingTree = await getWorkingTreeChanges(filterPath);
10305
+ if (exitCode !== 0) {
10306
+ resolve3({
10307
+ files: [],
10308
+ workingTree,
10309
+ timeRange: since,
10310
+ totalFiles: 0,
10311
+ error: stderr || "Git command failed"
10312
+ });
10313
+ return;
10314
+ }
10315
+ const files = stdout.split("\n").filter((line) => line.trim().length > 0).reduce((acc, filePath) => {
10316
+ const existing = acc.find((f) => f.path === filePath);
10317
+ if (existing) {
10318
+ existing.changes++;
10319
+ } else {
10320
+ acc.push({ path: filePath, changes: 1 });
10321
+ }
10322
+ return acc;
10323
+ }, []);
10324
+ files.sort((a, b) => b.changes - a.changes);
10325
+ const limitedFiles = files.slice(0, limit);
10326
+ if (include_stats && limitedFiles.length > 0) {
10327
+ const filesWithStats = await getFileStats(limitedFiles, since, filterPath);
10328
+ resolve3({
10329
+ files: filesWithStats,
10330
+ workingTree,
10331
+ timeRange: since,
10332
+ totalFiles: files.length
10333
+ });
10334
+ } else {
10335
+ resolve3({
10336
+ files: limitedFiles,
10337
+ workingTree,
10338
+ timeRange: since,
10339
+ totalFiles: files.length
10340
+ });
10341
+ }
10342
+ });
10343
+ proc.on("error", async (error) => {
10344
+ const workingTree = await getWorkingTreeChanges(filterPath);
10345
+ resolve3({
10346
+ files: [],
10347
+ workingTree,
10348
+ timeRange: since,
10349
+ totalFiles: 0,
10350
+ error: `Failed to execute git command: ${error.message}`
10351
+ });
10352
+ });
10353
+ });
10354
+ }
10355
+ async function getFileStats(files, since, filterPath) {
10356
+ const args = ["log", `--since=${since}`, "--numstat", "--pretty=format:"];
10357
+ if (filterPath) {
10358
+ args.push("--", filterPath);
10359
+ }
10360
+ return new Promise((resolve3) => {
10361
+ const proc = spawn2("git", args, {
10362
+ cwd: process.cwd(),
10363
+ stdio: ["ignore", "pipe", "pipe"]
10364
+ });
10365
+ let stdout = "";
10366
+ proc.stdout.on("data", (data) => {
10367
+ stdout += data.toString();
10368
+ });
10369
+ proc.on("close", () => {
10370
+ const stats = /* @__PURE__ */ new Map();
10371
+ stdout.split("\n").forEach((line) => {
10372
+ const match = line.match(/^(\d+)\s+(\d+)\s+(.+)$/);
10373
+ if (match) {
10374
+ const [, additions, deletions, filePath] = match;
10375
+ const existing = stats.get(filePath);
10376
+ if (existing) {
10377
+ existing.additions += parseInt(additions, 10);
10378
+ existing.deletions += parseInt(deletions, 10);
10379
+ } else {
10380
+ stats.set(filePath, {
10381
+ additions: parseInt(additions, 10),
10382
+ deletions: parseInt(deletions, 10)
10383
+ });
10384
+ }
10385
+ }
10386
+ });
10387
+ const result = files.map((file) => {
10388
+ const stat3 = stats.get(file.path);
10389
+ if (stat3) {
10390
+ return { ...file, ...stat3 };
10391
+ }
10392
+ return file;
10393
+ });
10394
+ resolve3(result);
10395
+ });
10396
+ proc.on("error", () => {
10397
+ resolve3(files);
10398
+ });
10399
+ });
10400
+ }
10401
+ function formatResult2(result) {
10402
+ const parts = [];
10403
+ if (result.error) {
10404
+ parts.push(`Error: ${result.error}`);
10405
+ parts.push("");
10406
+ parts.push("Make sure you are running this command in a git repository.");
10407
+ if (result.workingTree.length > 0) {
10408
+ parts.push("");
10409
+ parts.push(formatWorkingTree(result.workingTree));
10410
+ }
10411
+ return parts.join("\n");
10412
+ }
10413
+ if (result.workingTree.length > 0) {
10414
+ parts.push(formatWorkingTree(result.workingTree));
10415
+ parts.push("");
10416
+ }
10417
+ parts.push(`Recently committed files (${result.timeRange}):`);
10418
+ parts.push("");
10419
+ if (result.files.length === 0) {
10420
+ parts.push("No files changed in this time period.");
10421
+ } else {
10422
+ result.files.forEach((file, index) => {
10423
+ const stats = file.additions !== void 0 && file.deletions !== void 0 ? ` (+${file.additions}/-${file.deletions})` : "";
10424
+ parts.push(`${index + 1}. ${file.path} (${file.changes} commits)${stats}`);
10425
+ });
10426
+ if (result.totalFiles > result.files.length) {
10427
+ parts.push("");
10428
+ parts.push(`Showing ${result.files.length} of ${result.totalFiles} changed files.`);
10429
+ parts.push(`Use the 'limit' parameter to see more files.`);
10430
+ }
10431
+ }
10432
+ return parts.join("\n");
10433
+ }
10434
+ function formatWorkingTree(workingTree) {
10435
+ const parts = [];
10436
+ const staged = workingTree.filter((f) => f.status === "staged");
10437
+ const unstaged = workingTree.filter((f) => f.status === "unstaged");
10438
+ const untracked = workingTree.filter((f) => f.status === "untracked");
10439
+ parts.push("Working tree changes:");
10440
+ if (staged.length > 0) {
10441
+ parts.push("");
10442
+ parts.push(` Staged (${staged.length}):`);
10443
+ staged.forEach((f) => parts.push(` ${f.type}: ${f.path}`));
10444
+ }
10445
+ if (unstaged.length > 0) {
10446
+ parts.push("");
10447
+ parts.push(` Unstaged (${unstaged.length}):`);
10448
+ unstaged.forEach((f) => parts.push(` ${f.type}: ${f.path}`));
10449
+ }
10450
+ if (untracked.length > 0) {
10451
+ parts.push("");
10452
+ parts.push(` Untracked (${untracked.length}):`);
10453
+ untracked.forEach((f) => parts.push(` ${f.path}`));
10454
+ }
10455
+ return parts.join("\n");
10456
+ }
10457
+ var recentChangesTool = {
10458
+ name: "recent_changes",
10459
+ implementation: (context) => ({
10460
+ toolFn: async (value) => {
10461
+ const params = value;
10462
+ context.logger.info("RecentChanges: Getting recently changed files", {
10463
+ since: params.since || DEFAULT_SINCE,
10464
+ path: params.path || "all",
10465
+ limit: params.limit || DEFAULT_LIMIT,
10466
+ include_stats: params.include_stats || DEFAULT_INCLUDE_STATS
10467
+ });
10468
+ if (context.onStart) {
10469
+ await context.onStart("recent_changes", {
10470
+ since: params.since,
10471
+ path: params.path
10472
+ });
10473
+ }
10474
+ try {
10475
+ const result = await getRecentChanges(params);
10476
+ const formattedResult = formatResult2(result);
10477
+ context.logger.info("RecentChanges: Retrieved file changes", {
10478
+ totalFiles: result.totalFiles,
10479
+ displayedFiles: result.files.length,
10480
+ error: result.error
10481
+ });
10482
+ if (context.onFinish) {
10483
+ await context.onFinish("recent_changes", {
10484
+ totalFiles: result.totalFiles,
10485
+ displayedFiles: result.files.length
10486
+ });
10487
+ }
10488
+ return formattedResult;
10489
+ } catch (error) {
10490
+ context.logger.error("RecentChanges: Failed to get changes", error);
10491
+ if (context.onFinish) {
10492
+ await context.onFinish("recent_changes", {
10493
+ error: error instanceof Error ? error.message : "Unknown error"
10494
+ });
10495
+ }
10496
+ throw error;
10497
+ }
10498
+ },
10499
+ toolSchema: {
10500
+ name: "recent_changes",
10501
+ description: `Get a list of recently changed files in the git repository, including uncommitted working tree changes (staged, unstaged, and untracked files) plus committed changes sorted by activity. This tool is extremely useful for:
10502
+
10503
+ - **Debugging recent issues**: "Something broke after my last commit" \u2192 See exactly what changed
10504
+ - **Understanding active development**: "What are we working on?" \u2192 See most active files and current work-in-progress
10505
+ - **Code review preparation**: "What changed since last release?" \u2192 Get comprehensive file list
10506
+ - **Feature context**: "What files are part of the new dashboard?" \u2192 Filter by path
10507
+ - **Current work-in-progress**: See staged, unstaged, and untracked files alongside commit history
10508
+
10509
+ The tool combines git status (working tree) with git log (commit history) to give a complete picture of recent activity. Working tree changes are shown first, followed by committed files ranked by number of commits (most active first).
10510
+
10511
+ **Time Range Examples:**
10512
+ - "7 days ago" (default)
10513
+ - "2 hours ago"
10514
+ - "3 weeks ago"
10515
+ - "2025-01-01"
10516
+ - "yesterday"
10517
+
10518
+ **Path Filter Examples:**
10519
+ - "src/components" - Only files in components directory
10520
+ - "apps/client" - Only client app files
10521
+ - "**/*.test.ts" - Only test files (use glob patterns)
10522
+
10523
+ **Performance:** Very fast (< 1 second) since it only queries git metadata, not file contents.`,
10524
+ parameters: {
10525
+ type: "object",
10526
+ properties: {
10527
+ since: {
10528
+ type: "string",
10529
+ description: 'Time range for changes (default: "7 days ago"). Examples: "2 hours ago", "3 weeks ago", "2025-01-01", "yesterday".'
10530
+ },
10531
+ path: {
10532
+ type: "string",
10533
+ description: 'Optional path filter to limit results to specific directory or pattern. Examples: "src/components", "apps/client", "**/*.test.ts".'
10534
+ },
10535
+ limit: {
10536
+ type: "number",
10537
+ description: "Maximum number of files to return (default: 50). Files are sorted by activity (most commits first)."
10538
+ },
10539
+ include_stats: {
10540
+ type: "boolean",
10541
+ description: "Include lines added/removed statistics for each file (default: false). Note: This makes the command slightly slower."
10542
+ }
10543
+ },
10544
+ required: []
10545
+ }
10546
+ }
10547
+ })
10548
+ };
10549
+
10181
10550
  // ../../b4m-core/packages/services/dist/src/llm/tools/index.js
10182
- var tools = {
10551
+ var b4mTools = {
10183
10552
  dice_roll: diceRollTool,
10184
10553
  weather_info: weatherTool,
10185
10554
  image_generation: imageGenerationTool,
@@ -10202,18 +10571,23 @@ var tools = {
10202
10571
  sunrise_sunset: sunriseSunsetTool,
10203
10572
  iss_tracker: issTrackerTool,
10204
10573
  planet_visibility: planetVisibilityTool,
10574
+ // Knowledge base search
10575
+ search_knowledge_base: knowledgeBaseSearchTool
10576
+ };
10577
+ var cliOnlyTools = {
10578
+ // File operation tools
10205
10579
  file_read: fileReadTool,
10206
10580
  create_file: createFileTool,
10207
10581
  edit_local_file: editLocalFileTool,
10208
10582
  glob_files: globFilesTool,
10209
10583
  grep_search: grepSearchTool,
10210
10584
  delete_file: deleteFileTool,
10211
- // Knowledge base search
10212
- search_knowledge_base: knowledgeBaseSearchTool,
10213
10585
  // Shell execution
10214
- bash_execute: bashExecuteTool
10586
+ bash_execute: bashExecuteTool,
10587
+ // Git operations
10588
+ recent_changes: recentChangesTool
10215
10589
  };
10216
- var generateTools = (userId, user, logger2, { db }, storage, imageGenerateStorage, statusUpdate, onStart, onFinish, llm, config, model) => {
10590
+ var generateTools = (userId, user, logger2, { db }, storage, imageGenerateStorage, statusUpdate, onStart, onFinish, llm, config, model, tools = b4mTools) => {
10217
10591
  const context = {
10218
10592
  userId,
10219
10593
  user,
@@ -10234,9 +10608,9 @@ var generateTools = (userId, user, logger2, { db }, storage, imageGenerateStorag
10234
10608
  };
10235
10609
  var ATLASSIAN_RECONNECT_MESSAGE = "\u26A0\uFE0F Your Atlassian connection has expired.\n\nPlease reconnect your Atlassian account in Settings > Connected Apps to continue using Confluence and Jira tools.";
10236
10610
  var generateMcpTools = async (mcpData) => {
10237
- let tools2;
10611
+ let tools;
10238
10612
  try {
10239
- tools2 = await mcpData.getTools();
10613
+ tools = await mcpData.getTools();
10240
10614
  } catch (error) {
10241
10615
  const errorName = error instanceof Error ? error.name : "";
10242
10616
  if (errorName === "AtlassianReconnectRequiredError") {
@@ -10245,12 +10619,12 @@ var generateMcpTools = async (mcpData) => {
10245
10619
  }
10246
10620
  throw error;
10247
10621
  }
10248
- const toolList = Array.isArray(tools2) ? tools2 : tools2?.tools || [];
10249
- if (!Array.isArray(tools2)) {
10250
- console.warn(`MCP server ${mcpData.serverName} returned unexpected tools payload:`, JSON.stringify(tools2));
10622
+ const toolList = Array.isArray(tools) ? tools : tools?.tools || [];
10623
+ if (!Array.isArray(tools)) {
10624
+ console.warn(`MCP server ${mcpData.serverName} returned unexpected tools payload:`, JSON.stringify(tools));
10251
10625
  }
10252
10626
  if (!Array.isArray(toolList)) {
10253
- throw new Error(`Expected getTools() to return an array, but got ${typeof tools2}`);
10627
+ throw new Error(`Expected getTools() to return an array, but got ${typeof tools}`);
10254
10628
  }
10255
10629
  const result = toolList.map((item) => {
10256
10630
  const { name: originalToolName, ...rest } = item;
@@ -10589,7 +10963,8 @@ var LOCAL_TOOLS = [
10589
10963
  "dice_roll",
10590
10964
  "math_evaluate",
10591
10965
  "current_datetime",
10592
- "bash_execute"
10966
+ "bash_execute",
10967
+ "recent_changes"
10593
10968
  ];
10594
10969
  function isServerTool(toolName) {
10595
10970
  return SERVER_TOOLS.includes(toolName);
@@ -10615,11 +10990,11 @@ async function executeTool(toolName, input, apiClient, localToolFn) {
10615
10990
  }
10616
10991
 
10617
10992
  // src/utils/shellRunner.ts
10618
- import { spawn as spawn2 } from "child_process";
10993
+ import { spawn as spawn3 } from "child_process";
10619
10994
  async function runShellCommand(options) {
10620
10995
  const { command, cwd, timeoutMs, env, stdin } = options;
10621
10996
  return new Promise((resolve3) => {
10622
- const child = spawn2("bash", ["-c", command], {
10997
+ const child = spawn3("bash", ["-c", command], {
10623
10998
  cwd,
10624
10999
  env,
10625
11000
  stdio: ["pipe", "pipe", "pipe"]
@@ -11048,37 +11423,21 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
11048
11423
  adminSettings: mockAdminSettings
11049
11424
  }
11050
11425
  };
11051
- const toolConfig = {
11426
+ const enabledB4mToolNames = [
11052
11427
  // Local-only tools (no external API keys needed)
11053
- dice_roll: {},
11054
- math_evaluate: {},
11055
- current_datetime: {},
11056
- prompt_enhancement: {},
11057
- // File operation tools (CLI-specific, local execution)
11058
- file_read: {},
11059
- create_file: {},
11060
- edit_local_file: {},
11061
- glob_files: {},
11062
- grep_search: {},
11063
- delete_file: {},
11064
- // Shell execution (always requires permission)
11065
- bash_execute: {},
11428
+ "dice_roll",
11429
+ "math_evaluate",
11430
+ "current_datetime",
11431
+ "prompt_enhancement",
11066
11432
  // Server-side tools (executed via /api/ai/v1/tools Lambda)
11067
- // Routed by ToolRouter to use B4M company API keys
11068
- weather_info: {},
11069
- web_search: {},
11070
- web_fetch: {}
11071
- // Disable web-only tools
11072
- // blog_publish: undefined,
11073
- // blog_edit: undefined,
11074
- // blog_draft: undefined,
11075
- // image_generation: undefined,
11076
- // edit_image: undefined,
11077
- // mermaid_chart: undefined,
11078
- // recharts: undefined,
11079
- // edit_file: undefined,
11080
- // deep_research: undefined,
11081
- };
11433
+ "weather_info",
11434
+ "web_search",
11435
+ "web_fetch"
11436
+ ];
11437
+ const filteredB4mTools = Object.fromEntries(
11438
+ enabledB4mToolNames.filter((name) => name in b4mTools).map((name) => [name, b4mTools[name]])
11439
+ );
11440
+ const tools_to_generate = { ...filteredB4mTools, ...cliOnlyTools };
11082
11441
  const toolsMap = generateTools(
11083
11442
  userId,
11084
11443
  user,
@@ -11091,17 +11450,18 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
11091
11450
  onStart,
11092
11451
  onFinish,
11093
11452
  llm,
11094
- toolConfig,
11095
- model
11453
+ {},
11454
+ model,
11455
+ tools_to_generate
11096
11456
  );
11097
- let tools2 = Object.entries(toolsMap).filter(([key]) => toolConfig[key] !== void 0).map(
11457
+ let tools = Object.entries(toolsMap).map(
11098
11458
  ([_, tool]) => wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient)
11099
11459
  );
11100
11460
  if (toolFilter) {
11101
11461
  const { allowedTools, deniedTools } = toolFilter;
11102
11462
  const normalizedAllowed = allowedTools?.map(normalizeToolName);
11103
11463
  const normalizedDenied = deniedTools?.map(normalizeToolName);
11104
- tools2 = tools2.filter((tool) => {
11464
+ tools = tools.filter((tool) => {
11105
11465
  const toolName = tool.toolSchema.name;
11106
11466
  if (normalizedDenied && normalizedDenied.includes(toolName)) {
11107
11467
  return false;
@@ -11112,7 +11472,7 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
11112
11472
  return true;
11113
11473
  });
11114
11474
  }
11115
- return { tools: tools2, agentContext };
11475
+ return { tools, agentContext };
11116
11476
  }
11117
11477
 
11118
11478
  // src/utils/PermissionManager.ts
@@ -11490,9 +11850,9 @@ var TokenCounter = class {
11490
11850
  * Count tokens in tool schemas.
11491
11851
  * Tool schemas are sent as part of the API call and consume context.
11492
11852
  */
11493
- countToolSchemaTokens(tools2) {
11494
- if (tools2.length === 0) return 0;
11495
- const schemaText = tools2.map(
11853
+ countToolSchemaTokens(tools) {
11854
+ if (tools.length === 0) return 0;
11855
+ const schemaText = tools.map(
11496
11856
  ({ toolSchema }) => `Tool: ${toolSchema.name}
11497
11857
  Description: ${toolSchema.description}
11498
11858
  Parameters: ${JSON.stringify(toolSchema.parameters)}`
@@ -11794,14 +12154,14 @@ var McpManager = class {
11794
12154
  getTools: async () => client.tools,
11795
12155
  callTool: async (name, args) => client.callTool(name, args)
11796
12156
  };
11797
- const tools2 = await generateMcpTools(mcpData);
12157
+ const tools = await generateMcpTools(mcpData);
11798
12158
  this.servers.set(serverConfig.name, {
11799
12159
  name: serverConfig.name,
11800
12160
  client,
11801
- tools: tools2
12161
+ tools
11802
12162
  });
11803
12163
  this.connectionStates.set(serverConfig.name, "connected");
11804
- logger.debug(`\u2705 Connected to ${serverConfig.name} (${tools2.length} tools)`);
12164
+ logger.debug(`\u2705 Connected to ${serverConfig.name} (${tools.length} tools)`);
11805
12165
  } catch (error) {
11806
12166
  this.connectionStates.set(serverConfig.name, "failed");
11807
12167
  logger.debug(`\u274C Failed to connect to ${serverConfig.name}: ${error}`);
@@ -12686,7 +13046,7 @@ import { isAxiosError as isAxiosError2 } from "axios";
12686
13046
  // package.json
12687
13047
  var package_default = {
12688
13048
  name: "@bike4mind/cli",
12689
- version: "0.2.25-cli-file-read-offset.18530+8faa14e02",
13049
+ version: "0.2.25-cli-file-read-offset.18541+a5717c121",
12690
13050
  type: "module",
12691
13051
  description: "Interactive CLI tool for Bike4Mind with ReAct agents",
12692
13052
  license: "UNLICENSED",
@@ -12794,10 +13154,10 @@ var package_default = {
12794
13154
  },
12795
13155
  devDependencies: {
12796
13156
  "@bike4mind/agents": "0.1.0",
12797
- "@bike4mind/common": "2.47.2-cli-file-read-offset.18530+8faa14e02",
12798
- "@bike4mind/mcp": "1.28.1-cli-file-read-offset.18530+8faa14e02",
12799
- "@bike4mind/services": "2.45.2-cli-file-read-offset.18530+8faa14e02",
12800
- "@bike4mind/utils": "2.3.4-cli-file-read-offset.18530+8faa14e02",
13157
+ "@bike4mind/common": "2.47.2-cli-file-read-offset.18541+a5717c121",
13158
+ "@bike4mind/mcp": "1.28.1-cli-file-read-offset.18541+a5717c121",
13159
+ "@bike4mind/services": "2.45.2-cli-file-read-offset.18541+a5717c121",
13160
+ "@bike4mind/utils": "2.3.4-cli-file-read-offset.18541+a5717c121",
12801
13161
  "@types/better-sqlite3": "^7.6.13",
12802
13162
  "@types/diff": "^5.0.9",
12803
13163
  "@types/jsonwebtoken": "^9.0.4",
@@ -12814,7 +13174,7 @@ var package_default = {
12814
13174
  optionalDependencies: {
12815
13175
  "@vscode/ripgrep": "^1.17.0"
12816
13176
  },
12817
- gitHead: "8faa14e02d9366eaa7099de2b41a70fc0dfc7789"
13177
+ gitHead: "a5717c121a35ad3b958b4f8471fe5a3b94781c42"
12818
13178
  };
12819
13179
 
12820
13180
  // src/config/constants.ts
@@ -14099,7 +14459,7 @@ function CliApp() {
14099
14459
  currentAgent: null,
14100
14460
  observationQueue: []
14101
14461
  };
14102
- const { tools: b4mTools } = generateCliTools(
14462
+ const { tools: b4mTools2 } = generateCliTools(
14103
14463
  config.userId,
14104
14464
  llm,
14105
14465
  modelInfo.id,
@@ -14109,7 +14469,7 @@ function CliApp() {
14109
14469
  state.configStore,
14110
14470
  apiClient
14111
14471
  );
14112
- console.log(`\u{1F6E0}\uFE0F Loaded ${b4mTools.length} B4M tool(s)`);
14472
+ console.log(`\u{1F6E0}\uFE0F Loaded ${b4mTools2.length} B4M tool(s)`);
14113
14473
  const mcpManager = new McpManager(config);
14114
14474
  await mcpManager.initialize();
14115
14475
  const mcpTools = mcpManager.getTools();
@@ -14151,7 +14511,7 @@ function CliApp() {
14151
14511
  if (skillTool) {
14152
14512
  cliTools.push(skillTool);
14153
14513
  }
14154
- const allTools = [...b4mTools, ...mcpTools, ...cliTools];
14514
+ const allTools = [...b4mTools2, ...mcpTools, ...cliTools];
14155
14515
  console.log(`\u{1F4C2} Working directory: ${process.cwd()}`);
14156
14516
  console.log(`\u{1F916} Subagent delegation enabled (explore, plan, review)`);
14157
14517
  if (skillTool) {
@@ -14161,7 +14521,7 @@ function CliApp() {
14161
14521
  }
14162
14522
  }
14163
14523
  logger.debug(
14164
- `Total tools available to agent: ${allTools.length} (${b4mTools.length} B4M + ${mcpTools.length} MCP + ${cliTools.length} CLI)`
14524
+ `Total tools available to agent: ${allTools.length} (${b4mTools2.length} B4M + ${mcpTools.length} MCP + ${cliTools.length} CLI)`
14165
14525
  );
14166
14526
  const projectDir = state.configStore.getProjectConfigDir();
14167
14527
  const contextResult = await loadContextFiles(projectDir);
@@ -14846,13 +15206,17 @@ Custom Commands:
14846
15206
  console.log("No active session to save");
14847
15207
  return;
14848
15208
  }
15209
+ if (state.session.messages.length === 0) {
15210
+ console.log("\u274C Cannot save session with no messages");
15211
+ return;
15212
+ }
14849
15213
  const sessionName = args.join(" ") || state.session.name;
14850
15214
  state.session.name = sessionName;
14851
15215
  await state.sessionStore.save(state.session);
14852
15216
  console.log(`\u2705 Session saved as "${sessionName}"`);
14853
15217
  break;
14854
15218
  case "sessions": {
14855
- const sessions = await state.sessionStore.list();
15219
+ const sessions = await state.sessionStore.list(20);
14856
15220
  if (sessions.length === 0) {
14857
15221
  console.log("\n\u{1F4DA} No saved sessions found.");
14858
15222
  console.log("\u{1F4A1} Use /save <name> to save your current session.\n");
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  CurationArtifactType
4
- } from "./chunk-7L7YVDTP.js";
4
+ } from "./chunk-CRG4F6FD.js";
5
5
 
6
6
  // ../../b4m-core/packages/services/dist/src/notebookCurationService/llmMarkdownGenerator.js
7
7
  var DEFAULT_OPTIONS = {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  CurationArtifactType
4
- } from "./chunk-7L7YVDTP.js";
4
+ } from "./chunk-CRG4F6FD.js";
5
5
 
6
6
  // ../../b4m-core/packages/services/dist/src/notebookCurationService/markdownGenerator.js
7
7
  var DEFAULT_OPTIONS = {
@@ -2,9 +2,9 @@
2
2
  import {
3
3
  findMostSimilarMemento,
4
4
  getRelevantMementos
5
- } from "./chunk-AHVXP2NS.js";
6
- import "./chunk-6ZSQC2VF.js";
7
- import "./chunk-7L7YVDTP.js";
5
+ } from "./chunk-ETQCFCFT.js";
6
+ import "./chunk-KGERFIL4.js";
7
+ import "./chunk-CRG4F6FD.js";
8
8
  import "./chunk-OCYRD7D6.js";
9
9
  export {
10
10
  findMostSimilarMemento,
@@ -333,7 +333,7 @@ import {
333
333
  validateReactArtifactV2,
334
334
  validateSvgArtifactV2,
335
335
  wikiMarkupToAdf
336
- } from "./chunk-7L7YVDTP.js";
336
+ } from "./chunk-CRG4F6FD.js";
337
337
  export {
338
338
  ALL_IMAGE_MODELS,
339
339
  ALL_IMAGE_SIZES,
@@ -132,8 +132,8 @@ import {
132
132
  validateMermaidSyntax,
133
133
  warmUpSettingsCache,
134
134
  withRetry
135
- } from "./chunk-6ZSQC2VF.js";
136
- import "./chunk-7L7YVDTP.js";
135
+ } from "./chunk-KGERFIL4.js";
136
+ import "./chunk-CRG4F6FD.js";
137
137
  import {
138
138
  Logger,
139
139
  NotificationDeduplicator,
@@ -2,9 +2,9 @@
2
2
  import {
3
3
  SubtractCreditsSchema,
4
4
  subtractCredits
5
- } from "./chunk-LPM3TSSU.js";
6
- import "./chunk-6ZSQC2VF.js";
7
- import "./chunk-7L7YVDTP.js";
5
+ } from "./chunk-TNLRJZRH.js";
6
+ import "./chunk-KGERFIL4.js";
7
+ import "./chunk-CRG4F6FD.js";
8
8
  import "./chunk-OCYRD7D6.js";
9
9
  export {
10
10
  SubtractCreditsSchema,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bike4mind/cli",
3
- "version": "0.2.25-cli-file-read-offset.18530+8faa14e02",
3
+ "version": "0.2.25-cli-file-read-offset.18541+a5717c121",
4
4
  "type": "module",
5
5
  "description": "Interactive CLI tool for Bike4Mind with ReAct agents",
6
6
  "license": "UNLICENSED",
@@ -108,10 +108,10 @@
108
108
  },
109
109
  "devDependencies": {
110
110
  "@bike4mind/agents": "0.1.0",
111
- "@bike4mind/common": "2.47.2-cli-file-read-offset.18530+8faa14e02",
112
- "@bike4mind/mcp": "1.28.1-cli-file-read-offset.18530+8faa14e02",
113
- "@bike4mind/services": "2.45.2-cli-file-read-offset.18530+8faa14e02",
114
- "@bike4mind/utils": "2.3.4-cli-file-read-offset.18530+8faa14e02",
111
+ "@bike4mind/common": "2.47.2-cli-file-read-offset.18541+a5717c121",
112
+ "@bike4mind/mcp": "1.28.1-cli-file-read-offset.18541+a5717c121",
113
+ "@bike4mind/services": "2.45.2-cli-file-read-offset.18541+a5717c121",
114
+ "@bike4mind/utils": "2.3.4-cli-file-read-offset.18541+a5717c121",
115
115
  "@types/better-sqlite3": "^7.6.13",
116
116
  "@types/diff": "^5.0.9",
117
117
  "@types/jsonwebtoken": "^9.0.4",
@@ -128,5 +128,5 @@
128
128
  "optionalDependencies": {
129
129
  "@vscode/ripgrep": "^1.17.0"
130
130
  },
131
- "gitHead": "8faa14e02d9366eaa7099de2b41a70fc0dfc7789"
131
+ "gitHead": "a5717c121a35ad3b958b4f8471fe5a3b94781c42"
132
132
  }