@bike4mind/cli 0.2.29-slack-native-search.18848 → 0.2.29

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.js CHANGED
@@ -5,7 +5,8 @@ import {
5
5
  getEffectiveApiKey,
6
6
  getOpenWeatherKey,
7
7
  getSerperKey
8
- } from "./chunk-T33BVGIR.js";
8
+ } from "./chunk-22VR7SKO.js";
9
+ import "./chunk-RUI6HNLO.js";
9
10
  import {
10
11
  ConfigStore,
11
12
  logger
@@ -14,8 +15,8 @@ import {
14
15
  selectActiveBackgroundAgents,
15
16
  useCliStore
16
17
  } from "./chunk-TVW4ZESU.js";
17
- import "./chunk-WO5GGMEO.js";
18
- import "./chunk-LM4PJCVX.js";
18
+ import "./chunk-UYN4HMRF.js";
19
+ import "./chunk-YQWFK5O2.js";
19
20
  import {
20
21
  BFLImageService,
21
22
  BaseStorage,
@@ -27,7 +28,7 @@ import {
27
28
  OpenAIBackend,
28
29
  OpenAIImageService,
29
30
  XAIImageService
30
- } from "./chunk-MDOLASLI.js";
31
+ } from "./chunk-5J4RL57F.js";
31
32
  import {
32
33
  AiEvents,
33
34
  ApiKeyEvents,
@@ -83,32 +84,37 @@ import {
83
84
  XAI_IMAGE_MODELS,
84
85
  b4mLLMTools,
85
86
  getMcpProviderMetadata
86
- } from "./chunk-MPSPHOIZ.js";
87
+ } from "./chunk-TNFZP7FG.js";
87
88
  import {
88
89
  Logger
89
90
  } from "./chunk-OCYRD7D6.js";
90
91
 
91
92
  // src/index.tsx
92
- import React21, { useState as useState9, useEffect as useEffect6, useCallback as useCallback2, useRef as useRef3 } from "react";
93
+ import React21, { useState as useState10, useEffect as useEffect7, useCallback as useCallback2, useRef as useRef3 } from "react";
93
94
  import { render, Box as Box20, Text as Text20, useApp, useInput as useInput9 } from "ink";
94
95
  import { execSync } from "child_process";
95
96
  import { randomBytes as randomBytes5 } from "crypto";
96
97
  import { v4 as uuidv411 } from "uuid";
97
98
 
98
99
  // src/components/App.tsx
99
- import React15, { useState as useState5, useEffect as useEffect4 } from "react";
100
+ import React15, { useState as useState6, useEffect as useEffect5 } from "react";
100
101
  import { Box as Box14, Text as Text14, Static, useInput as useInput6 } from "ink";
101
102
 
102
103
  // src/components/StatusBar.tsx
103
104
  import React from "react";
104
105
  import { Box, Text } from "ink";
105
- var StatusBar = React.memo(function StatusBar2({ sessionName, model, tokenUsage }) {
106
+ var StatusBar = React.memo(function StatusBar2({
107
+ sessionName,
108
+ model,
109
+ tokenUsage,
110
+ creditsUsage
111
+ }) {
106
112
  const autoAcceptEdits = useCliStore((state) => state.autoAcceptEdits);
107
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "row", justifyContent: "space-between", width: "100%", paddingX: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, sessionName), /* @__PURE__ */ React.createElement(Box, { gap: 2 }, autoAcceptEdits && /* @__PURE__ */ React.createElement(Text, { color: "green", bold: true }, "AUTO ACCEPT: Edits"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, tokenUsage.toLocaleString(), " tokens"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, model)));
113
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "row", justifyContent: "space-between", width: "100%", paddingX: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, sessionName), /* @__PURE__ */ React.createElement(Box, { gap: 2 }, autoAcceptEdits && /* @__PURE__ */ React.createElement(Text, { color: "green", bold: true }, "AUTO ACCEPT: Edits"), tokenUsage > 0 && /* @__PURE__ */ React.createElement(Text, { dimColor: true }, tokenUsage.toLocaleString(), " tokens"), creditsUsage !== void 0 && creditsUsage > 0 && /* @__PURE__ */ React.createElement(Text, { dimColor: true }, creditsUsage.toLocaleString(), " ", creditsUsage === 1 ? "credit" : "credits"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, model)));
108
114
  });
109
115
 
110
116
  // src/components/InputPrompt.tsx
111
- import React5, { useState as useState2, useMemo, useEffect as useEffect2, useRef as useRef2 } from "react";
117
+ import React5, { useState as useState3, useMemo, useEffect as useEffect3, useRef as useRef2 } from "react";
112
118
  import { Box as Box4, Text as Text5, useInput as useInput2 } from "ink";
113
119
 
114
120
  // src/components/CustomTextInput.tsx
@@ -321,65 +327,53 @@ function CommandAutocomplete({ commands, selectedIndex }) {
321
327
  // src/components/FileAutocomplete.tsx
322
328
  import React4 from "react";
323
329
  import { Box as Box3, Text as Text4 } from "ink";
330
+ import * as path2 from "path";
324
331
 
325
332
  // src/utils/fileSearch.ts
326
333
  import * as fs from "fs";
327
334
  import * as path from "path";
328
- import Fuse from "fuse.js";
329
- var DEFAULT_IGNORE_PATTERNS = [
330
- "node_modules",
331
- ".git",
332
- ".next",
333
- ".turbo",
334
- "dist",
335
- "build",
336
- ".cache",
337
- "coverage",
338
- ".DS_Store",
339
- ".env",
340
- ".env.local",
341
- ".env.secrets"
342
- ];
343
- function walkDirectory(basePath = process.cwd(), maxDepth = 5, ignorePatterns = DEFAULT_IGNORE_PATTERNS) {
344
- const results = [];
345
- const normalizedBase = path.resolve(basePath);
346
- function walk(currentPath, depth) {
347
- if (depth > maxDepth) return;
335
+ import { AsyncFzf } from "fzf";
336
+ import { fdir } from "fdir";
337
+ import ignore from "ignore";
338
+ function loadIgnoreRules(projectRoot) {
339
+ const ig = ignore();
340
+ ig.add(".git/");
341
+ const gitignorePath = path.join(projectRoot, ".gitignore");
342
+ if (fs.existsSync(gitignorePath)) {
348
343
  try {
349
- const entries = fs.readdirSync(currentPath, { withFileTypes: true });
350
- for (const entry of entries) {
351
- if (ignorePatterns.some((pattern) => entry.name === pattern || entry.name.startsWith(pattern))) {
352
- continue;
353
- }
354
- const fullPath = path.join(currentPath, entry.name);
355
- const relativePath = path.relative(normalizedBase, fullPath);
356
- if (entry.isDirectory()) {
357
- results.push({
358
- path: relativePath,
359
- isDirectory: true
360
- });
361
- walk(fullPath, depth + 1);
362
- } else if (entry.isFile()) {
363
- try {
364
- const stats = fs.statSync(fullPath);
365
- results.push({
366
- path: relativePath,
367
- isDirectory: false,
368
- size: stats.size
369
- });
370
- } catch {
371
- results.push({
372
- path: relativePath,
373
- isDirectory: false
374
- });
375
- }
376
- }
377
- }
344
+ const gitignoreContent = fs.readFileSync(gitignorePath, "utf-8");
345
+ ig.add(gitignoreContent);
378
346
  } catch {
379
347
  }
380
348
  }
381
- walk(normalizedBase, 0);
382
- return results;
349
+ return ig;
350
+ }
351
+ function crawlDirectory(projectRoot, maxDepth = 10, maxFiles = 2e4, ig) {
352
+ let fileCount = 0;
353
+ const crawler = new fdir().withRelativePaths().withDirs().withPathSeparator("/").exclude((dirPath) => {
354
+ const relativePath = path.posix.relative(projectRoot, dirPath);
355
+ if (!relativePath || relativePath === ".") {
356
+ return false;
357
+ }
358
+ const pathWithSlash = `${relativePath}/`;
359
+ return ig.ignores(pathWithSlash);
360
+ }).filter(() => {
361
+ if (fileCount >= maxFiles) {
362
+ return false;
363
+ }
364
+ fileCount++;
365
+ return true;
366
+ });
367
+ if (maxDepth !== void 0) {
368
+ crawler.withMaxDepth(maxDepth);
369
+ }
370
+ const paths = crawler.crawl(projectRoot).sync();
371
+ return paths.filter((p) => {
372
+ if (!p || p === ".") {
373
+ return true;
374
+ }
375
+ return !ig.ignores(p);
376
+ });
383
377
  }
384
378
  function formatFileSize(bytes) {
385
379
  if (bytes < 1024) return `${bytes} B`;
@@ -390,42 +384,112 @@ function formatFileSize(bytes) {
390
384
  var cachedFiles = null;
391
385
  var cacheTimestamp = 0;
392
386
  var CACHE_TTL_MS = 3e4;
393
- function getCachedFiles() {
387
+ function getCachedFiles(projectRoot = process.cwd()) {
394
388
  const now = Date.now();
395
389
  if (!cachedFiles || now - cacheTimestamp > CACHE_TTL_MS) {
396
- cachedFiles = walkDirectory();
390
+ const normalizedBase = path.resolve(projectRoot);
391
+ const ig = loadIgnoreRules(normalizedBase);
392
+ cachedFiles = crawlDirectory(normalizedBase, 10, 2e4, ig);
397
393
  cacheTimestamp = now;
398
394
  }
399
395
  return cachedFiles;
400
396
  }
401
- function searchFiles(query) {
402
- const files = getCachedFiles();
397
+ function warmFileCache() {
398
+ if (!cachedFiles || Date.now() - cacheTimestamp > CACHE_TTL_MS) {
399
+ setImmediate(() => {
400
+ getCachedFiles();
401
+ });
402
+ }
403
+ }
404
+ function listAbsoluteDirectory(absolutePath, filterQuery) {
405
+ try {
406
+ const normalizedPath = path.normalize(absolutePath);
407
+ if (!fs.existsSync(normalizedPath)) {
408
+ return [];
409
+ }
410
+ const stats = fs.statSync(normalizedPath);
411
+ if (!stats.isDirectory()) {
412
+ return [];
413
+ }
414
+ const entries = fs.readdirSync(normalizedPath, { withFileTypes: true });
415
+ let filteredEntries = entries;
416
+ if (filterQuery && filterQuery.length > 0) {
417
+ const lowerQuery = filterQuery.toLowerCase();
418
+ filteredEntries = entries.filter((entry) => entry.name.toLowerCase().includes(lowerQuery));
419
+ }
420
+ return filteredEntries.slice(0, 15).map((entry) => {
421
+ const fullPath = path.join(normalizedPath, entry.name);
422
+ const result = {
423
+ path: fullPath,
424
+ isDirectory: entry.isDirectory()
425
+ };
426
+ if (entry.isFile()) {
427
+ try {
428
+ const fileStats = fs.statSync(fullPath);
429
+ result.size = fileStats.size;
430
+ } catch {
431
+ }
432
+ }
433
+ return result;
434
+ });
435
+ } catch {
436
+ return [];
437
+ }
438
+ }
439
+ async function searchFiles(query, maxResults = 20) {
440
+ const projectRoot = process.cwd();
441
+ if (path.isAbsolute(query)) {
442
+ try {
443
+ const stats = fs.statSync(query);
444
+ if (stats.isDirectory()) {
445
+ return listAbsoluteDirectory(query);
446
+ }
447
+ } catch {
448
+ }
449
+ let dirToList;
450
+ let filterQuery;
451
+ if (query.endsWith("/") || query.endsWith(path.sep)) {
452
+ dirToList = query;
453
+ filterQuery = void 0;
454
+ } else {
455
+ dirToList = path.dirname(query);
456
+ filterQuery = path.basename(query);
457
+ }
458
+ return listAbsoluteDirectory(dirToList, filterQuery);
459
+ }
460
+ const files = getCachedFiles(projectRoot);
403
461
  if (!query || query.trim() === "") {
404
- return files.filter((f) => !f.path.includes(path.sep)).slice(0, 20);
405
- }
406
- const queryDir = path.dirname(query);
407
- let searchPool = files;
408
- if (queryDir && queryDir !== ".") {
409
- const normalizedDir = queryDir.replace(/\\/g, "/");
410
- searchPool = files.filter((f) => {
411
- const normalizedPath = f.path.replace(/\\/g, "/");
412
- return normalizedPath.startsWith(normalizedDir + "/") || normalizedPath === normalizedDir;
462
+ const rootFiles = files.filter((f) => !f.includes("/")).slice(0, maxResults);
463
+ return rootFiles.map((p) => {
464
+ const fullPath = path.join(projectRoot, p);
465
+ try {
466
+ const stats = fs.statSync(fullPath);
467
+ return {
468
+ path: p,
469
+ isDirectory: stats.isDirectory(),
470
+ size: stats.isFile() ? stats.size : void 0
471
+ };
472
+ } catch {
473
+ return { path: p, isDirectory: false };
474
+ }
413
475
  });
414
- if (searchPool.length === 0) {
415
- searchPool = files;
416
- }
417
- }
418
- const fuse = new Fuse(searchPool, {
419
- keys: [{ name: "path", weight: 1 }],
420
- threshold: 0.4,
421
- // Allow some fuzzy matching
422
- includeScore: true,
423
- minMatchCharLength: 1,
424
- // Use extended search for better path matching
425
- useExtendedSearch: false
476
+ }
477
+ const fzf = new AsyncFzf(files);
478
+ const results = await fzf.find(query);
479
+ return results.slice(0, maxResults).map((result) => {
480
+ const p = result.item;
481
+ const fullPath = path.join(projectRoot, p);
482
+ try {
483
+ const stats = fs.statSync(fullPath);
484
+ return {
485
+ path: p,
486
+ isDirectory: stats.isDirectory(),
487
+ size: stats.isFile() ? stats.size : void 0
488
+ };
489
+ } catch {
490
+ return { path: p, isDirectory: false };
491
+ }
426
492
  });
427
- const results = fuse.search(query);
428
- return results.slice(0, 20).map((result) => result.item);
429
493
  }
430
494
  function isPathWithinCwd(filePath) {
431
495
  const cwd = path.resolve(process.cwd());
@@ -509,10 +573,27 @@ var MAX_FILE_SIZE = 10 * 1024 * 1024;
509
573
  // src/components/FileAutocomplete.tsx
510
574
  function FileAutocomplete({ files, selectedIndex, query }) {
511
575
  if (files.length === 0) {
512
- return /* @__PURE__ */ React4.createElement(Box3, { marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "No matching files", query ? ` for "${query}"` : ""));
513
- }
514
- return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column", marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React4.createElement(Box3, { marginBottom: 1 }, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, files.length === 1 ? "1 match" : `${files.length} matches`, " - Use up/down to navigate, Tab to select")), files.map((file, index) => {
515
- const isSelected = index === selectedIndex;
576
+ if (path2.isAbsolute(query)) {
577
+ return /* @__PURE__ */ React4.createElement(Box3, { marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "No items in "), /* @__PURE__ */ React4.createElement(Text4, { color: "cyan" }, query), /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, " (or path does not exist)"));
578
+ }
579
+ return /* @__PURE__ */ React4.createElement(Box3, { marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "No matching files", query ? ` for "${query}"` : " in root directory"));
580
+ }
581
+ const VIEWPORT_SIZE = 6;
582
+ const totalFiles = files.length;
583
+ let startIndex = 0;
584
+ let endIndex = Math.min(VIEWPORT_SIZE, totalFiles);
585
+ if (totalFiles > VIEWPORT_SIZE) {
586
+ const halfViewport = Math.floor(VIEWPORT_SIZE / 2);
587
+ startIndex = Math.max(0, selectedIndex - halfViewport);
588
+ endIndex = Math.min(totalFiles, startIndex + VIEWPORT_SIZE);
589
+ if (endIndex === totalFiles) {
590
+ startIndex = Math.max(0, totalFiles - VIEWPORT_SIZE);
591
+ }
592
+ }
593
+ const visibleFiles = files.slice(startIndex, endIndex);
594
+ return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column", marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React4.createElement(Box3, { marginBottom: 1 }, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, totalFiles === 1 ? "1 match" : `${totalFiles} matches`, totalFiles > VIEWPORT_SIZE && ` (${selectedIndex + 1}/${totalFiles})`, " - Use up/down to navigate, Tab to select")), visibleFiles.map((file, viewportIndex) => {
595
+ const actualIndex = startIndex + viewportIndex;
596
+ const isSelected = actualIndex === selectedIndex;
516
597
  const icon = file.isDirectory ? "[folder]" : "[file] ";
517
598
  const sizeDisplay = file.isDirectory ? "" : file.size !== void 0 ? ` (${formatFileSize(file.size)})` : "";
518
599
  const pathDisplay = file.isDirectory ? `${file.path}/` : file.path;
@@ -521,7 +602,7 @@ function FileAutocomplete({ files, selectedIndex, query }) {
521
602
  }
522
603
 
523
604
  // src/utils/fuzzySearch.ts
524
- import Fuse2 from "fuse.js";
605
+ import Fuse from "fuse.js";
525
606
 
526
607
  // src/config/commands.ts
527
608
  var COMMANDS = [
@@ -698,13 +779,13 @@ function searchCommands(query, commands = COMMANDS) {
698
779
  if (!query || query.trim() === "") {
699
780
  return commands;
700
781
  }
701
- const fuse = new Fuse2(commands, fuseOptions);
782
+ const fuse = new Fuse(commands, fuseOptions);
702
783
  const results = fuse.search(query);
703
784
  return results.map((result) => result.item);
704
785
  }
705
786
 
706
787
  // src/utils/imageDetector.ts
707
- import { readFileSync, existsSync, statSync as statSync2 } from "fs";
788
+ import { readFileSync as readFileSync2, existsSync as existsSync2, statSync as statSync2 } from "fs";
708
789
  import { extname as extname2 } from "path";
709
790
  var ImageInputDetector = class {
710
791
  static {
@@ -845,14 +926,14 @@ var ImageInputDetector = class {
845
926
  let filepath = input.trim();
846
927
  filepath = filepath.replace(/^["']|["']$/g, "");
847
928
  filepath = filepath.replace(/\\(.)/g, "$1");
848
- if (!existsSync(filepath)) return null;
929
+ if (!existsSync2(filepath)) return null;
849
930
  const stats = statSync2(filepath);
850
931
  if (!stats.isFile()) return null;
851
932
  const ext = extname2(filepath).toLowerCase();
852
933
  if (!this.SUPPORTED_EXTENSIONS.includes(ext)) return null;
853
934
  if (stats.size > this.MAX_IMAGE_SIZE) return null;
854
935
  try {
855
- const data = readFileSync(filepath);
936
+ const data = readFileSync2(filepath);
856
937
  const format = ext.substring(1) === "jpeg" ? "jpg" : ext.substring(1);
857
938
  const filename = filepath.split("/").pop() || "image";
858
939
  return {
@@ -907,6 +988,21 @@ var ImageInputDetector = class {
907
988
  }
908
989
  };
909
990
 
991
+ // src/hooks/useDebounce.ts
992
+ import { useEffect as useEffect2, useState as useState2 } from "react";
993
+ function useDebounce(value, delay = 300) {
994
+ const [debouncedValue, setDebouncedValue] = useState2(value);
995
+ useEffect2(() => {
996
+ const timer = setTimeout(() => {
997
+ setDebouncedValue(value);
998
+ }, delay);
999
+ return () => {
1000
+ clearTimeout(timer);
1001
+ };
1002
+ }, [value, delay]);
1003
+ return debouncedValue;
1004
+ }
1005
+
910
1006
  // src/components/InputPrompt.tsx
911
1007
  function looksLikeFilePath(input) {
912
1008
  const trimmed = input.trim();
@@ -947,20 +1043,20 @@ function InputPrompt({
947
1043
  const value = useCliStore((state) => state.inputValue);
948
1044
  const setInputValue = useCliStore((state) => state.setInputValue);
949
1045
  const setValue = setInputValue;
950
- const [selectedIndex, setSelectedIndex] = useState2(0);
951
- const [historyIndex, setHistoryIndex] = useState2(-1);
952
- const [tempInput, setTempInput] = useState2("");
1046
+ const [selectedIndex, setSelectedIndex] = useState3(0);
1047
+ const [historyIndex, setHistoryIndex] = useState3(-1);
1048
+ const [tempInput, setTempInput] = useState3("");
953
1049
  const inputKey = useRef2(0);
954
- useEffect2(() => {
1050
+ useEffect3(() => {
955
1051
  if (prefillInput) {
956
1052
  setValue(prefillInput);
957
1053
  onPrefillConsumed?.();
958
1054
  }
959
1055
  }, [prefillInput, onPrefillConsumed]);
960
- const [fileAutocomplete, setFileAutocomplete] = useState2(null);
961
- const [fileSelectedIndex, setFileSelectedIndex] = useState2(0);
1056
+ const [fileAutocomplete, setFileAutocomplete] = useState3(null);
1057
+ const [fileSelectedIndex, setFileSelectedIndex] = useState3(0);
962
1058
  const isBashMode = value.startsWith("!");
963
- useEffect2(() => {
1059
+ useEffect3(() => {
964
1060
  onBashModeChange?.(isBashMode);
965
1061
  }, [isBashMode, onBashModeChange]);
966
1062
  const shouldShowCommandAutocomplete = value.startsWith("/") && !disabled && !fileAutocomplete?.active && !looksLikeFilePath(value);
@@ -969,14 +1065,32 @@ function InputPrompt({
969
1065
  if (!shouldShowCommandAutocomplete) return [];
970
1066
  return searchCommands(commandQuery, commands);
971
1067
  }, [shouldShowCommandAutocomplete, commandQuery, commands]);
972
- const filteredFiles = useMemo(() => {
973
- if (!fileAutocomplete?.active) return [];
974
- return searchFiles(fileAutocomplete.query);
975
- }, [fileAutocomplete?.active, fileAutocomplete?.query]);
976
- useEffect2(() => {
1068
+ const debouncedFileQuery = useDebounce(fileAutocomplete?.query ?? "", 200);
1069
+ const [filteredFiles, setFilteredFiles] = useState3([]);
1070
+ useEffect3(() => {
1071
+ if (!fileAutocomplete?.active) {
1072
+ setFilteredFiles([]);
1073
+ return;
1074
+ }
1075
+ let cancelled = false;
1076
+ searchFiles(debouncedFileQuery).then((results) => {
1077
+ if (!cancelled) {
1078
+ setFilteredFiles(results);
1079
+ }
1080
+ }).catch((error) => {
1081
+ if (!cancelled) {
1082
+ console.error("File search error:", error);
1083
+ setFilteredFiles([]);
1084
+ }
1085
+ });
1086
+ return () => {
1087
+ cancelled = true;
1088
+ };
1089
+ }, [fileAutocomplete?.active, debouncedFileQuery]);
1090
+ useEffect3(() => {
977
1091
  setSelectedIndex(0);
978
1092
  }, [filteredCommands]);
979
- useEffect2(() => {
1093
+ useEffect3(() => {
980
1094
  setFileSelectedIndex(0);
981
1095
  }, [filteredFiles]);
982
1096
  useInput2(
@@ -1214,13 +1328,13 @@ function BackgroundAgentStatus() {
1214
1328
  }
1215
1329
 
1216
1330
  // src/components/CompletedGroupNotification.tsx
1217
- import React9, { useEffect as useEffect3 } from "react";
1331
+ import React9, { useEffect as useEffect4 } from "react";
1218
1332
  import { Box as Box8, Text as Text8 } from "ink";
1219
1333
  var NOTIFICATION_DISPLAY_DURATION_MS = 3e3;
1220
1334
  function CompletedGroupNotification() {
1221
1335
  const notifications = useCliStore((state) => state.completedGroupNotifications);
1222
1336
  const clearNotifications = useCliStore((state) => state.clearCompletedGroupNotifications);
1223
- useEffect3(() => {
1337
+ useEffect4(() => {
1224
1338
  if (notifications.length > 0) {
1225
1339
  const timer = setTimeout(() => {
1226
1340
  clearNotifications();
@@ -1233,7 +1347,7 @@ function CompletedGroupNotification() {
1233
1347
  }
1234
1348
 
1235
1349
  // src/components/PermissionPrompt.tsx
1236
- import React10, { useState as useState3, useCallback } from "react";
1350
+ import React10, { useState as useState4, useCallback } from "react";
1237
1351
  import { Box as Box9, Text as Text9, useInput as useInput3 } from "ink";
1238
1352
  function renderDiffPreview(preview) {
1239
1353
  const lines = preview.split("\n");
@@ -1260,14 +1374,16 @@ function PermissionPrompt({
1260
1374
  }) {
1261
1375
  const items = canBeTrusted ? [
1262
1376
  { label: "\u2713 Allow once", value: "allow-once" },
1377
+ { label: "\u2713 Allow for this session", value: "allow-session" },
1263
1378
  { label: "\u2713 Always allow (trust this tool)", value: "allow-always" },
1264
1379
  { label: "\u2717 Deny", value: "deny" }
1265
1380
  ] : [
1266
1381
  { label: "\u2713 Allow once", value: "allow-once" },
1382
+ { label: "\u2713 Allow for this session", value: "allow-session" },
1267
1383
  { label: "\u2717 Deny", value: "deny" }
1268
1384
  ];
1269
- const [selectedIndex, setSelectedIndex] = useState3(0);
1270
- const [responded, setResponded] = useState3(false);
1385
+ const [selectedIndex, setSelectedIndex] = useState4(0);
1386
+ const [responded, setResponded] = useState4(false);
1271
1387
  const handleSelect = useCallback(() => {
1272
1388
  if (responded) return;
1273
1389
  setResponded(true);
@@ -1315,7 +1431,7 @@ function PermissionPrompt({
1315
1431
  }
1316
1432
 
1317
1433
  // src/components/ConfigEditor.tsx
1318
- import React11, { useState as useState4, useMemo as useMemo3 } from "react";
1434
+ import React11, { useState as useState5, useMemo as useMemo3 } from "react";
1319
1435
  import { Box as Box10, Text as Text10, useInput as useInput4 } from "ink";
1320
1436
  var MAX_ITERATIONS_OPTIONS = [
1321
1437
  { label: "10", value: 10 },
@@ -1460,10 +1576,10 @@ function buildConfigItems(availableModels) {
1460
1576
  return items;
1461
1577
  }
1462
1578
  function ConfigEditor({ config, availableModels, onSave, onClose }) {
1463
- const [selectedIndex, setSelectedIndex] = useState4(0);
1464
- const [editedConfig, setEditedConfig] = useState4(config);
1465
- const [saveError, setSaveError] = useState4(null);
1466
- const [isSaving, setIsSaving] = useState4(false);
1579
+ const [selectedIndex, setSelectedIndex] = useState5(0);
1580
+ const [editedConfig, setEditedConfig] = useState5(config);
1581
+ const [saveError, setSaveError] = useState5(null);
1582
+ const [isSaving, setIsSaving] = useState5(false);
1467
1583
  const configItems = useMemo3(() => buildConfigItems(availableModels), [availableModels]);
1468
1584
  const hasChanges = useMemo3(() => {
1469
1585
  return JSON.stringify(config.preferences) !== JSON.stringify(editedConfig.preferences) || config.defaultModel !== editedConfig.defaultModel;
@@ -1744,12 +1860,12 @@ var MessageItem = React14.memo(function MessageItem2({ message }) {
1744
1860
  return /* @__PURE__ */ React14.createElement(Box13, { key: idx, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React14.createElement(Text13, { color: "yellow" }, "\u{1F527} ", toolName), toolInput && /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, ` Input: ${truncateValue(toolInput, 100)}`), result && /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, ` Result: ${truncateValue(result, 200)}`));
1745
1861
  }
1746
1862
  return null;
1747
- }).filter(Boolean)), !isUser && message.content !== "..." && /* @__PURE__ */ React14.createElement(Box13, { paddingLeft: 2, marginBottom: 1 }, message.metadata?.permissionDenied ? /* @__PURE__ */ React14.createElement(Text13, { color: "yellow" }, "\u26A0\uFE0F ", message.content) : /* @__PURE__ */ React14.createElement(MarkdownRenderer, { content: message.content })), message.metadata?.tokenUsage && (!message.metadata.steps || message.metadata.steps.length === 0) && /* @__PURE__ */ React14.createElement(Box13, { paddingLeft: 2 }, /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, `${message.metadata.tokenUsage.total} tokens`)));
1863
+ }).filter(Boolean)), !isUser && message.content !== "..." && /* @__PURE__ */ React14.createElement(Box13, { paddingLeft: 2, marginBottom: 1 }, message.metadata?.permissionDenied ? /* @__PURE__ */ React14.createElement(Text13, { color: "yellow" }, "\u26A0\uFE0F ", message.content) : /* @__PURE__ */ React14.createElement(MarkdownRenderer, { content: message.content })), !isUser && message.content !== "..." && message.metadata && /* @__PURE__ */ React14.createElement(Box13, { paddingLeft: 2, marginBottom: 1 }, (message.metadata.tokenUsage?.total || message.metadata.creditsUsed) && /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "(", message.metadata.tokenUsage?.total ? `${message.metadata.tokenUsage.total.toLocaleString()} tokens` : "", message.metadata.creditsUsed && message.metadata.creditsUsed > 0 ? (message.metadata.tokenUsage?.total ? " \u2022 " : "") + `used ${message.metadata.creditsUsed.toLocaleString()} ${message.metadata.creditsUsed === 1 ? "credit" : "credits"}` : "", ")")));
1748
1864
  });
1749
1865
 
1750
1866
  // src/utils/processFileReferences.ts
1751
1867
  import * as fs2 from "fs";
1752
- import * as path2 from "path";
1868
+ import * as path3 from "path";
1753
1869
 
1754
1870
  // src/utils/constants.ts
1755
1871
  var NAME_SUFFIXES = ["jr", "sr", "ii", "iii", "iv", "v", "phd", "md", "esq"];
@@ -1760,7 +1876,7 @@ function isNameSuffix(value) {
1760
1876
  // src/utils/processFileReferences.ts
1761
1877
  var FILE_REFERENCE_REGEX = /(?:^|\s)@([^\s@]+)/g;
1762
1878
  function looksLikeFilePath2(ref) {
1763
- if (ref.includes("/") || ref.includes(path2.sep)) {
1879
+ if (ref.includes("/") || ref.includes(path3.sep)) {
1764
1880
  return true;
1765
1881
  }
1766
1882
  const extensionMatch = /\.(\w+)$/.exec(ref);
@@ -1790,9 +1906,13 @@ function extractFileReferences(message) {
1790
1906
  }
1791
1907
  function readFileContents(filePath) {
1792
1908
  const cwd = process.cwd();
1793
- const absolutePath = path2.resolve(cwd, filePath);
1794
- if (!isPathWithinCwd(filePath)) {
1795
- return { error: `Security: Path "${filePath}" is outside the current working directory` };
1909
+ const isAbsolutePath = path3.isAbsolute(filePath);
1910
+ if (filePath.includes("..")) {
1911
+ return { error: `Security: Path traversal detected in "${filePath}"` };
1912
+ }
1913
+ const absolutePath = isAbsolutePath ? path3.normalize(filePath) : path3.resolve(cwd, filePath);
1914
+ if (!isAbsolutePath && !isPathWithinCwd(filePath)) {
1915
+ return { error: `Security: Relative path "${filePath}" escapes the current working directory` };
1796
1916
  }
1797
1917
  if (!fs2.existsSync(absolutePath)) {
1798
1918
  return { error: `File not found: "${filePath}"` };
@@ -1883,6 +2003,7 @@ function App({
1883
2003
  const sessionName = useCliStore((state) => state.session?.name || "New Session");
1884
2004
  const currentModel = useCliStore((state) => state.session?.model || "claude-sonnet-4-5-20250929");
1885
2005
  const totalTokens = useCliStore((state) => state.session?.metadata.totalTokens || 0);
2006
+ const totalCredits = useCliStore((state) => state.session?.metadata.totalCredits);
1886
2007
  const isThinking = useCliStore((state) => state.isThinking);
1887
2008
  const permissionPrompt = useCliStore((state) => state.permissionPrompt);
1888
2009
  const showConfigEditor = useCliStore((state) => state.showConfigEditor);
@@ -1893,7 +2014,7 @@ function App({
1893
2014
  const setIsThinking = useCliStore((state) => state.setIsThinking);
1894
2015
  const pendingBackgroundTrigger = useCliStore((state) => state.pendingBackgroundTrigger);
1895
2016
  const setPendingBackgroundTrigger = useCliStore((state) => state.setPendingBackgroundTrigger);
1896
- useEffect4(() => {
2017
+ useEffect5(() => {
1897
2018
  if (pendingBackgroundTrigger && !isThinking && onBackgroundCompletion) {
1898
2019
  setPendingBackgroundTrigger(false);
1899
2020
  onBackgroundCompletion();
@@ -1905,7 +2026,7 @@ function App({
1905
2026
  toggleAutoAcceptEdits();
1906
2027
  }
1907
2028
  });
1908
- const [isBashMode, setIsBashMode] = useState5(false);
2029
+ const [isBashMode, setIsBashMode] = useState6(false);
1909
2030
  const handleSubmit = React15.useCallback(
1910
2031
  async (input) => {
1911
2032
  const trimmed = input.trim();
@@ -1968,7 +2089,15 @@ ${errorBlock}`;
1968
2089
  onPrefillConsumed,
1969
2090
  onBashModeChange: setIsBashMode
1970
2091
  }
1971
- )), /* @__PURE__ */ React15.createElement(StatusBar, { sessionName, model: currentModel, tokenUsage: totalTokens })));
2092
+ )), /* @__PURE__ */ React15.createElement(
2093
+ StatusBar,
2094
+ {
2095
+ sessionName,
2096
+ model: currentModel,
2097
+ tokenUsage: totalTokens,
2098
+ creditsUsage: totalCredits
2099
+ }
2100
+ )));
1972
2101
  }
1973
2102
 
1974
2103
  // src/components/MessageList.tsx
@@ -2044,12 +2173,12 @@ function TrustLocationSelector({ inProject, onSelect, onCancel }) {
2044
2173
  }
2045
2174
 
2046
2175
  // src/components/RewindSelector.tsx
2047
- import React18, { useState as useState6 } from "react";
2176
+ import React18, { useState as useState7 } from "react";
2048
2177
  import { Box as Box17, Text as Text17, useInput as useInput7 } from "ink";
2049
2178
  import SelectInput2 from "ink-select-input";
2050
2179
  function RewindSelector({ messages, onSelect, onCancel }) {
2051
- const [step, setStep] = useState6("selection");
2052
- const [selectedMessageIndex, setSelectedMessageIndex] = useState6(null);
2180
+ const [step, setStep] = useState7("selection");
2181
+ const [selectedMessageIndex, setSelectedMessageIndex] = useState7(null);
2053
2182
  useInput7((input, key) => {
2054
2183
  if (key.escape) {
2055
2184
  if (step === "confirmation") {
@@ -2113,12 +2242,12 @@ function RewindSelector({ messages, onSelect, onCancel }) {
2113
2242
  }
2114
2243
 
2115
2244
  // src/components/SessionSelector.tsx
2116
- import React19, { useState as useState7 } from "react";
2245
+ import React19, { useState as useState8 } from "react";
2117
2246
  import { Box as Box18, Text as Text18, useInput as useInput8 } from "ink";
2118
2247
  import SelectInput3 from "ink-select-input";
2119
2248
  function SessionSelector({ sessions, currentSession, onSelect, onCancel }) {
2120
- const [step, setStep] = useState7("selection");
2121
- const [selectedSession, setSelectedSession] = useState7(null);
2249
+ const [step, setStep] = useState8("selection");
2250
+ const [selectedSession, setSelectedSession] = useState8(null);
2122
2251
  useInput8((_input, key) => {
2123
2252
  if (key.escape) {
2124
2253
  if (step === "confirmation") {
@@ -2193,7 +2322,7 @@ function SessionSelector({ sessions, currentSession, onSelect, onCancel }) {
2193
2322
  }
2194
2323
 
2195
2324
  // src/components/LoginFlow.tsx
2196
- import React20, { useState as useState8, useEffect as useEffect5 } from "react";
2325
+ import React20, { useState as useState9, useEffect as useEffect6 } from "react";
2197
2326
  import { Box as Box19, Text as Text19 } from "ink";
2198
2327
  import Spinner3 from "ink-spinner";
2199
2328
  import jwt from "jsonwebtoken";
@@ -2310,11 +2439,11 @@ var OAuthClient = class {
2310
2439
 
2311
2440
  // src/components/LoginFlow.tsx
2312
2441
  function LoginFlow({ apiUrl = "http://localhost:3000", configStore, onSuccess, onError }) {
2313
- const [status, setStatus] = useState8("initiating");
2314
- const [deviceFlow, setDeviceFlow] = useState8(null);
2315
- const [statusMessage, setStatusMessage] = useState8("Initiating device authorization...");
2316
- const [error, setError] = useState8(null);
2317
- useEffect5(() => {
2442
+ const [status, setStatus] = useState9("initiating");
2443
+ const [deviceFlow, setDeviceFlow] = useState9(null);
2444
+ const [statusMessage, setStatusMessage] = useState9("Initiating device authorization...");
2445
+ const [error, setError] = useState9(null);
2446
+ useEffect6(() => {
2318
2447
  const runLoginFlow = async () => {
2319
2448
  const oauth = new OAuthClient(apiUrl);
2320
2449
  try {
@@ -2349,7 +2478,7 @@ function LoginFlow({ apiUrl = "http://localhost:3000", configStore, onSuccess, o
2349
2478
  };
2350
2479
  runLoginFlow();
2351
2480
  }, [apiUrl, configStore, onSuccess, onError]);
2352
- useEffect5(() => {
2481
+ useEffect6(() => {
2353
2482
  if (deviceFlow && status === "waiting") {
2354
2483
  open(deviceFlow.verification_uri_complete).catch((err) => {
2355
2484
  console.error("Failed to auto-open browser:", err);
@@ -2370,12 +2499,12 @@ function LoginFlow({ apiUrl = "http://localhost:3000", configStore, onSuccess, o
2370
2499
 
2371
2500
  // src/storage/SessionStore.ts
2372
2501
  import { promises as fs3 } from "fs";
2373
- import path3 from "path";
2502
+ import path4 from "path";
2374
2503
  import { homedir } from "os";
2375
2504
  import { v4 as uuidv4 } from "uuid";
2376
2505
  var SessionStore = class {
2377
2506
  constructor(basePath) {
2378
- this.basePath = basePath || path3.join(homedir(), ".bike4mind", "sessions");
2507
+ this.basePath = basePath || path4.join(homedir(), ".bike4mind", "sessions");
2379
2508
  }
2380
2509
  /**
2381
2510
  * Initialize storage directory
@@ -2396,7 +2525,7 @@ var SessionStore = class {
2396
2525
  throw new Error("Cannot save session with no messages");
2397
2526
  }
2398
2527
  await this.init();
2399
- const filePath = path3.join(this.basePath, `${session.id}.json`);
2528
+ const filePath = path4.join(this.basePath, `${session.id}.json`);
2400
2529
  try {
2401
2530
  await fs3.writeFile(filePath, JSON.stringify(session, null, 2), "utf-8");
2402
2531
  } catch (error) {
@@ -2408,7 +2537,7 @@ var SessionStore = class {
2408
2537
  * Load a session from disk by ID
2409
2538
  */
2410
2539
  async load(id) {
2411
- const filePath = path3.join(this.basePath, `${id}.json`);
2540
+ const filePath = path4.join(this.basePath, `${id}.json`);
2412
2541
  try {
2413
2542
  const data = await fs3.readFile(filePath, "utf-8");
2414
2543
  const session = JSON.parse(data);
@@ -2446,7 +2575,7 @@ var SessionStore = class {
2446
2575
  const jsonFiles = files.filter((f) => f.endsWith(".json"));
2447
2576
  const sessionsWithFiles = await Promise.all(
2448
2577
  jsonFiles.map(async (file) => {
2449
- const filePath = path3.join(this.basePath, file);
2578
+ const filePath = path4.join(this.basePath, file);
2450
2579
  const data = await fs3.readFile(filePath, "utf-8");
2451
2580
  const session = JSON.parse(data);
2452
2581
  session.messages = session.messages.map((msg) => {
@@ -2481,7 +2610,7 @@ var SessionStore = class {
2481
2610
  * Delete a session
2482
2611
  */
2483
2612
  async delete(id) {
2484
- const filePath = path3.join(this.basePath, `${id}.json`);
2613
+ const filePath = path4.join(this.basePath, `${id}.json`);
2485
2614
  try {
2486
2615
  await fs3.unlink(filePath);
2487
2616
  return true;
@@ -2520,19 +2649,19 @@ var SessionStore = class {
2520
2649
 
2521
2650
  // src/storage/CommandHistoryStore.ts
2522
2651
  import { promises as fs4 } from "fs";
2523
- import path4 from "path";
2652
+ import path5 from "path";
2524
2653
  import { homedir as homedir2 } from "os";
2525
2654
  var MAX_HISTORY_ENTRIES = 1e3;
2526
2655
  var CommandHistoryStore = class {
2527
2656
  constructor(historyPath) {
2528
2657
  this.history = null;
2529
- this.historyPath = historyPath || path4.join(homedir2(), ".bike4mind", "history.jsonl");
2658
+ this.historyPath = historyPath || path5.join(homedir2(), ".bike4mind", "history.jsonl");
2530
2659
  }
2531
2660
  /**
2532
2661
  * Initialize history directory
2533
2662
  */
2534
2663
  async init() {
2535
- const dir = path4.dirname(this.historyPath);
2664
+ const dir = path5.dirname(this.historyPath);
2536
2665
  try {
2537
2666
  await fs4.mkdir(dir, { recursive: true });
2538
2667
  } catch (error) {
@@ -2635,7 +2764,7 @@ var CommandHistoryStore = class {
2635
2764
 
2636
2765
  // src/storage/CustomCommandStore.ts
2637
2766
  import fs5 from "fs/promises";
2638
- import path5 from "path";
2767
+ import path6 from "path";
2639
2768
  import os from "os";
2640
2769
 
2641
2770
  // src/utils/commandParser.ts
@@ -2798,14 +2927,14 @@ var CustomCommandStore = class {
2798
2927
  const home = os.homedir();
2799
2928
  const root = projectRoot || process.cwd();
2800
2929
  this.globalCommandsDirs = [
2801
- path5.join(home, ".bike4mind", "commands"),
2802
- path5.join(home, ".claude", "commands"),
2803
- path5.join(home, ".claude", "skills")
2930
+ path6.join(home, ".bike4mind", "commands"),
2931
+ path6.join(home, ".claude", "commands"),
2932
+ path6.join(home, ".claude", "skills")
2804
2933
  ];
2805
2934
  this.projectCommandsDirs = [
2806
- path5.join(root, ".bike4mind", "commands"),
2807
- path5.join(root, ".claude", "commands"),
2808
- path5.join(root, ".claude", "skills")
2935
+ path6.join(root, ".bike4mind", "commands"),
2936
+ path6.join(root, ".claude", "commands"),
2937
+ path6.join(root, ".claude", "skills")
2809
2938
  ];
2810
2939
  }
2811
2940
  /**
@@ -2865,7 +2994,7 @@ var CustomCommandStore = class {
2865
2994
  try {
2866
2995
  const entries = await fs5.readdir(directory, { withFileTypes: true });
2867
2996
  for (const entry of entries) {
2868
- const fullPath = path5.join(directory, entry.name);
2997
+ const fullPath = path6.join(directory, entry.name);
2869
2998
  if (entry.isDirectory()) {
2870
2999
  const subFiles = await this.findCommandFiles(fullPath);
2871
3000
  files.push(...subFiles);
@@ -2885,7 +3014,7 @@ var CustomCommandStore = class {
2885
3014
  * @param source - Source identifier ('global' or 'project')
2886
3015
  */
2887
3016
  async loadCommandFile(filePath, source) {
2888
- const filename = path5.basename(filePath);
3017
+ const filename = path6.basename(filePath);
2889
3018
  const isSkillFile = filename.toLowerCase() === "skill.md";
2890
3019
  const commandName = isSkillFile ? this.extractSkillName(filePath) : extractCommandName(filename);
2891
3020
  if (!commandName) {
@@ -2904,7 +3033,7 @@ var CustomCommandStore = class {
2904
3033
  * @returns Skill name or null if invalid
2905
3034
  */
2906
3035
  extractSkillName(filePath) {
2907
- const parentDir = path5.basename(path5.dirname(filePath));
3036
+ const parentDir = path6.basename(path6.dirname(filePath));
2908
3037
  return parentDir && parentDir !== "skills" ? parentDir : null;
2909
3038
  }
2910
3039
  /**
@@ -2966,7 +3095,7 @@ var CustomCommandStore = class {
2966
3095
  */
2967
3096
  async createCommandFile(name, isGlobal = false) {
2968
3097
  const targetDir = isGlobal ? this.globalCommandsDirs[0] : this.projectCommandsDirs[0];
2969
- const filePath = path5.join(targetDir, `${name}.md`);
3098
+ const filePath = path6.join(targetDir, `${name}.md`);
2970
3099
  const fileExists = await fs5.access(filePath).then(
2971
3100
  () => true,
2972
3101
  () => false
@@ -3122,6 +3251,7 @@ var ReActAgent = class extends EventEmitter {
3122
3251
  super();
3123
3252
  this.steps = [];
3124
3253
  this.totalTokens = 0;
3254
+ this.totalCredits = 0;
3125
3255
  this.toolCallCount = 0;
3126
3256
  this.observationQueue = [];
3127
3257
  this.context = {
@@ -3141,6 +3271,7 @@ var ReActAgent = class extends EventEmitter {
3141
3271
  async run(query, options = {}) {
3142
3272
  this.steps = [];
3143
3273
  this.totalTokens = 0;
3274
+ this.totalCredits = 0;
3144
3275
  this.toolCallCount = 0;
3145
3276
  const maxIterations = options.maxIterations ?? this.context.maxIterations ?? 5;
3146
3277
  const temperature = options.temperature ?? this.context.temperature ?? 0.7;
@@ -3177,6 +3308,7 @@ ${options.context}` : this.getSystemPrompt()
3177
3308
  steps: this.steps,
3178
3309
  completionInfo: {
3179
3310
  totalTokens: this.totalTokens,
3311
+ totalCredits: this.totalCredits > 0 ? this.totalCredits : void 0,
3180
3312
  iterations,
3181
3313
  toolCalls: this.toolCallCount,
3182
3314
  reachedMaxIterations: false
@@ -3212,6 +3344,9 @@ ${options.context}` : this.getSystemPrompt()
3212
3344
  const inputTokens = completionInfo.inputTokens || 0;
3213
3345
  const outputTokens = completionInfo.outputTokens || 0;
3214
3346
  this.totalTokens += inputTokens + outputTokens;
3347
+ if (completionInfo.creditsUsed) {
3348
+ this.totalCredits += completionInfo.creditsUsed;
3349
+ }
3215
3350
  if (currentText.trim() && completionInfo.toolsUsed?.length) {
3216
3351
  const thoughtStep = {
3217
3352
  type: "thought",
@@ -3325,6 +3460,7 @@ ${options.context}` : this.getSystemPrompt()
3325
3460
  steps: this.steps,
3326
3461
  completionInfo: {
3327
3462
  totalTokens: this.totalTokens,
3463
+ totalCredits: this.totalCredits > 0 ? this.totalCredits : void 0,
3328
3464
  iterations,
3329
3465
  toolCalls: this.toolCallCount,
3330
3466
  reachedMaxIterations
@@ -3342,6 +3478,7 @@ ${options.context}` : this.getSystemPrompt()
3342
3478
  steps: this.steps,
3343
3479
  completionInfo: {
3344
3480
  totalTokens: this.totalTokens,
3481
+ totalCredits: this.totalCredits > 0 ? this.totalCredits : void 0,
3345
3482
  iterations,
3346
3483
  toolCalls: this.toolCallCount,
3347
3484
  reachedMaxIterations: false
@@ -3587,6 +3724,50 @@ function isReadOnlyTool(toolName, customCategories) {
3587
3724
  return getToolCategory(toolName, customCategories) !== "prompt_always";
3588
3725
  }
3589
3726
 
3727
+ // src/core/skillsPrompt.ts
3728
+ function getSkillDisplayName(cmd) {
3729
+ return cmd.displayName || cmd.name;
3730
+ }
3731
+ function formatSkillEntry(cmd) {
3732
+ const displayName = getSkillDisplayName(cmd);
3733
+ const argHint = cmd.argumentHint ? ` ${cmd.argumentHint}` : "";
3734
+ const nameDisplay = cmd.displayName && cmd.displayName !== cmd.name ? `**${displayName}** (\`${cmd.name}\`)` : `**${cmd.name}**`;
3735
+ return `- ${nameDisplay}${argHint}: ${cmd.description}
3736
+ `;
3737
+ }
3738
+ function formatSkillGroup(heading, commands) {
3739
+ if (commands.length === 0) {
3740
+ return "";
3741
+ }
3742
+ return `
3743
+ ### ${heading}
3744
+ ${commands.map(formatSkillEntry).join("")}`;
3745
+ }
3746
+ function filterAIVisibleSkills(commands) {
3747
+ return commands.filter((cmd) => !cmd.disableModelInvocation);
3748
+ }
3749
+ function filterSkillsByAllowedList(commands, allowedSkills) {
3750
+ if (!allowedSkills || allowedSkills.length === 0) {
3751
+ return commands;
3752
+ }
3753
+ return commands.filter((cmd) => allowedSkills.includes(cmd.name));
3754
+ }
3755
+ function buildSkillsPromptSection(commands, allowedSkills) {
3756
+ const filteredByAllowed = filterSkillsByAllowedList(commands, allowedSkills);
3757
+ const visibleCommands = filterAIVisibleSkills(filteredByAllowed);
3758
+ if (visibleCommands.length === 0) {
3759
+ return "";
3760
+ }
3761
+ const projectSkills = visibleCommands.filter((c) => c.source === "project");
3762
+ const globalSkills = visibleCommands.filter((c) => c.source === "global");
3763
+ return `
3764
+
3765
+ ## Available Skills
3766
+
3767
+ Use the \`skill\` tool to invoke these. Example: skill({ skill: "commit" })
3768
+ ` + formatSkillGroup("Project Skills", projectSkills) + formatSkillGroup("Global Skills", globalSkills);
3769
+ }
3770
+
3590
3771
  // src/core/prompts.ts
3591
3772
  var TOOL_GREP_SEARCH = "grep_search";
3592
3773
  var TOOL_GLOB_FILES = "glob_files";
@@ -3597,7 +3778,38 @@ var TOOL_BASH_EXECUTE = "bash_execute";
3597
3778
  var TOOL_SUBAGENT_DELEGATE = "subagent_delegate";
3598
3779
  var TOOL_WRITE_TODOS = "write_todos";
3599
3780
  var EXPLORE_SUBAGENT_TYPE = "explore";
3600
- function buildCoreSystemPrompt(contextSection = "") {
3781
+ function buildCoreSystemPrompt(contextSection, config) {
3782
+ let projectContextSection = "";
3783
+ let agentDirectoryContext = "";
3784
+ let skillsSection = "";
3785
+ if (typeof contextSection === "string") {
3786
+ projectContextSection = contextSection;
3787
+ if (config) {
3788
+ if (config.enableSkillTool !== false && config.customCommands && config.customCommands.length > 0) {
3789
+ skillsSection = buildSkillsPromptSection(config.customCommands);
3790
+ }
3791
+ if (config.agentStore) {
3792
+ agentDirectoryContext = config.agentStore.getDirectoryContext();
3793
+ }
3794
+ }
3795
+ } else if (contextSection && typeof contextSection === "object") {
3796
+ config = contextSection;
3797
+ if (config.contextContent) {
3798
+ projectContextSection = `
3799
+
3800
+ ## Project Context
3801
+
3802
+ Follow these project-specific instructions:
3803
+
3804
+ ${config.contextContent}`;
3805
+ }
3806
+ if (config.enableSkillTool !== false && config.customCommands && config.customCommands.length > 0) {
3807
+ skillsSection = buildSkillsPromptSection(config.customCommands);
3808
+ }
3809
+ if (config.agentStore) {
3810
+ agentDirectoryContext = config.agentStore.getDirectoryContext();
3811
+ }
3812
+ }
3601
3813
  return `You are an autonomous AI assistant with access to tools. Your job is to help users by taking action and solving problems proactively.
3602
3814
 
3603
3815
  CORE BEHAVIOR:
@@ -3627,12 +3839,8 @@ When requested to perform tasks like fixing bugs, adding features, refactoring,
3627
3839
 
3628
3840
  SUBAGENT DELEGATION:
3629
3841
  - You have access to specialized subagents via the ${TOOL_SUBAGENT_DELEGATE} tool
3630
- - Use subagents for focused exploration, planning, or review tasks:
3631
- * explore: Fast read-only codebase search (e.g., "find all auth files", "locate API endpoints")
3632
- * plan: Break down complex tasks into actionable steps
3633
- * review: Analyze code quality and identify issues
3842
+ - ${agentDirectoryContext ? `${agentDirectoryContext}` : ""}
3634
3843
  - Subagents keep the main conversation clean and run faster with optimized models
3635
- - Delegate when you need to search extensively or analyze code structure
3636
3844
 
3637
3845
  CODE SEARCH BEST PRACTICES:
3638
3846
  When searching code, follow this hierarchy for speed and efficiency:
@@ -3689,7 +3897,7 @@ EXAMPLES:
3689
3897
  - "what packages installed?" \u2192 ${TOOL_GLOB_FILES} "**/package.json" \u2192 ${TOOL_FILE_READ}
3690
3898
  - "find all components using React hooks" \u2192 ${TOOL_SUBAGENT_DELEGATE}(task="find all components using React hooks", type="explore")
3691
3899
 
3692
- Remember: Use context from previous messages to understand follow-up questions.${contextSection}`;
3900
+ Remember: Use context from previous messages to understand follow-up questions.${projectContextSection}${skillsSection}`;
3693
3901
  }
3694
3902
 
3695
3903
  // ../../b4m-core/packages/services/dist/src/referService/generateCodes.js
@@ -3865,7 +4073,8 @@ var recalculateUserStorageSchema = z14.object({
3865
4073
  // ../../b4m-core/packages/services/dist/src/userService/listRecentActivities.js
3866
4074
  import { z as z15 } from "zod";
3867
4075
  var listRecentActivitiesSchema = z15.object({
3868
- coverage: z15.enum(["all", "important"]).default("important")
4076
+ coverage: z15.enum(["all", "important"]).default("important"),
4077
+ userId: z15.string().optional()
3869
4078
  });
3870
4079
  var IMPORTANT_COUNTER_NAMES = [
3871
4080
  SessionEvents.CREATE_SESSION,
@@ -5509,7 +5718,19 @@ import { z as z129 } from "zod";
5509
5718
  var createArtifactSchema = z129.object({
5510
5719
  id: z129.string().optional(),
5511
5720
  // Allow custom ID for AI-generated artifacts
5512
- type: z129.enum(["mermaid", "recharts", "python", "react", "html", "svg", "code", "quest", "file", "questmaster"]),
5721
+ type: z129.enum([
5722
+ "mermaid",
5723
+ "recharts",
5724
+ "python",
5725
+ "react",
5726
+ "html",
5727
+ "svg",
5728
+ "code",
5729
+ "quest",
5730
+ "file",
5731
+ "questmaster",
5732
+ "lattice"
5733
+ ]),
5513
5734
  title: z129.string().min(1).max(255),
5514
5735
  description: z129.string().max(1e3).optional(),
5515
5736
  content: z129.string().min(1),
@@ -5818,8 +6039,8 @@ async function processAndStoreImages(images, context) {
5818
6039
  const buffer = await downloadImage(image);
5819
6040
  const fileType = await fileTypeFromBuffer2(buffer);
5820
6041
  const filename = `${uuidv45()}.${fileType?.ext}`;
5821
- const path18 = await context.imageGenerateStorage.upload(buffer, filename, {});
5822
- return path18;
6042
+ const path19 = await context.imageGenerateStorage.upload(buffer, filename, {});
6043
+ return path19;
5823
6044
  }));
5824
6045
  }
5825
6046
  async function updateQuestAndReturnMarkdown(storedImageUrls, context) {
@@ -7145,8 +7366,8 @@ async function processAndStoreImage(imageUrl, context) {
7145
7366
  const buffer = await downloadImage2(imageUrl);
7146
7367
  const fileType = await fileTypeFromBuffer3(buffer);
7147
7368
  const filename = `${uuidv46()}.${fileType?.ext}`;
7148
- const path18 = await context.imageGenerateStorage.upload(buffer, filename, {});
7149
- return path18;
7369
+ const path19 = await context.imageGenerateStorage.upload(buffer, filename, {});
7370
+ return path19;
7150
7371
  }
7151
7372
  async function generateFullMask(imageBuffer) {
7152
7373
  const sharp = (await import("sharp")).default;
@@ -9088,18 +9309,18 @@ var planetVisibilityTool = {
9088
9309
 
9089
9310
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/fileRead/index.js
9090
9311
  import { promises as fs7 } from "fs";
9091
- import { existsSync as existsSync3, statSync as statSync4 } from "fs";
9092
- import path6 from "path";
9312
+ import { existsSync as existsSync4, statSync as statSync4 } from "fs";
9313
+ import path7 from "path";
9093
9314
  var MAX_FILE_SIZE2 = 10 * 1024 * 1024;
9094
9315
  async function readFileContent(params) {
9095
9316
  const { path: filePath, encoding = "utf-8", offset = 0, limit } = params;
9096
- const normalizedPath = path6.normalize(filePath);
9097
- const resolvedPath = path6.resolve(process.cwd(), normalizedPath);
9098
- const cwd = path6.resolve(process.cwd());
9317
+ const normalizedPath = path7.normalize(filePath);
9318
+ const resolvedPath = path7.resolve(process.cwd(), normalizedPath);
9319
+ const cwd = path7.resolve(process.cwd());
9099
9320
  if (!resolvedPath.startsWith(cwd)) {
9100
9321
  throw new Error(`Access denied: Cannot read files outside of current working directory`);
9101
9322
  }
9102
- if (!existsSync3(resolvedPath)) {
9323
+ if (!existsSync4(resolvedPath)) {
9103
9324
  throw new Error(`File not found: ${filePath}`);
9104
9325
  }
9105
9326
  const stats = statSync4(resolvedPath);
@@ -9168,7 +9389,7 @@ var fileReadTool = {
9168
9389
  context.logger.info("\u{1F4C4} FileRead: Reading file", { path: params.path });
9169
9390
  try {
9170
9391
  const content = await readFileContent(params);
9171
- const stats = statSync4(path6.resolve(process.cwd(), path6.normalize(params.path)));
9392
+ const stats = statSync4(path7.resolve(process.cwd(), path7.normalize(params.path)));
9172
9393
  context.logger.info("\u2705 FileRead: Success", {
9173
9394
  path: params.path,
9174
9395
  size: stats.size,
@@ -9213,20 +9434,20 @@ var fileReadTool = {
9213
9434
 
9214
9435
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/createFile/index.js
9215
9436
  import { promises as fs8 } from "fs";
9216
- import { existsSync as existsSync4 } from "fs";
9217
- import path7 from "path";
9437
+ import { existsSync as existsSync5 } from "fs";
9438
+ import path8 from "path";
9218
9439
  async function createFile(params) {
9219
9440
  const { path: filePath, content, createDirectories = true } = params;
9220
- const normalizedPath = path7.normalize(filePath);
9221
- const resolvedPath = path7.resolve(process.cwd(), normalizedPath);
9222
- const cwd = path7.resolve(process.cwd());
9441
+ const normalizedPath = path8.normalize(filePath);
9442
+ const resolvedPath = path8.resolve(process.cwd(), normalizedPath);
9443
+ const cwd = path8.resolve(process.cwd());
9223
9444
  if (!resolvedPath.startsWith(cwd)) {
9224
9445
  throw new Error(`Access denied: Cannot create files outside of current working directory`);
9225
9446
  }
9226
- const fileExists = existsSync4(resolvedPath);
9447
+ const fileExists = existsSync5(resolvedPath);
9227
9448
  const action = fileExists ? "overwritten" : "created";
9228
9449
  if (createDirectories) {
9229
- const dir = path7.dirname(resolvedPath);
9450
+ const dir = path8.dirname(resolvedPath);
9230
9451
  await fs8.mkdir(dir, { recursive: true });
9231
9452
  }
9232
9453
  await fs8.writeFile(resolvedPath, content, "utf-8");
@@ -9241,7 +9462,7 @@ var createFileTool = {
9241
9462
  implementation: (context) => ({
9242
9463
  toolFn: async (value) => {
9243
9464
  const params = value;
9244
- const fileExists = existsSync4(path7.resolve(process.cwd(), path7.normalize(params.path)));
9465
+ const fileExists = existsSync5(path8.resolve(process.cwd(), path8.normalize(params.path)));
9245
9466
  context.logger.info(`\u{1F4DD} CreateFile: ${fileExists ? "Overwriting" : "Creating"} file`, {
9246
9467
  path: params.path,
9247
9468
  size: params.content.length
@@ -9283,8 +9504,8 @@ var createFileTool = {
9283
9504
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/globFiles/index.js
9284
9505
  import { glob } from "glob";
9285
9506
  import { stat } from "fs/promises";
9286
- import path8 from "path";
9287
- var DEFAULT_IGNORE_PATTERNS2 = [
9507
+ import path9 from "path";
9508
+ var DEFAULT_IGNORE_PATTERNS = [
9288
9509
  "**/node_modules/**",
9289
9510
  "**/.git/**",
9290
9511
  "**/dist/**",
@@ -9299,11 +9520,11 @@ var DEFAULT_IGNORE_PATTERNS2 = [
9299
9520
  async function findFiles(params) {
9300
9521
  const { pattern, dir_path, case_sensitive = true, respect_git_ignore = true } = params;
9301
9522
  const baseCwd = process.cwd();
9302
- const targetDir = dir_path ? path8.resolve(baseCwd, path8.normalize(dir_path)) : baseCwd;
9523
+ const targetDir = dir_path ? path9.resolve(baseCwd, path9.normalize(dir_path)) : baseCwd;
9303
9524
  if (!targetDir.startsWith(baseCwd)) {
9304
9525
  throw new Error(`Access denied: Cannot search outside of current working directory`);
9305
9526
  }
9306
- const ignorePatterns = respect_git_ignore ? DEFAULT_IGNORE_PATTERNS2 : [];
9527
+ const ignorePatterns = respect_git_ignore ? DEFAULT_IGNORE_PATTERNS : [];
9307
9528
  const matches = await glob(pattern, {
9308
9529
  cwd: targetDir,
9309
9530
  dot: false,
@@ -9339,7 +9560,7 @@ async function findFiles(params) {
9339
9560
  const summary = `Found ${filesWithStats.length} file(s)${truncated ? ` (showing first ${MAX_RESULTS})` : ""} matching: ${pattern}`;
9340
9561
  const dirInfo = dir_path ? `
9341
9562
  Directory: ${dir_path}` : "";
9342
- const filesList = results.map((file) => path8.relative(baseCwd, file.path)).join("\n");
9563
+ const filesList = results.map((file) => path9.relative(baseCwd, file.path)).join("\n");
9343
9564
  return `${summary}${dirInfo}
9344
9565
 
9345
9566
  ${filesList}`;
@@ -9393,7 +9614,7 @@ var globFilesTool = {
9393
9614
 
9394
9615
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/grepSearch/index.js
9395
9616
  import { stat as stat2 } from "fs/promises";
9396
- import path9 from "path";
9617
+ import path10 from "path";
9397
9618
  import { execFile } from "child_process";
9398
9619
  import { promisify } from "util";
9399
9620
  import { createRequire } from "module";
@@ -9402,18 +9623,18 @@ var require2 = createRequire(import.meta.url);
9402
9623
  function getRipgrepPath() {
9403
9624
  try {
9404
9625
  const ripgrepPath = require2.resolve("@vscode/ripgrep");
9405
- const ripgrepDir = path9.dirname(ripgrepPath);
9406
- const rgBinary = path9.join(ripgrepDir, "..", "bin", "rg" + (process.platform === "win32" ? ".exe" : ""));
9626
+ const ripgrepDir = path10.dirname(ripgrepPath);
9627
+ const rgBinary = path10.join(ripgrepDir, "..", "bin", "rg" + (process.platform === "win32" ? ".exe" : ""));
9407
9628
  return rgBinary;
9408
9629
  } catch (error) {
9409
9630
  throw new Error("ripgrep is not available. Please install @vscode/ripgrep: pnpm add @vscode/ripgrep --filter @bike4mind/services");
9410
9631
  }
9411
9632
  }
9412
9633
  function isPathWithinWorkspace(targetPath, baseCwd) {
9413
- const resolvedTarget = path9.resolve(targetPath);
9414
- const resolvedBase = path9.resolve(baseCwd);
9415
- const relativePath = path9.relative(resolvedBase, resolvedTarget);
9416
- return !relativePath.startsWith("..") && !path9.isAbsolute(relativePath);
9634
+ const resolvedTarget = path10.resolve(targetPath);
9635
+ const resolvedBase = path10.resolve(baseCwd);
9636
+ const relativePath = path10.relative(resolvedBase, resolvedTarget);
9637
+ return !relativePath.startsWith("..") && !path10.isAbsolute(relativePath);
9417
9638
  }
9418
9639
  function convertGlobToRipgrepGlobs(globPattern) {
9419
9640
  if (!globPattern || globPattern === "**/*") {
@@ -9429,7 +9650,7 @@ function convertGlobToRipgrepGlobs(globPattern) {
9429
9650
  async function searchFiles2(params) {
9430
9651
  const { pattern, dir_path, include } = params;
9431
9652
  const baseCwd = process.cwd();
9432
- const targetDir = dir_path ? path9.resolve(baseCwd, dir_path) : baseCwd;
9653
+ const targetDir = dir_path ? path10.resolve(baseCwd, dir_path) : baseCwd;
9433
9654
  if (!isPathWithinWorkspace(targetDir, baseCwd)) {
9434
9655
  throw new Error(`Path validation failed: "${dir_path}" resolves outside the allowed workspace directory`);
9435
9656
  }
@@ -9491,7 +9712,7 @@ async function searchFiles2(params) {
9491
9712
  if (item.type === "match") {
9492
9713
  const match = item;
9493
9714
  allMatches.push({
9494
- filePath: path9.relative(targetDir, match.data.path.text) || path9.basename(match.data.path.text),
9715
+ filePath: path10.relative(targetDir, match.data.path.text) || path10.basename(match.data.path.text),
9495
9716
  lineNumber: match.data.line_number,
9496
9717
  line: match.data.lines.text.trimEnd()
9497
9718
  // Remove trailing newline
@@ -9585,17 +9806,17 @@ var grepSearchTool = {
9585
9806
 
9586
9807
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/deleteFile/index.js
9587
9808
  import { promises as fs9 } from "fs";
9588
- import { existsSync as existsSync5, statSync as statSync5 } from "fs";
9589
- import path10 from "path";
9809
+ import { existsSync as existsSync6, statSync as statSync5 } from "fs";
9810
+ import path11 from "path";
9590
9811
  async function deleteFile(params) {
9591
9812
  const { path: filePath, recursive = false } = params;
9592
- const normalizedPath = path10.normalize(filePath);
9593
- const resolvedPath = path10.resolve(process.cwd(), normalizedPath);
9594
- const cwd = path10.resolve(process.cwd());
9813
+ const normalizedPath = path11.normalize(filePath);
9814
+ const resolvedPath = path11.resolve(process.cwd(), normalizedPath);
9815
+ const cwd = path11.resolve(process.cwd());
9595
9816
  if (!resolvedPath.startsWith(cwd)) {
9596
9817
  throw new Error(`Access denied: Cannot delete files outside of current working directory`);
9597
9818
  }
9598
- if (!existsSync5(resolvedPath)) {
9819
+ if (!existsSync6(resolvedPath)) {
9599
9820
  throw new Error(`File or directory not found: ${filePath}`);
9600
9821
  }
9601
9822
  const stats = statSync5(resolvedPath);
@@ -9618,8 +9839,8 @@ var deleteFileTool = {
9618
9839
  implementation: (context) => ({
9619
9840
  toolFn: async (value) => {
9620
9841
  const params = value;
9621
- const resolvedPath = path10.resolve(process.cwd(), path10.normalize(params.path));
9622
- const isDirectory = existsSync5(resolvedPath) && statSync5(resolvedPath).isDirectory();
9842
+ const resolvedPath = path11.resolve(process.cwd(), path11.normalize(params.path));
9843
+ const isDirectory = existsSync6(resolvedPath) && statSync5(resolvedPath).isDirectory();
9623
9844
  context.logger.info(`\u{1F5D1}\uFE0F DeleteFile: Deleting ${isDirectory ? "directory" : "file"}`, {
9624
9845
  path: params.path,
9625
9846
  recursive: params.recursive
@@ -9744,7 +9965,7 @@ var knowledgeBaseSearchTool = {
9744
9965
 
9745
9966
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/bashExecute/index.js
9746
9967
  import { spawn } from "child_process";
9747
- import path11 from "path";
9968
+ import path12 from "path";
9748
9969
  var DEFAULT_TIMEOUT_MS = 6e4;
9749
9970
  var MAX_OUTPUT_SIZE = 100 * 1024;
9750
9971
  var DANGEROUS_PATTERNS = [
@@ -9928,7 +10149,7 @@ async function executeBashCommand(params) {
9928
10149
  };
9929
10150
  }
9930
10151
  const baseCwd = process.cwd();
9931
- const targetCwd = relativeCwd ? path11.resolve(baseCwd, relativeCwd) : baseCwd;
10152
+ const targetCwd = relativeCwd ? path12.resolve(baseCwd, relativeCwd) : baseCwd;
9932
10153
  const effectiveTimeout = Math.min(timeout, 5 * 60 * 1e3);
9933
10154
  return new Promise((resolve3) => {
9934
10155
  let stdout = "";
@@ -10117,69 +10338,882 @@ BLOCKED OPERATIONS:
10117
10338
  })
10118
10339
  };
10119
10340
 
10120
- // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/editLocalFile/index.js
10121
- import { promises as fs10 } from "fs";
10122
- import { existsSync as existsSync6 } from "fs";
10123
- import path12 from "path";
10124
- import { diffLines as diffLines3 } from "diff";
10125
- function generateDiff(original, modified) {
10126
- const differences = diffLines3(original, modified);
10127
- let diffString = "";
10128
- let additions = 0;
10129
- let deletions = 0;
10130
- differences.forEach((part) => {
10131
- if (part.added) {
10132
- additions += part.count || 0;
10133
- diffString += part.value.split("\n").filter((line) => line).map((line) => `+ ${line}`).join("\n");
10134
- if (diffString && !diffString.endsWith("\n"))
10135
- diffString += "\n";
10136
- } else if (part.removed) {
10137
- deletions += part.count || 0;
10138
- diffString += part.value.split("\n").filter((line) => line).map((line) => `- ${line}`).join("\n");
10139
- if (diffString && !diffString.endsWith("\n"))
10140
- diffString += "\n";
10141
- }
10142
- });
10143
- return { additions, deletions, diff: diffString.trim() };
10144
- }
10145
- async function editLocalFile(params) {
10146
- const { path: filePath, old_string, new_string } = params;
10147
- const normalizedPath = path12.normalize(filePath);
10148
- const resolvedPath = path12.resolve(process.cwd(), normalizedPath);
10149
- const cwd = path12.resolve(process.cwd());
10150
- if (!resolvedPath.startsWith(cwd)) {
10151
- throw new Error(`Access denied: Cannot edit files outside of current working directory`);
10152
- }
10153
- if (!existsSync6(resolvedPath)) {
10154
- throw new Error(`File not found: ${filePath}`);
10155
- }
10156
- const currentContent = await fs10.readFile(resolvedPath, "utf-8");
10157
- if (!currentContent.includes(old_string)) {
10158
- const preview = old_string.length > 100 ? old_string.substring(0, 100) + "..." : old_string;
10159
- throw new Error(`String to replace not found in file. Make sure the old_string matches exactly (including whitespace and line endings). Searched for: "${preview}"`);
10160
- }
10161
- const occurrences = currentContent.split(old_string).length - 1;
10162
- if (occurrences > 1) {
10163
- throw new Error(`Found ${occurrences} occurrences of the string to replace. Please provide a more specific old_string that matches exactly one location.`);
10164
- }
10165
- const newContent = currentContent.replace(old_string, new_string);
10166
- await fs10.writeFile(resolvedPath, newContent, "utf-8");
10167
- const diffResult = generateDiff(old_string, new_string);
10168
- return `File edited successfully: ${filePath}
10169
- Changes: +${diffResult.additions} lines, -${diffResult.deletions} lines
10170
-
10171
- Diff:
10172
- ${diffResult.diff}`;
10341
+ // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/lattice/index.js
10342
+ function createModelData(name, modelType, userId, description, sessionId) {
10343
+ const now = /* @__PURE__ */ new Date();
10344
+ return {
10345
+ name,
10346
+ description: description || "",
10347
+ modelType,
10348
+ userId,
10349
+ sessionId,
10350
+ data: { entities: [], relationships: [] },
10351
+ rules: { rules: [], rulesets: [] },
10352
+ views: { views: [] },
10353
+ settings: {
10354
+ currency: "USD",
10355
+ fiscalYearStart: "01-01",
10356
+ periodGrain: "quarter",
10357
+ defaultDecimalPlaces: 2,
10358
+ negativeFormat: "parentheses"
10359
+ },
10360
+ scenarios: [],
10361
+ operations: [],
10362
+ operationIndex: -1,
10363
+ version: 1,
10364
+ createdAt: now,
10365
+ updatedAt: now
10366
+ };
10173
10367
  }
10174
- var editLocalFileTool = {
10175
- name: "edit_local_file",
10368
+ var latticeCreateModelTool = {
10369
+ name: "lattice_create_model",
10176
10370
  implementation: (context) => ({
10177
- toolFn: async (value) => {
10178
- const params = value;
10179
- context.logger.info(`\u{1F4DD} EditLocalFile: Editing file`, {
10180
- path: params.path,
10181
- oldStringLength: params.old_string.length,
10182
- newStringLength: params.new_string.length
10371
+ toolFn: async (params) => {
10372
+ const { name, description, modelType = "custom", initialData } = params;
10373
+ console.log("[LATTICE DEBUG] \u{1F3D7}\uFE0F lattice_create_model called:", {
10374
+ name,
10375
+ description,
10376
+ modelType,
10377
+ hasInitialData: !!initialData,
10378
+ entityCount: initialData?.entities?.length || 0,
10379
+ ruleCount: initialData?.rules?.length || 0
10380
+ });
10381
+ const modelData = createModelData(name, modelType, context.userId, description);
10382
+ if (initialData?.entities && modelData.data) {
10383
+ const now = /* @__PURE__ */ new Date();
10384
+ for (const entityDef of initialData.entities) {
10385
+ const entityId = entityDef.name.toLowerCase().replace(/\s+/g, "_");
10386
+ const attributes = [];
10387
+ if (entityDef.values) {
10388
+ for (const val of entityDef.values) {
10389
+ attributes.push({
10390
+ key: "period",
10391
+ value: val.period,
10392
+ dataType: "string",
10393
+ isComputed: false
10394
+ });
10395
+ attributes.push({
10396
+ key: "value",
10397
+ value: val.value,
10398
+ dataType: "currency",
10399
+ isComputed: false
10400
+ });
10401
+ attributes.push({
10402
+ key: "category",
10403
+ value: entityDef.name,
10404
+ dataType: "string",
10405
+ isComputed: false
10406
+ });
10407
+ modelData.data.entities.push({
10408
+ id: `${entityId}_${val.period.toLowerCase().replace(/\s+/g, "_")}`,
10409
+ type: entityDef.type || "line_item",
10410
+ name: `${entityDef.name} ${val.period}`,
10411
+ displayName: `${entityDef.name} ${val.period}`,
10412
+ attributes: [
10413
+ { key: "period", value: val.period, dataType: "string", isComputed: false },
10414
+ { key: "value", value: val.value, dataType: "currency", isComputed: false },
10415
+ { key: "category", value: entityDef.name, dataType: "string", isComputed: false }
10416
+ ],
10417
+ metadata: {},
10418
+ createdAt: now,
10419
+ updatedAt: now
10420
+ });
10421
+ }
10422
+ }
10423
+ console.log(`[LATTICE DEBUG] \u2795 Created entity: ${entityDef.name} with ${entityDef.values?.length || 0} period values`);
10424
+ }
10425
+ }
10426
+ if (initialData?.rules && modelData.rules) {
10427
+ const now = /* @__PURE__ */ new Date();
10428
+ for (const ruleDef of initialData.rules) {
10429
+ const ruleId = `rule_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`;
10430
+ const parsedRule = parseFormula(ruleDef.formula);
10431
+ modelData.rules.rules.push({
10432
+ id: ruleId,
10433
+ name: ruleDef.name,
10434
+ description: ruleDef.formula,
10435
+ type: "formula",
10436
+ definition: {
10437
+ operation: parsedRule.operation,
10438
+ inputs: parsedRule.inputs.map((ref) => ({ type: "entity", ref })),
10439
+ output: {
10440
+ targetEntityId: parsedRule.outputEntity.toLowerCase().replace(/\s+/g, "_"),
10441
+ targetAttribute: "computed",
10442
+ dataType: "number"
10443
+ }
10444
+ },
10445
+ dependencies: parsedRule.inputs.map((i) => i.toLowerCase().replace(/\s+/g, "_")),
10446
+ priority: 0,
10447
+ enabled: true,
10448
+ createdAt: now,
10449
+ updatedAt: now
10450
+ });
10451
+ console.log(`[LATTICE DEBUG] \u{1F4D0} Created rule: ${ruleDef.name} = ${ruleDef.formula}`);
10452
+ }
10453
+ }
10454
+ let model;
10455
+ let persistenceStatus = "unknown";
10456
+ const dbKeys = Object.keys(context.db || {});
10457
+ const hasLatticeModels = !!context.db?.latticeModels;
10458
+ if (context.db.latticeModels) {
10459
+ try {
10460
+ model = await context.db.latticeModels.create(modelData);
10461
+ persistenceStatus = `PERSISTED to MongoDB (id: ${model.id})`;
10462
+ context.logger.info(`[Lattice] Created model ${model.id} in database`);
10463
+ } catch (error) {
10464
+ persistenceStatus = `FAILED: ${error instanceof Error ? error.message : String(error)}`;
10465
+ context.logger.error(`[Lattice] Failed to persist model to database:`, error);
10466
+ model = {
10467
+ ...modelData,
10468
+ id: `lattice_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
10469
+ };
10470
+ }
10471
+ } else {
10472
+ persistenceStatus = `IN-MEMORY (no latticeModels adapter). DB keys: [${dbKeys.join(", ")}]`;
10473
+ context.logger.warn("[Lattice] No database adapter available, creating in-memory model");
10474
+ model = {
10475
+ ...modelData,
10476
+ id: `lattice_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
10477
+ };
10478
+ }
10479
+ const artifactData = {
10480
+ type: "lattice",
10481
+ id: model.id,
10482
+ title: name,
10483
+ content: JSON.stringify(model),
10484
+ metadata: {
10485
+ modelType,
10486
+ periodGrain: model.settings?.periodGrain || "quarter",
10487
+ currency: model.settings?.currency || "USD",
10488
+ entityCount: model.data?.entities?.length || 0,
10489
+ ruleCount: model.rules?.rules?.length || 0,
10490
+ // DEBUG: Include persistence info in metadata
10491
+ _debug: {
10492
+ persistenceStatus,
10493
+ hasLatticeModels,
10494
+ dbKeys
10495
+ }
10496
+ },
10497
+ createdAt: model.createdAt?.toISOString?.() || (/* @__PURE__ */ new Date()).toISOString(),
10498
+ updatedAt: model.updatedAt?.toISOString?.() || (/* @__PURE__ */ new Date()).toISOString()
10499
+ };
10500
+ const entityCount = model.data?.entities?.length || 0;
10501
+ const ruleCount = model.rules?.rules?.length || 0;
10502
+ return `Created "${name}" model with ${entityCount} entities and ${ruleCount} rules.
10503
+
10504
+ <artifact identifier="${model.id}" type="application/vnd.b4m.lattice" title="${name}">
10505
+ ${JSON.stringify(artifactData, null, 2)}
10506
+ </artifact>
10507
+
10508
+ The model is ready for viewing. You can add more data by asking to add line items or create formulas.`;
10509
+ },
10510
+ toolSchema: {
10511
+ name: "lattice_create_model",
10512
+ description: `Create a new Lattice financial model with optional initial data. Use initialData to populate entities and rules in ONE call.
10513
+
10514
+ **IMPORTANT**: Always include initialData when the user provides specific values! This creates a fully populated model immediately.
10515
+
10516
+ **When to use:** When the user wants to:
10517
+ - Create a new financial model, spreadsheet, or pro-forma
10518
+ - Start building an income statement, balance sheet, or cash flow
10519
+ - Set up a SaaS metrics dashboard
10520
+
10521
+ **Example with initialData:**
10522
+ User: "Create income statement with $100K revenue and $60K costs for Q1"
10523
+ Call: lattice_create_model with:
10524
+ - name: "Income Statement"
10525
+ - modelType: "income_statement"
10526
+ - initialData: {
10527
+ entities: [
10528
+ { name: "Revenue", values: [{ period: "Q1", value: 100000 }] },
10529
+ { name: "Costs", values: [{ period: "Q1", value: 60000 }] }
10530
+ ],
10531
+ rules: [
10532
+ { name: "Gross Profit", formula: "Gross Profit = Revenue - Costs" }
10533
+ ]
10534
+ }`,
10535
+ parameters: {
10536
+ type: "object",
10537
+ properties: {
10538
+ name: {
10539
+ type: "string",
10540
+ description: 'Name of the model (e.g., "2024 Budget", "Q1 Forecast")'
10541
+ },
10542
+ description: {
10543
+ type: "string",
10544
+ description: "Optional description of what this model represents"
10545
+ },
10546
+ modelType: {
10547
+ type: "string",
10548
+ enum: ["income_statement", "balance_sheet", "cashflow", "saas_metrics", "custom"],
10549
+ description: "Type of financial model to create"
10550
+ },
10551
+ initialData: {
10552
+ type: "object",
10553
+ description: "Initial entities and rules to populate the model. ALWAYS use this when user provides data!",
10554
+ properties: {
10555
+ entities: {
10556
+ type: "array",
10557
+ description: "Line items with their period values",
10558
+ items: {
10559
+ type: "object",
10560
+ properties: {
10561
+ name: { type: "string", description: 'Entity name (e.g., "Revenue", "COGS")' },
10562
+ type: { type: "string", description: "Entity type (default: line_item)" },
10563
+ values: {
10564
+ type: "array",
10565
+ description: "Period-value pairs",
10566
+ items: {
10567
+ type: "object",
10568
+ properties: {
10569
+ period: { type: "string", description: 'Period name (e.g., "Q1", "Jan", "2024")' },
10570
+ value: { type: "number", description: "Numeric value" }
10571
+ },
10572
+ required: ["period", "value"]
10573
+ }
10574
+ }
10575
+ },
10576
+ required: ["name"]
10577
+ }
10578
+ },
10579
+ rules: {
10580
+ type: "array",
10581
+ description: "Formulas/calculations to create",
10582
+ items: {
10583
+ type: "object",
10584
+ properties: {
10585
+ name: { type: "string", description: 'Rule name (e.g., "Gross Profit Calculation")' },
10586
+ formula: {
10587
+ type: "string",
10588
+ description: 'Natural language formula (e.g., "Gross Profit = Revenue - Costs")'
10589
+ }
10590
+ },
10591
+ required: ["name", "formula"]
10592
+ }
10593
+ }
10594
+ }
10595
+ }
10596
+ },
10597
+ required: ["name"]
10598
+ }
10599
+ }
10600
+ })
10601
+ };
10602
+ var latticeAddEntityTool = {
10603
+ name: "lattice_add_entity",
10604
+ implementation: (context) => ({
10605
+ toolFn: async (params) => {
10606
+ const { modelId, name, type, displayName, initialValues = [] } = params;
10607
+ console.log("[LATTICE DEBUG] \u2795 lattice_add_entity called:", { modelId, name, type, initialValues });
10608
+ const entityId = name.toLowerCase().replace(/\s+/g, "_");
10609
+ const entityData = {
10610
+ id: entityId,
10611
+ type,
10612
+ name,
10613
+ displayName: displayName || name,
10614
+ attributes: initialValues.map((v) => ({
10615
+ key: v.key,
10616
+ value: v.value,
10617
+ dataType: v.dataType || (typeof v.value === "number" ? "number" : "string"),
10618
+ isComputed: false
10619
+ })),
10620
+ metadata: {},
10621
+ createdAt: /* @__PURE__ */ new Date(),
10622
+ updatedAt: /* @__PURE__ */ new Date()
10623
+ };
10624
+ if (context.db.latticeModels && modelId && /^[a-f0-9]{24}$/.test(modelId)) {
10625
+ try {
10626
+ const model = await context.db.latticeModels.findById(modelId);
10627
+ if (model) {
10628
+ const existingIndex = model.data.entities.findIndex((e) => e.id === entityId);
10629
+ if (existingIndex >= 0) {
10630
+ model.data.entities[existingIndex] = entityData;
10631
+ } else {
10632
+ model.data.entities.push(entityData);
10633
+ }
10634
+ await context.db.latticeModels.update({
10635
+ id: modelId,
10636
+ data: model.data,
10637
+ updatedAt: /* @__PURE__ */ new Date()
10638
+ });
10639
+ context.logger.info(`[Lattice] Added entity ${entityId} to model ${modelId}`);
10640
+ } else {
10641
+ context.logger.warn(`[Lattice] Model ${modelId} not found in database`);
10642
+ }
10643
+ } catch (error) {
10644
+ context.logger.error(`[Lattice] Failed to persist entity to database:`, error);
10645
+ }
10646
+ }
10647
+ return JSON.stringify({
10648
+ success: true,
10649
+ action: "ADD_ENTITY",
10650
+ modelId,
10651
+ entityId,
10652
+ data: entityData,
10653
+ message: `Added ${type} "${displayName || name}" to model. ${initialValues.length > 0 ? `Set initial values: ${initialValues.map((v) => `${v.key}=${v.value}`).join(", ")}` : "No initial values set."}`
10654
+ });
10655
+ },
10656
+ toolSchema: {
10657
+ name: "lattice_add_entity",
10658
+ description: `Add a line item, account, period, or other entity to a Lattice model.
10659
+
10660
+ **When to use:** When the user mentions:
10661
+ - Adding a revenue line, expense category, or account
10662
+ - Creating periods (Q1, Q2, Jan, Feb, etc.)
10663
+ - Adding any measurable item to their model
10664
+
10665
+ **Entity types:**
10666
+ - line_item: Revenue streams, expense lines, KPIs
10667
+ - account: Cash, AR, AP, inventory, etc.
10668
+ - period: Q1 2024, Jan, FY2025, etc.
10669
+ - category: Groups of line items (Operating Expenses)
10670
+ - scenario: Base case, upside, downside
10671
+ - custom: Any other structured element
10672
+
10673
+ **Examples:**
10674
+ - "Add Revenue" \u2192 line_item named "Revenue"
10675
+ - "Create a Marketing Expenses category" \u2192 category named "Marketing Expenses"
10676
+ - "Add quarters Q1 through Q4" \u2192 4 period entities`,
10677
+ parameters: {
10678
+ type: "object",
10679
+ properties: {
10680
+ modelId: {
10681
+ type: "string",
10682
+ description: "ID of the model to add the entity to"
10683
+ },
10684
+ name: {
10685
+ type: "string",
10686
+ description: "Internal name for the entity (used in formulas)"
10687
+ },
10688
+ type: {
10689
+ type: "string",
10690
+ enum: ["line_item", "account", "period", "category", "scenario", "custom"],
10691
+ description: "Type of entity"
10692
+ },
10693
+ displayName: {
10694
+ type: "string",
10695
+ description: "Human-readable display name (defaults to name)"
10696
+ },
10697
+ initialValues: {
10698
+ type: "array",
10699
+ items: {
10700
+ type: "object",
10701
+ properties: {
10702
+ key: { type: "string", description: 'Period or attribute key (e.g., "Q1_2024")' },
10703
+ value: { type: "string", description: "The value (number or string)" },
10704
+ dataType: {
10705
+ type: "string",
10706
+ enum: ["number", "currency", "percentage", "string"],
10707
+ description: "Data type for the value"
10708
+ }
10709
+ },
10710
+ required: ["key", "value"]
10711
+ },
10712
+ description: "Optional initial values for this entity"
10713
+ }
10714
+ },
10715
+ required: ["modelId", "name", "type"]
10716
+ }
10717
+ }
10718
+ })
10719
+ };
10720
+ var latticeSetValueTool = {
10721
+ name: "lattice_set_value",
10722
+ implementation: (context) => ({
10723
+ toolFn: async (params) => {
10724
+ const { modelId, entityName, attributeKey, value: rawValue } = params;
10725
+ console.log("[LATTICE DEBUG] \u{1F4DD} lattice_set_value called:", { modelId, entityName, attributeKey, rawValue });
10726
+ let value = rawValue;
10727
+ const numValue = parseFloat(rawValue);
10728
+ if (!isNaN(numValue)) {
10729
+ value = numValue;
10730
+ } else if (rawValue.toLowerCase() === "true") {
10731
+ value = true;
10732
+ } else if (rawValue.toLowerCase() === "false") {
10733
+ value = false;
10734
+ }
10735
+ const entityId = entityName.toLowerCase().replace(/\s+/g, "_");
10736
+ if (context.db.latticeModels && modelId && /^[a-f0-9]{24}$/.test(modelId)) {
10737
+ try {
10738
+ const model = await context.db.latticeModels.findById(modelId);
10739
+ if (model) {
10740
+ const entity = model.data.entities.find((e) => e.id === entityId || e.name === entityName);
10741
+ if (entity) {
10742
+ const attrIndex = entity.attributes.findIndex((a) => a.key === attributeKey);
10743
+ const dataType = typeof value === "number" ? "number" : typeof value === "boolean" ? "boolean" : "string";
10744
+ const attributeData = {
10745
+ key: attributeKey,
10746
+ value,
10747
+ dataType,
10748
+ isComputed: false
10749
+ };
10750
+ if (attrIndex >= 0) {
10751
+ entity.attributes[attrIndex] = attributeData;
10752
+ } else {
10753
+ entity.attributes.push(attributeData);
10754
+ }
10755
+ entity.updatedAt = /* @__PURE__ */ new Date();
10756
+ await context.db.latticeModels.update({
10757
+ id: modelId,
10758
+ data: model.data,
10759
+ updatedAt: /* @__PURE__ */ new Date()
10760
+ });
10761
+ context.logger.info(`[Lattice] Set ${entityId}.${attributeKey} = ${value} in model ${modelId}`);
10762
+ } else {
10763
+ context.logger.warn(`[Lattice] Entity ${entityName} not found in model ${modelId}`);
10764
+ }
10765
+ } else {
10766
+ context.logger.warn(`[Lattice] Model ${modelId} not found in database`);
10767
+ }
10768
+ } catch (error) {
10769
+ context.logger.error(`[Lattice] Failed to persist value to database:`, error);
10770
+ }
10771
+ }
10772
+ return JSON.stringify({
10773
+ success: true,
10774
+ action: "SET_VALUE",
10775
+ modelId,
10776
+ data: {
10777
+ entityName,
10778
+ attributeKey,
10779
+ value
10780
+ },
10781
+ message: `Set ${entityName}.${attributeKey} = ${value}`
10782
+ });
10783
+ },
10784
+ toolSchema: {
10785
+ name: "lattice_set_value",
10786
+ description: `Set a specific value in a Lattice model.
10787
+
10788
+ **When to use:** When the user provides a specific number:
10789
+ - "Revenue in Q1 is 150,000"
10790
+ - "Set marketing spend to 50k for January"
10791
+ - "COGS is 40% of revenue" (use lattice_create_rule for formulas)
10792
+
10793
+ **Important:** This tool is for setting raw input values. For calculated values (formulas), use \`lattice_create_rule\` instead.
10794
+
10795
+ **Examples:**
10796
+ - "Q1 revenue is $100,000" \u2192 entityName="Revenue", attributeKey="Q1", value=100000
10797
+ - "Set headcount to 25" \u2192 entityName="Headcount", attributeKey="current", value=25`,
10798
+ parameters: {
10799
+ type: "object",
10800
+ properties: {
10801
+ modelId: {
10802
+ type: "string",
10803
+ description: "ID of the model"
10804
+ },
10805
+ entityName: {
10806
+ type: "string",
10807
+ description: "Name of the entity (line item, account, etc.)"
10808
+ },
10809
+ attributeKey: {
10810
+ type: "string",
10811
+ description: 'Period or attribute key (e.g., "Q1_2024", "current", "budget")'
10812
+ },
10813
+ value: {
10814
+ type: "string",
10815
+ description: "The value to set (will be parsed as number if numeric)"
10816
+ }
10817
+ },
10818
+ required: ["modelId", "entityName", "attributeKey", "value"]
10819
+ }
10820
+ }
10821
+ })
10822
+ };
10823
+ var latticeCreateRuleTool = {
10824
+ name: "lattice_create_rule",
10825
+ implementation: (context) => ({
10826
+ toolFn: async (params) => {
10827
+ const { modelId, name, description, formula } = params;
10828
+ console.log("[LATTICE DEBUG] \u{1F4D0} lattice_create_rule called:", { modelId, name, formula });
10829
+ const parsedRule = parseFormula(formula);
10830
+ const ruleId = `rule_${Date.now()}`;
10831
+ const ruleData = {
10832
+ id: ruleId,
10833
+ name,
10834
+ description: description || formula,
10835
+ type: "formula",
10836
+ definition: {
10837
+ operation: parsedRule.operation,
10838
+ inputs: parsedRule.inputs.map((ref) => ({
10839
+ type: "entity",
10840
+ ref
10841
+ })),
10842
+ output: {
10843
+ targetEntityId: parsedRule.outputEntity.toLowerCase().replace(/\s+/g, "_"),
10844
+ targetAttribute: "computed",
10845
+ dataType: "number"
10846
+ }
10847
+ },
10848
+ dependencies: parsedRule.inputs.map((i) => i.toLowerCase().replace(/\s+/g, "_")),
10849
+ priority: 0,
10850
+ enabled: true,
10851
+ createdAt: /* @__PURE__ */ new Date(),
10852
+ updatedAt: /* @__PURE__ */ new Date()
10853
+ };
10854
+ const outputEntityId = parsedRule.outputEntity.toLowerCase().replace(/\s+/g, "_");
10855
+ let entityCreatedMessage = "";
10856
+ if (context.db.latticeModels && modelId && /^[a-f0-9]{24}$/.test(modelId)) {
10857
+ try {
10858
+ const model = await context.db.latticeModels.findById(modelId);
10859
+ if (model) {
10860
+ const outputEntityExists = model.data.entities.some((e) => e.id === outputEntityId || e.name.toLowerCase() === parsedRule.outputEntity.toLowerCase());
10861
+ if (!outputEntityExists && parsedRule.outputEntity !== "unknown") {
10862
+ const now = /* @__PURE__ */ new Date();
10863
+ const newEntity = {
10864
+ id: outputEntityId,
10865
+ type: "line_item",
10866
+ name: parsedRule.outputEntity,
10867
+ displayName: parsedRule.outputEntity,
10868
+ attributes: [
10869
+ {
10870
+ key: "value",
10871
+ value: 0,
10872
+ dataType: "currency",
10873
+ isComputed: true
10874
+ },
10875
+ {
10876
+ key: "category",
10877
+ value: "Computed",
10878
+ dataType: "string",
10879
+ isComputed: false
10880
+ }
10881
+ ],
10882
+ metadata: { isComputed: true },
10883
+ createdAt: now,
10884
+ updatedAt: now
10885
+ };
10886
+ model.data.entities.push(newEntity);
10887
+ entityCreatedMessage = ` Created output entity "${parsedRule.outputEntity}".`;
10888
+ context.logger.info(`[Lattice] Auto-created output entity ${outputEntityId} for rule ${ruleId}`);
10889
+ }
10890
+ const existingIndex = model.rules.rules.findIndex((r) => r.id === ruleId || r.name === name);
10891
+ if (existingIndex >= 0) {
10892
+ model.rules.rules[existingIndex] = ruleData;
10893
+ } else {
10894
+ model.rules.rules.push(ruleData);
10895
+ }
10896
+ await context.db.latticeModels.update({
10897
+ id: modelId,
10898
+ data: model.data,
10899
+ rules: model.rules,
10900
+ updatedAt: /* @__PURE__ */ new Date()
10901
+ });
10902
+ context.logger.info(`[Lattice] Created rule ${ruleId} in model ${modelId}`);
10903
+ } else {
10904
+ context.logger.warn(`[Lattice] Model ${modelId} not found in database`);
10905
+ }
10906
+ } catch (error) {
10907
+ context.logger.error(`[Lattice] Failed to persist rule to database:`, error);
10908
+ }
10909
+ }
10910
+ return JSON.stringify({
10911
+ success: true,
10912
+ action: "CREATE_RULE",
10913
+ modelId,
10914
+ ruleId,
10915
+ data: {
10916
+ name,
10917
+ description,
10918
+ formula,
10919
+ parsed: parsedRule,
10920
+ outputEntityCreated: entityCreatedMessage !== ""
10921
+ },
10922
+ message: `Created rule "${name}": ${formula}.${entityCreatedMessage}`
10923
+ });
10924
+ },
10925
+ toolSchema: {
10926
+ name: "lattice_create_rule",
10927
+ description: `Create a formula or calculation rule in a Lattice model.
10928
+
10929
+ **When to use:** When the user defines a calculation:
10930
+ - "Gross profit equals revenue minus COGS"
10931
+ - "Net margin is net income divided by revenue times 100"
10932
+ - "Total expenses is the sum of all expense line items"
10933
+
10934
+ **IMPORTANT: For per-period calculations like "calculate X for each quarter", you MUST call this tool ONCE for EACH period.** For example, if asked to "calculate gross margin for each quarter", call this tool 4 times:
10935
+ 1. lattice_create_rule with formula "Q1 Gross Margin = Q1 Revenue - Q1 COGS"
10936
+ 2. lattice_create_rule with formula "Q2 Gross Margin = Q2 Revenue - Q2 COGS"
10937
+ 3. lattice_create_rule with formula "Q3 Gross Margin = Q3 Revenue - Q3 COGS"
10938
+ 4. lattice_create_rule with formula "Q4 Gross Margin = Q4 Revenue - Q4 COGS"
10939
+
10940
+ **Formula syntax (natural language):**
10941
+ - Arithmetic: "X equals Y plus Z", "A minus B", "C times D"
10942
+ - Aggregation: "sum of", "average of", "total"
10943
+ - Percentage: "X percent of Y", "as a percentage"
10944
+ - Comparison: "if X is greater than Y then A else B"
10945
+
10946
+ **Examples:**
10947
+ - "Gross Profit = Revenue - COGS"
10948
+ - "Gross Margin = Gross Profit / Revenue * 100"
10949
+ - "Total OpEx = sum of all Operating Expenses"
10950
+ - "YoY Growth = (Current Year - Prior Year) / Prior Year * 100"
10951
+ - "Q1 Gross Margin = Q1 Revenue - Q1 COGS" (per-period)`,
10952
+ parameters: {
10953
+ type: "object",
10954
+ properties: {
10955
+ modelId: {
10956
+ type: "string",
10957
+ description: "ID of the model"
10958
+ },
10959
+ name: {
10960
+ type: "string",
10961
+ description: 'Name for this rule (e.g., "Gross Profit Calculation")'
10962
+ },
10963
+ description: {
10964
+ type: "string",
10965
+ description: "Optional description of what this rule calculates"
10966
+ },
10967
+ formula: {
10968
+ type: "string",
10969
+ description: "The formula in natural language or equation format"
10970
+ }
10971
+ },
10972
+ required: ["modelId", "name", "formula"]
10973
+ }
10974
+ }
10975
+ })
10976
+ };
10977
+ var latticeQueryTool = {
10978
+ name: "lattice_query",
10979
+ implementation: () => ({
10980
+ toolFn: async (params) => {
10981
+ const { modelId, query } = params;
10982
+ const parsedQuery = parseQuery(query);
10983
+ return JSON.stringify({
10984
+ success: true,
10985
+ action: "QUERY",
10986
+ modelId,
10987
+ data: {
10988
+ query,
10989
+ parsed: parsedQuery
10990
+ },
10991
+ message: `Querying model for: ${query}`
10992
+ });
10993
+ },
10994
+ toolSchema: {
10995
+ name: "lattice_query",
10996
+ description: `Query a Lattice model for specific values or aggregations.
10997
+
10998
+ **When to use:** When the user asks about values in the model:
10999
+ - "What's Q1 gross margin?"
11000
+ - "Show me total revenue for 2024"
11001
+ - "What are all the expense categories?"
11002
+ - "Compare Q1 vs Q2 performance"
11003
+
11004
+ **Query types:**
11005
+ - Single value: "What is [entity] for [period]?"
11006
+ - Aggregation: "Total [entity] for [range]"
11007
+ - Comparison: "Compare [entity] across [periods]"
11008
+ - List: "Show all [entity type]"
11009
+
11010
+ **Examples:**
11011
+ - "What's Q2 revenue?" \u2192 Returns specific value
11012
+ - "Total revenue for 2024" \u2192 Sums Q1-Q4
11013
+ - "Compare gross margin Q1 vs Q2" \u2192 Side-by-side comparison`,
11014
+ parameters: {
11015
+ type: "object",
11016
+ properties: {
11017
+ modelId: {
11018
+ type: "string",
11019
+ description: "ID of the model to query"
11020
+ },
11021
+ query: {
11022
+ type: "string",
11023
+ description: "Natural language query about the model"
11024
+ }
11025
+ },
11026
+ required: ["modelId", "query"]
11027
+ }
11028
+ }
11029
+ })
11030
+ };
11031
+ var latticeExplainTool = {
11032
+ name: "lattice_explain",
11033
+ implementation: () => ({
11034
+ toolFn: async (params) => {
11035
+ const { modelId, entityName, attributeKey } = params;
11036
+ return JSON.stringify({
11037
+ success: true,
11038
+ action: "EXPLAIN",
11039
+ modelId,
11040
+ data: {
11041
+ entityName,
11042
+ attributeKey
11043
+ },
11044
+ message: `Explaining calculation for ${entityName}.${attributeKey}`
11045
+ });
11046
+ },
11047
+ toolSchema: {
11048
+ name: "lattice_explain",
11049
+ description: `Explain how a value in a Lattice model was calculated.
11050
+
11051
+ **When to use:** When the user wants to understand a calculation:
11052
+ - "How did you calculate gross profit?"
11053
+ - "Explain the Q2 margin number"
11054
+ - "Show me the formula for total expenses"
11055
+ - "Why is this number so high?"
11056
+
11057
+ This tool traces the calculation chain from the target value back through all its inputs and rules, providing a step-by-step explanation.`,
11058
+ parameters: {
11059
+ type: "object",
11060
+ properties: {
11061
+ modelId: {
11062
+ type: "string",
11063
+ description: "ID of the model"
11064
+ },
11065
+ entityName: {
11066
+ type: "string",
11067
+ description: "Name of the entity containing the value to explain"
11068
+ },
11069
+ attributeKey: {
11070
+ type: "string",
11071
+ description: "Period or attribute key of the value to explain"
11072
+ }
11073
+ },
11074
+ required: ["modelId", "entityName", "attributeKey"]
11075
+ }
11076
+ }
11077
+ })
11078
+ };
11079
+ function parseFormula(formula) {
11080
+ const normalized = formula.toLowerCase().trim();
11081
+ const equalsMatch = normalized.match(/^(.+?)\s*(?:=|equals?)\s*(.+)$/i);
11082
+ if (equalsMatch) {
11083
+ const [, output, expression] = equalsMatch;
11084
+ if (expression.includes("+") || expression.includes("plus")) {
11085
+ return {
11086
+ operation: "ADD",
11087
+ outputEntity: output,
11088
+ inputs: expression.split(/[+]|plus/i).map((s) => s.trim())
11089
+ };
11090
+ }
11091
+ if (expression.includes("-") || expression.includes("minus")) {
11092
+ return {
11093
+ operation: "SUBTRACT",
11094
+ outputEntity: output,
11095
+ inputs: expression.split(/[-]|minus/i).map((s) => s.trim())
11096
+ };
11097
+ }
11098
+ if (expression.includes("*") || expression.includes("times") || expression.includes("x")) {
11099
+ return {
11100
+ operation: "MULTIPLY",
11101
+ outputEntity: output,
11102
+ inputs: expression.split(/[*x]|times/i).map((s) => s.trim())
11103
+ };
11104
+ }
11105
+ if (expression.includes("/") || expression.includes("divided")) {
11106
+ return {
11107
+ operation: "DIVIDE",
11108
+ outputEntity: output,
11109
+ inputs: expression.split(/[/]|divided by/i).map((s) => s.trim())
11110
+ };
11111
+ }
11112
+ if (expression.includes("sum")) {
11113
+ return {
11114
+ operation: "SUM",
11115
+ outputEntity: output,
11116
+ inputs: [expression.replace(/sum of/i, "").trim()]
11117
+ };
11118
+ }
11119
+ }
11120
+ return {
11121
+ operation: "REFERENCE",
11122
+ outputEntity: "unknown",
11123
+ inputs: [formula]
11124
+ };
11125
+ }
11126
+ function parseQuery(query) {
11127
+ const normalized = query.toLowerCase();
11128
+ let type = "single_value";
11129
+ if (normalized.includes("total") || normalized.includes("sum")) {
11130
+ type = "aggregation";
11131
+ } else if (normalized.includes("compare") || normalized.includes("vs")) {
11132
+ type = "comparison";
11133
+ } else if (normalized.includes("list") || normalized.includes("show all")) {
11134
+ type = "list";
11135
+ }
11136
+ const entities = [];
11137
+ const periods = [];
11138
+ const periodPatterns = [/q[1-4]/gi, /\d{4}/g, /jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec/gi];
11139
+ for (const pattern of periodPatterns) {
11140
+ const matches = query.match(pattern);
11141
+ if (matches) {
11142
+ periods.push(...matches);
11143
+ }
11144
+ }
11145
+ const commonEntities = ["revenue", "expenses", "profit", "margin", "income", "cost", "cogs", "ebitda"];
11146
+ for (const entity of commonEntities) {
11147
+ if (normalized.includes(entity)) {
11148
+ entities.push(entity);
11149
+ }
11150
+ }
11151
+ return { type, entities, periods };
11152
+ }
11153
+
11154
+ // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/editLocalFile/index.js
11155
+ import { promises as fs10 } from "fs";
11156
+ import { existsSync as existsSync7 } from "fs";
11157
+ import path13 from "path";
11158
+ import { diffLines as diffLines3 } from "diff";
11159
+ function generateDiff(original, modified) {
11160
+ const differences = diffLines3(original, modified);
11161
+ let diffString = "";
11162
+ let additions = 0;
11163
+ let deletions = 0;
11164
+ differences.forEach((part) => {
11165
+ if (part.added) {
11166
+ additions += part.count || 0;
11167
+ diffString += part.value.split("\n").filter((line) => line).map((line) => `+ ${line}`).join("\n");
11168
+ if (diffString && !diffString.endsWith("\n"))
11169
+ diffString += "\n";
11170
+ } else if (part.removed) {
11171
+ deletions += part.count || 0;
11172
+ diffString += part.value.split("\n").filter((line) => line).map((line) => `- ${line}`).join("\n");
11173
+ if (diffString && !diffString.endsWith("\n"))
11174
+ diffString += "\n";
11175
+ }
11176
+ });
11177
+ return { additions, deletions, diff: diffString.trim() };
11178
+ }
11179
+ async function editLocalFile(params) {
11180
+ const { path: filePath, old_string, new_string } = params;
11181
+ const normalizedPath = path13.normalize(filePath);
11182
+ const resolvedPath = path13.resolve(process.cwd(), normalizedPath);
11183
+ const cwd = path13.resolve(process.cwd());
11184
+ if (!resolvedPath.startsWith(cwd)) {
11185
+ throw new Error(`Access denied: Cannot edit files outside of current working directory`);
11186
+ }
11187
+ if (!existsSync7(resolvedPath)) {
11188
+ throw new Error(`File not found: ${filePath}`);
11189
+ }
11190
+ const currentContent = await fs10.readFile(resolvedPath, "utf-8");
11191
+ if (!currentContent.includes(old_string)) {
11192
+ const preview = old_string.length > 100 ? old_string.substring(0, 100) + "..." : old_string;
11193
+ throw new Error(`String to replace not found in file. Make sure the old_string matches exactly (including whitespace and line endings). Searched for: "${preview}"`);
11194
+ }
11195
+ const occurrences = currentContent.split(old_string).length - 1;
11196
+ if (occurrences > 1) {
11197
+ throw new Error(`Found ${occurrences} occurrences of the string to replace. Please provide a more specific old_string that matches exactly one location.`);
11198
+ }
11199
+ const newContent = currentContent.replace(old_string, new_string);
11200
+ await fs10.writeFile(resolvedPath, newContent, "utf-8");
11201
+ const diffResult = generateDiff(old_string, new_string);
11202
+ return `File edited successfully: ${filePath}
11203
+ Changes: +${diffResult.additions} lines, -${diffResult.deletions} lines
11204
+
11205
+ Diff:
11206
+ ${diffResult.diff}`;
11207
+ }
11208
+ var editLocalFileTool = {
11209
+ name: "edit_local_file",
11210
+ implementation: (context) => ({
11211
+ toolFn: async (value) => {
11212
+ const params = value;
11213
+ context.logger.info(`\u{1F4DD} EditLocalFile: Editing file`, {
11214
+ path: params.path,
11215
+ oldStringLength: params.old_string.length,
11216
+ newStringLength: params.new_string.length
10183
11217
  });
10184
11218
  try {
10185
11219
  const result = await editLocalFile(params);
@@ -10588,7 +11622,14 @@ var cliOnlyTools = {
10588
11622
  // Shell execution
10589
11623
  bash_execute: bashExecuteTool,
10590
11624
  // Git operations
10591
- recent_changes: recentChangesTool
11625
+ recent_changes: recentChangesTool,
11626
+ // Lattice financial modeling tools
11627
+ lattice_create_model: latticeCreateModelTool,
11628
+ lattice_add_entity: latticeAddEntityTool,
11629
+ lattice_set_value: latticeSetValueTool,
11630
+ lattice_create_rule: latticeCreateRuleTool,
11631
+ lattice_query: latticeQueryTool,
11632
+ lattice_explain: latticeExplainTool
10592
11633
  };
10593
11634
  var generateTools = (userId, user, logger2, { db }, storage, imageGenerateStorage, statusUpdate, onStart, onFinish, llm, config, model, imageProcessorLambdaName, tools = b4mTools) => {
10594
11635
  const context = {
@@ -10714,6 +11755,7 @@ var QuestStartBodySchema = z139.object({
10714
11755
  enableMementos: z139.boolean().optional(),
10715
11756
  enableArtifacts: z139.boolean().optional(),
10716
11757
  enableAgents: z139.boolean().optional(),
11758
+ enableLattice: z139.boolean().optional(),
10717
11759
  promptMeta: PromptMetaZodSchema,
10718
11760
  tools: z139.array(z139.union([b4mLLMTools, z139.string()])).optional(),
10719
11761
  mcpServers: z139.array(z139.string()).optional(),
@@ -11341,10 +12383,10 @@ var ToolErrorType;
11341
12383
  // src/utils/diffPreview.ts
11342
12384
  import * as Diff from "diff";
11343
12385
  import { readFile } from "fs/promises";
11344
- import { existsSync as existsSync7 } from "fs";
12386
+ import { existsSync as existsSync8 } from "fs";
11345
12387
  async function generateFileDiffPreview(args) {
11346
12388
  try {
11347
- if (!existsSync7(args.path)) {
12389
+ if (!existsSync8(args.path)) {
11348
12390
  const lines2 = args.content.split("\n");
11349
12391
  const preview = lines2.slice(0, 20).join("\n");
11350
12392
  const hasMore = lines2.length > 20;
@@ -11382,7 +12424,7 @@ ${diffLines4.join("\n")}`;
11382
12424
  }
11383
12425
  async function generateFileDeletePreview(args) {
11384
12426
  try {
11385
- if (!existsSync7(args.path)) {
12427
+ if (!existsSync8(args.path)) {
11386
12428
  return `[File does not exist: ${args.path}]`;
11387
12429
  }
11388
12430
  const stats = await import("fs/promises").then((fs14) => fs14.stat(args.path));
@@ -11676,21 +12718,21 @@ var NoOpStorage = class extends BaseStorage {
11676
12718
  async upload(input, destination, options) {
11677
12719
  return `/tmp/${destination}`;
11678
12720
  }
11679
- async download(path18) {
12721
+ async download(path19) {
11680
12722
  throw new Error("Download not supported in CLI");
11681
12723
  }
11682
- async delete(path18) {
12724
+ async delete(path19) {
11683
12725
  }
11684
- async getSignedUrl(path18) {
11685
- return `/tmp/${path18}`;
12726
+ async getSignedUrl(path19) {
12727
+ return `/tmp/${path19}`;
11686
12728
  }
11687
- getPublicUrl(path18) {
11688
- return `/tmp/${path18}`;
12729
+ getPublicUrl(path19) {
12730
+ return `/tmp/${path19}`;
11689
12731
  }
11690
- async getPreview(path18) {
11691
- return `/tmp/${path18}`;
12732
+ async getPreview(path19) {
12733
+ return `/tmp/${path19}`;
11692
12734
  }
11693
- async getMetadata(path18) {
12735
+ async getMetadata(path19) {
11694
12736
  return { size: 0, contentType: "application/octet-stream" };
11695
12737
  }
11696
12738
  };
@@ -11764,6 +12806,9 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
11764
12806
  if (response.action === "deny") {
11765
12807
  throw new PermissionDeniedError(toolName, args);
11766
12808
  }
12809
+ if (response.action === "allow-session") {
12810
+ permissionManager.trustToolForSession(toolName);
12811
+ }
11767
12812
  if (response.action === "allow-always") {
11768
12813
  const canTrust = permissionManager.trustTool(toolName);
11769
12814
  if (canTrust) {
@@ -11968,6 +13013,7 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
11968
13013
  var PermissionManager = class {
11969
13014
  constructor(trustedTools = [], customCategories, deniedTools) {
11970
13015
  this.trustedTools = /* @__PURE__ */ new Set();
13016
+ this.sessionTrustedTools = /* @__PURE__ */ new Set();
11971
13017
  this.deniedTools = /* @__PURE__ */ new Set();
11972
13018
  this.trustedTools = new Set(trustedTools);
11973
13019
  this.customCategories = new Map(Object.entries(customCategories || {}));
@@ -11993,6 +13039,9 @@ var PermissionManager = class {
11993
13039
  if (category === "auto_approve") {
11994
13040
  return false;
11995
13041
  }
13042
+ if (this.sessionTrustedTools.has(toolName)) {
13043
+ return false;
13044
+ }
11996
13045
  if (category === "prompt_always") {
11997
13046
  return true;
11998
13047
  }
@@ -12060,6 +13109,29 @@ var PermissionManager = class {
12060
13109
  getDeniedTools() {
12061
13110
  return Array.from(this.deniedTools).sort();
12062
13111
  }
13112
+ /**
13113
+ * Trust a tool for the current session only (in-memory, no persistence)
13114
+ * Works for all tool categories including prompt_always, but NOT project-denied tools
13115
+ */
13116
+ trustToolForSession(toolName) {
13117
+ if (this.deniedTools.has(toolName)) {
13118
+ return false;
13119
+ }
13120
+ this.sessionTrustedTools.add(toolName);
13121
+ return true;
13122
+ }
13123
+ /**
13124
+ * Check if a tool is trusted for the current session
13125
+ */
13126
+ isSessionTrusted(toolName) {
13127
+ return this.sessionTrustedTools.has(toolName);
13128
+ }
13129
+ /**
13130
+ * Clear all session-scoped trust (called on exit or session reset)
13131
+ */
13132
+ clearSessionTrust() {
13133
+ this.sessionTrustedTools.clear();
13134
+ }
12063
13135
  /**
12064
13136
  * Clear all trusted tools
12065
13137
  */
@@ -12097,7 +13169,7 @@ function getEnvironmentName(configApiConfig) {
12097
13169
 
12098
13170
  // src/utils/contextLoader.ts
12099
13171
  import * as fs11 from "fs";
12100
- import * as path13 from "path";
13172
+ import * as path14 from "path";
12101
13173
  import { homedir as homedir3 } from "os";
12102
13174
  var CONTEXT_FILE_SIZE_LIMIT = 100 * 1024;
12103
13175
  var PROJECT_CONTEXT_FILES = [
@@ -12118,7 +13190,7 @@ function formatFileSize2(bytes) {
12118
13190
  return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
12119
13191
  }
12120
13192
  function tryReadContextFile(dir, filename, source) {
12121
- const filePath = path13.join(dir, filename);
13193
+ const filePath = path14.join(dir, filename);
12122
13194
  try {
12123
13195
  const stats = fs11.lstatSync(filePath);
12124
13196
  if (stats.isDirectory()) {
@@ -12186,7 +13258,7 @@ ${project.content}`;
12186
13258
  }
12187
13259
  async function loadContextFiles(projectDir) {
12188
13260
  const errors = [];
12189
- const globalDir = path13.join(homedir3(), ".bike4mind");
13261
+ const globalDir = path14.join(homedir3(), ".bike4mind");
12190
13262
  const projectDirectory = projectDir || process.cwd();
12191
13263
  const [globalResult, projectResult] = await Promise.all([
12192
13264
  Promise.resolve(findContextFile(globalDir, GLOBAL_CONTEXT_FILES, "global")),
@@ -12457,8 +13529,8 @@ function substituteArguments(template, args) {
12457
13529
  // ../../b4m-core/packages/mcp/dist/src/client.js
12458
13530
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
12459
13531
  import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
12460
- import path14 from "path";
12461
- import { existsSync as existsSync8, readdirSync as readdirSync3 } from "fs";
13532
+ import path15 from "path";
13533
+ import { existsSync as existsSync9, readdirSync as readdirSync3 } from "fs";
12462
13534
  var MCPClient = class {
12463
13535
  // Note: This class handles MCP server communication with repository filtering
12464
13536
  mcp;
@@ -12499,18 +13571,18 @@ var MCPClient = class {
12499
13571
  const root = process.env.INIT_CWD || process.cwd();
12500
13572
  const candidatePaths = [
12501
13573
  // When running from SST Lambda with node_modules structure (copyFiles)
12502
- path14.join(root, `node_modules/@bike4mind/mcp/dist/src/${this.serverName}/index.js`),
13574
+ path15.join(root, `node_modules/@bike4mind/mcp/dist/src/${this.serverName}/index.js`),
12503
13575
  // When running from SST Lambda deployed environment (/var/task)
12504
- path14.join(root, `b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
13576
+ path15.join(root, `b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
12505
13577
  // When running from SST Lambda (.sst/artifacts/mcpHandler-dev), navigate to monorepo root (3 levels up)
12506
- path14.join(root, `../../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
13578
+ path15.join(root, `../../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
12507
13579
  // When running from packages/client (Next.js app), navigate to monorepo root (2 levels up)
12508
- path14.join(root, `../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
13580
+ path15.join(root, `../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
12509
13581
  // Original paths (backward compatibility)
12510
- path14.join(root, `/b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
12511
- path14.join(root, "core", "mcp", "servers", this.serverName, "dist", "index.js")
13582
+ path15.join(root, `/b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
13583
+ path15.join(root, "core", "mcp", "servers", this.serverName, "dist", "index.js")
12512
13584
  ];
12513
- const serverScriptPath = candidatePaths.find((p) => existsSync8(p));
13585
+ const serverScriptPath = candidatePaths.find((p) => existsSync9(p));
12514
13586
  if (!serverScriptPath) {
12515
13587
  const getDirectories = (source) => readdirSync3(source, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
12516
13588
  console.error(`[MCP] Server script not found. Tried paths:`, candidatePaths);
@@ -13070,6 +14142,14 @@ ${totalText}`);
13070
14142
  function stripThinkingBlocks(text) {
13071
14143
  return text.replace(/<think>[\s\S]*?<\/think>/g, "").trim();
13072
14144
  }
14145
+ function extractUsageInfo(parsed) {
14146
+ return {
14147
+ inputTokens: parsed.usage?.inputTokens,
14148
+ outputTokens: parsed.usage?.outputTokens,
14149
+ creditsUsed: parsed.credits?.used,
14150
+ usdCost: parsed.credits?.usdCost
14151
+ };
14152
+ }
13073
14153
  var ServerLlmBackend = class {
13074
14154
  constructor(options) {
13075
14155
  this.completionsEndpoint = "/api/ai/v1/completions";
@@ -13221,11 +14301,8 @@ var ServerLlmBackend = class {
13221
14301
  if (parsed.type === "content") {
13222
14302
  const textChunk = parsed.text || "";
13223
14303
  accumulatedText += textChunk;
13224
- if (parsed.usage) {
13225
- lastUsageInfo = {
13226
- inputTokens: parsed.usage.inputTokens,
13227
- outputTokens: parsed.usage.outputTokens
13228
- };
14304
+ if (parsed.usage || parsed.credits) {
14305
+ lastUsageInfo = extractUsageInfo(parsed);
13229
14306
  }
13230
14307
  streamLogger.onContent(eventCount, textChunk, accumulatedText);
13231
14308
  } else if (parsed.type === "tool_use") {
@@ -13252,11 +14329,8 @@ var ServerLlmBackend = class {
13252
14329
  thinkingBlocks = parsed.thinking;
13253
14330
  streamLogger.onCriticalEvent(eventCount, "THINKING", `${thinkingBlocks.length} thinking blocks`);
13254
14331
  }
13255
- if (parsed.usage) {
13256
- lastUsageInfo = {
13257
- inputTokens: parsed.usage.inputTokens,
13258
- outputTokens: parsed.usage.outputTokens
13259
- };
14332
+ if (parsed.usage || parsed.credits) {
14333
+ lastUsageInfo = extractUsageInfo(parsed);
13260
14334
  }
13261
14335
  }
13262
14336
  } catch (parseError) {
@@ -13561,7 +14635,7 @@ import { isAxiosError as isAxiosError2 } from "axios";
13561
14635
  // package.json
13562
14636
  var package_default = {
13563
14637
  name: "@bike4mind/cli",
13564
- version: "0.2.29-slack-native-search.18848+70518c650",
14638
+ version: "0.2.29",
13565
14639
  type: "module",
13566
14640
  description: "Interactive CLI tool for Bike4Mind with ReAct agents",
13567
14641
  license: "UNLICENSED",
@@ -13632,10 +14706,13 @@ var package_default = {
13632
14706
  diff: "^8.0.2",
13633
14707
  dotenv: "^16.3.1",
13634
14708
  "eventsource-parser": "^3.0.6",
14709
+ fdir: "^6.5.0",
13635
14710
  "file-type": "^18.7.0",
13636
14711
  "fuse.js": "^7.1.0",
14712
+ fzf: "^0.5.2",
13637
14713
  glob: "^13.0.0",
13638
14714
  "gray-matter": "^4.0.3",
14715
+ ignore: "^7.0.5",
13639
14716
  ink: "^6.5.1",
13640
14717
  "ink-select-input": "^6.2.0",
13641
14718
  "ink-spinner": "^5.0.0",
@@ -13651,6 +14728,7 @@ var package_default = {
13651
14728
  open: "^11.0.0",
13652
14729
  openai: "^6.18.0",
13653
14730
  "p-limit": "^6.2.0",
14731
+ picomatch: "^4.0.3",
13654
14732
  qrcode: "^1.5.4",
13655
14733
  react: "^19.2.3",
13656
14734
  sharp: "^0.34.5",
@@ -13671,14 +14749,15 @@ var package_default = {
13671
14749
  },
13672
14750
  devDependencies: {
13673
14751
  "@bike4mind/agents": "0.1.0",
13674
- "@bike4mind/common": "2.50.1-slack-native-search.18848+70518c650",
13675
- "@bike4mind/mcp": "1.29.1-slack-native-search.18848+70518c650",
13676
- "@bike4mind/services": "2.48.1-slack-native-search.18848+70518c650",
13677
- "@bike4mind/utils": "2.5.1-slack-native-search.18848+70518c650",
14752
+ "@bike4mind/common": "2.51.0",
14753
+ "@bike4mind/mcp": "1.30.0",
14754
+ "@bike4mind/services": "2.49.0",
14755
+ "@bike4mind/utils": "2.6.0",
13678
14756
  "@types/better-sqlite3": "^7.6.13",
13679
14757
  "@types/diff": "^5.0.9",
13680
14758
  "@types/jsonwebtoken": "^9.0.4",
13681
14759
  "@types/node": "^22.9.0",
14760
+ "@types/picomatch": "^4.0.2",
13682
14761
  "@types/react": "^19.2.7",
13683
14762
  "@types/uuid": "^9.0.7",
13684
14763
  "@types/yargs": "^17.0.32",
@@ -13691,7 +14770,7 @@ var package_default = {
13691
14770
  optionalDependencies: {
13692
14771
  "@vscode/ripgrep": "^1.17.0"
13693
14772
  },
13694
- gitHead: "70518c65045da6771d97bdadf52992a08b855d1d"
14773
+ gitHead: "dd78e3f7ac6eb31fbbae8df3be1f33310779a9d5"
13695
14774
  };
13696
14775
 
13697
14776
  // src/config/constants.ts
@@ -13928,50 +15007,6 @@ ${expandedBody}
13928
15007
  };
13929
15008
  }
13930
15009
 
13931
- // src/core/skillsPrompt.ts
13932
- function getSkillDisplayName(cmd) {
13933
- return cmd.displayName || cmd.name;
13934
- }
13935
- function formatSkillEntry(cmd) {
13936
- const displayName = getSkillDisplayName(cmd);
13937
- const argHint = cmd.argumentHint ? ` ${cmd.argumentHint}` : "";
13938
- const nameDisplay = cmd.displayName && cmd.displayName !== cmd.name ? `**${displayName}** (\`${cmd.name}\`)` : `**${cmd.name}**`;
13939
- return `- ${nameDisplay}${argHint}: ${cmd.description}
13940
- `;
13941
- }
13942
- function formatSkillGroup(heading, commands) {
13943
- if (commands.length === 0) {
13944
- return "";
13945
- }
13946
- return `
13947
- ### ${heading}
13948
- ${commands.map(formatSkillEntry).join("")}`;
13949
- }
13950
- function filterAIVisibleSkills(commands) {
13951
- return commands.filter((cmd) => !cmd.disableModelInvocation);
13952
- }
13953
- function filterSkillsByAllowedList(commands, allowedSkills) {
13954
- if (!allowedSkills || allowedSkills.length === 0) {
13955
- return commands;
13956
- }
13957
- return commands.filter((cmd) => allowedSkills.includes(cmd.name));
13958
- }
13959
- function buildSkillsPromptSection(commands, allowedSkills) {
13960
- const filteredByAllowed = filterSkillsByAllowedList(commands, allowedSkills);
13961
- const visibleCommands = filterAIVisibleSkills(filteredByAllowed);
13962
- if (visibleCommands.length === 0) {
13963
- return "";
13964
- }
13965
- const projectSkills = visibleCommands.filter((c) => c.source === "project");
13966
- const globalSkills = visibleCommands.filter((c) => c.source === "global");
13967
- return `
13968
-
13969
- ## Available Skills
13970
-
13971
- Use the \`skill\` tool to invoke these. Example: skill({ skill: "commit" })
13972
- ` + formatSkillGroup("Project Skills", projectSkills) + formatSkillGroup("Global Skills", globalSkills);
13973
- }
13974
-
13975
15010
  // src/agents/SubagentOrchestrator.ts
13976
15011
  var SubagentOrchestrator = class {
13977
15012
  constructor(deps) {
@@ -14185,7 +15220,7 @@ var SubagentOrchestrator = class {
14185
15220
 
14186
15221
  // src/agents/AgentStore.ts
14187
15222
  import fs12 from "fs/promises";
14188
- import path15 from "path";
15223
+ import path16 from "path";
14189
15224
  import os2 from "os";
14190
15225
  import matter2 from "gray-matter";
14191
15226
  var FULL_MODEL_ID_PREFIXES = [
@@ -14334,10 +15369,10 @@ var AgentStore = class {
14334
15369
  const root = projectRoot || process.cwd();
14335
15370
  const home = os2.homedir();
14336
15371
  this.builtinAgentsDir = builtinDir;
14337
- this.globalB4MAgentsDir = path15.join(home, ".bike4mind", "agents");
14338
- this.globalClaudeAgentsDir = path15.join(home, ".claude", "agents");
14339
- this.projectB4MAgentsDir = path15.join(root, ".bike4mind", "agents");
14340
- this.projectClaudeAgentsDir = path15.join(root, ".claude", "agents");
15372
+ this.globalB4MAgentsDir = path16.join(home, ".bike4mind", "agents");
15373
+ this.globalClaudeAgentsDir = path16.join(home, ".claude", "agents");
15374
+ this.projectB4MAgentsDir = path16.join(root, ".bike4mind", "agents");
15375
+ this.projectClaudeAgentsDir = path16.join(root, ".claude", "agents");
14341
15376
  }
14342
15377
  /**
14343
15378
  * Load all agents from all directories
@@ -14391,7 +15426,7 @@ var AgentStore = class {
14391
15426
  try {
14392
15427
  const entries = await fs12.readdir(directory, { withFileTypes: true });
14393
15428
  for (const entry of entries) {
14394
- const fullPath = path15.join(directory, entry.name);
15429
+ const fullPath = path16.join(directory, entry.name);
14395
15430
  if (entry.isDirectory()) {
14396
15431
  const subFiles = await this.findAgentFiles(fullPath);
14397
15432
  files.push(...subFiles);
@@ -14411,7 +15446,7 @@ var AgentStore = class {
14411
15446
  const content = await fs12.readFile(filePath, "utf-8");
14412
15447
  const { data: frontmatter, content: body } = matter2(content);
14413
15448
  const parsed = AgentFrontmatterSchema.parse(frontmatter);
14414
- const name = path15.basename(filePath, ".md");
15449
+ const name = path16.basename(filePath, ".md");
14415
15450
  const modelInput = parsed.model || DEFAULT_AGENT_MODEL;
14416
15451
  const resolution = resolveModelAlias(modelInput, name, filePath);
14417
15452
  if (!resolution.resolved && resolution.warning) {
@@ -14491,7 +15526,7 @@ var AgentStore = class {
14491
15526
  */
14492
15527
  async createAgentFile(name, isGlobal = false, useClaude = true) {
14493
15528
  const targetDir = isGlobal ? useClaude ? this.globalClaudeAgentsDir : this.globalB4MAgentsDir : useClaude ? this.projectClaudeAgentsDir : this.projectB4MAgentsDir;
14494
- const filePath = path15.join(targetDir, `${name}.md`);
15529
+ const filePath = path16.join(targetDir, `${name}.md`);
14495
15530
  try {
14496
15531
  await fs12.access(filePath);
14497
15532
  throw new Error(`Agent file already exists: ${filePath}`);
@@ -14552,6 +15587,21 @@ Describe the expected output format here.
14552
15587
  }
14553
15588
  return { builtin, global, project, total: this.agents.size };
14554
15589
  }
15590
+ /**
15591
+ * Generates a markdown "Phone Book" of available agents and their schemas.
15592
+ * This MUST be injected into the System Prompt of the parent agent.
15593
+ */
15594
+ getDirectoryContext() {
15595
+ if (this.agents.size === 0) {
15596
+ return "No sub-agents are currently available.";
15597
+ }
15598
+ let context = "Use `subagent_delegate` for complex tasks requiring specialized analysis.\n";
15599
+ for (const [name, def] of this.agents) {
15600
+ context += ` - **${name}**: ${def.description}
15601
+ `;
15602
+ }
15603
+ return context;
15604
+ }
14555
15605
  };
14556
15606
 
14557
15607
  // src/agents/delegateTool.ts
@@ -15173,7 +16223,7 @@ function createTodoStore(onUpdate) {
15173
16223
 
15174
16224
  // src/tools/findDefinitionTool.ts
15175
16225
  import { stat as stat3 } from "fs/promises";
15176
- import path16 from "path";
16226
+ import path17 from "path";
15177
16227
  import { execFile as execFile2 } from "child_process";
15178
16228
  import { promisify as promisify2 } from "util";
15179
16229
  import { createRequire as createRequire2 } from "module";
@@ -15194,8 +16244,8 @@ var ALL_KEYWORDS = Object.values(KIND_KEYWORDS).flat();
15194
16244
  function getRipgrepPath2() {
15195
16245
  try {
15196
16246
  const ripgrepPath = require3.resolve("@vscode/ripgrep");
15197
- const ripgrepDir = path16.dirname(ripgrepPath);
15198
- return path16.join(ripgrepDir, "..", "bin", "rg" + (process.platform === "win32" ? ".exe" : ""));
16247
+ const ripgrepDir = path17.dirname(ripgrepPath);
16248
+ return path17.join(ripgrepDir, "..", "bin", "rg" + (process.platform === "win32" ? ".exe" : ""));
15199
16249
  } catch {
15200
16250
  throw new Error(
15201
16251
  "ripgrep is not available. Please install @vscode/ripgrep: pnpm add @vscode/ripgrep --filter @bike4mind/services"
@@ -15203,10 +16253,10 @@ function getRipgrepPath2() {
15203
16253
  }
15204
16254
  }
15205
16255
  function isPathWithinWorkspace2(targetPath, baseCwd) {
15206
- const resolvedTarget = path16.resolve(targetPath);
15207
- const resolvedBase = path16.resolve(baseCwd);
15208
- const relativePath = path16.relative(resolvedBase, resolvedTarget);
15209
- return !relativePath.startsWith("..") && !path16.isAbsolute(relativePath);
16256
+ const resolvedTarget = path17.resolve(targetPath);
16257
+ const resolvedBase = path17.resolve(baseCwd);
16258
+ const relativePath = path17.relative(resolvedBase, resolvedTarget);
16259
+ return !relativePath.startsWith("..") && !path17.isAbsolute(relativePath);
15210
16260
  }
15211
16261
  function escapeRegex(str) {
15212
16262
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -15239,7 +16289,7 @@ async function findDefinitions(params) {
15239
16289
  throw new Error("symbol_name is required");
15240
16290
  }
15241
16291
  const baseCwd = process.cwd();
15242
- const targetDir = search_path ? path16.resolve(baseCwd, search_path) : baseCwd;
16292
+ const targetDir = search_path ? path17.resolve(baseCwd, search_path) : baseCwd;
15243
16293
  if (!isPathWithinWorkspace2(targetDir, baseCwd)) {
15244
16294
  throw new Error(`Path validation failed: "${search_path}" resolves outside the allowed workspace directory`);
15245
16295
  }
@@ -15294,7 +16344,7 @@ async function findDefinitions(params) {
15294
16344
  const lineText = match.data.lines.text.trimEnd();
15295
16345
  if (isLikelyDefinition(lineText)) {
15296
16346
  allMatches.push({
15297
- filePath: path16.relative(targetDir, match.data.path.text) || path16.basename(match.data.path.text),
16347
+ filePath: path17.relative(targetDir, match.data.path.text) || path17.basename(match.data.path.text),
15298
16348
  lineNumber: match.data.line_number,
15299
16349
  line: lineText
15300
16350
  });
@@ -15372,8 +16422,8 @@ function createFindDefinitionTool() {
15372
16422
  }
15373
16423
 
15374
16424
  // src/tools/getFileStructure/index.ts
15375
- import { existsSync as existsSync9, promises as fs13, statSync as statSync6 } from "fs";
15376
- import path17 from "path";
16425
+ import { existsSync as existsSync10, promises as fs13, statSync as statSync6 } from "fs";
16426
+ import path18 from "path";
15377
16427
 
15378
16428
  // src/tools/getFileStructure/formatter.ts
15379
16429
  var SECTIONS = [
@@ -15417,11 +16467,11 @@ function createGetFileStructureTool() {
15417
16467
  const params = value;
15418
16468
  try {
15419
16469
  const cwd = process.cwd();
15420
- const resolvedPath = path17.resolve(cwd, params.path);
16470
+ const resolvedPath = path18.resolve(cwd, params.path);
15421
16471
  if (!resolvedPath.startsWith(cwd)) {
15422
16472
  return "Error: Access denied - cannot read files outside of current working directory";
15423
16473
  }
15424
- if (!existsSync9(resolvedPath)) {
16474
+ if (!existsSync10(resolvedPath)) {
15425
16475
  return `Error: File not found: ${params.path}`;
15426
16476
  }
15427
16477
  const stats = statSync6(resolvedPath);
@@ -15431,7 +16481,7 @@ function createGetFileStructureTool() {
15431
16481
  if (stats.size > MAX_FILE_SIZE3) {
15432
16482
  return `Error: File too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Max: ${MAX_FILE_SIZE3 / 1024 / 1024}MB`;
15433
16483
  }
15434
- const ext = path17.extname(resolvedPath).toLowerCase();
16484
+ const ext = path18.extname(resolvedPath).toLowerCase();
15435
16485
  const { getLanguageForExtension, parseFileStructure, getSupportedLanguages } = await import("./treeSitterEngine-4SGFQDY3.js");
15436
16486
  const languageId = getLanguageForExtension(ext);
15437
16487
  if (!languageId) {
@@ -15478,7 +16528,7 @@ var usageCache = null;
15478
16528
  function CliApp() {
15479
16529
  const { exit } = useApp();
15480
16530
  const imageRenderer = new ImageRenderer();
15481
- const [state, setState] = useState9({
16531
+ const [state, setState] = useState10({
15482
16532
  session: null,
15483
16533
  sessionStore: new SessionStore(),
15484
16534
  configStore: new ConfigStore(),
@@ -15501,9 +16551,9 @@ function CliApp() {
15501
16551
  contextContent: "",
15502
16552
  backgroundManager: null
15503
16553
  });
15504
- const [isInitialized, setIsInitialized] = useState9(false);
15505
- const [initError, setInitError] = useState9(null);
15506
- const [commandHistory, setCommandHistory] = useState9([]);
16554
+ const [isInitialized, setIsInitialized] = useState10(false);
16555
+ const [initError, setInitError] = useState10(null);
16556
+ const [commandHistory, setCommandHistory] = useState10([]);
15507
16557
  const imageStoreInitPromise = useRef3(null);
15508
16558
  const setStoreSession = useCliStore((state2) => state2.setSession);
15509
16559
  const enqueuePermissionPrompt = useCliStore((state2) => state2.enqueuePermissionPrompt);
@@ -15809,18 +16859,12 @@ function CliApp() {
15809
16859
  for (const error of contextResult.errors) {
15810
16860
  startupLog.push(`\u26A0\uFE0F Context file error: ${error}`);
15811
16861
  }
15812
- let contextSection = contextResult.mergedContent ? `
15813
-
15814
- ## Project Context
15815
-
15816
- Follow these project-specific instructions:
15817
-
15818
- ${contextResult.mergedContent}` : "";
15819
- if (enableSkillTool) {
15820
- const commands = state.customCommandStore.getAllCommands();
15821
- contextSection += buildSkillsPromptSection(commands);
15822
- }
15823
- const cliSystemPrompt = buildCoreSystemPrompt(contextSection);
16862
+ const cliSystemPrompt = buildCoreSystemPrompt({
16863
+ contextContent: contextResult.mergedContent,
16864
+ agentStore,
16865
+ customCommands: state.customCommandStore.getAllCommands(),
16866
+ enableSkillTool
16867
+ });
15824
16868
  const maxIterations = config.preferences.maxIterations === null ? 999999 : config.preferences.maxIterations;
15825
16869
  const agent = new ReActAgent({
15826
16870
  userId: config.userId,
@@ -15926,7 +16970,7 @@ ${contextResult.mergedContent}` : "";
15926
16970
  setStoreSession,
15927
16971
  setState
15928
16972
  ]);
15929
- useEffect6(() => {
16973
+ useEffect7(() => {
15930
16974
  init();
15931
16975
  }, [init]);
15932
16976
  const handleCustomCommandMessage = async (fullTemplate, displayMessage) => {
@@ -16037,6 +17081,7 @@ ${contextResult.mergedContent}` : "";
16037
17081
  completion: 0,
16038
17082
  total: result.completionInfo.totalTokens
16039
17083
  },
17084
+ creditsUsed: result.completionInfo.totalCredits,
16040
17085
  model: state.session.model,
16041
17086
  permissionDenied
16042
17087
  }
@@ -16048,6 +17093,7 @@ ${contextResult.mergedContent}` : "";
16048
17093
  metadata: {
16049
17094
  ...currentSession.metadata,
16050
17095
  totalTokens: currentSession.metadata.totalTokens + result.completionInfo.totalTokens,
17096
+ totalCredits: (currentSession.metadata.totalCredits || 0) + (result.completionInfo.totalCredits || 0),
16051
17097
  toolCallCount: currentSession.metadata.toolCallCount + successfulToolCalls
16052
17098
  }
16053
17099
  };
@@ -16150,7 +17196,12 @@ ${contextResult.mergedContent}` : "";
16150
17196
  const tokenCounter2 = getTokenCounter();
16151
17197
  const contextWindow = tokenCounter2.getContextWindow(activeSession.model, state.availableModels);
16152
17198
  const threshold = contextWindow * 0.8;
16153
- const systemPrompt = buildCoreSystemPrompt(state.contextContent || "");
17199
+ const systemPrompt = buildCoreSystemPrompt({
17200
+ contextContent: state.contextContent,
17201
+ agentStore: state.agentStore || void 0,
17202
+ customCommands: state.customCommandStore.getAllCommands(),
17203
+ enableSkillTool: config?.preferences.enableSkillTool !== false
17204
+ });
16154
17205
  const contextUsage = tokenCounter2.countSessionTokens(activeSession, systemPrompt);
16155
17206
  if (contextUsage.totalTokens >= threshold) {
16156
17207
  console.log("\n\u26A0\uFE0F Context window 80% full. Auto-compacting...\n");
@@ -16245,6 +17296,7 @@ ${contextResult.mergedContent}` : "";
16245
17296
  completion: 0,
16246
17297
  total: result.completionInfo.totalTokens
16247
17298
  },
17299
+ creditsUsed: result.completionInfo.totalCredits,
16248
17300
  steps: result.steps.map(formatStep),
16249
17301
  // Complete history: thoughts, actions, observations
16250
17302
  permissionDenied
@@ -16258,6 +17310,7 @@ ${contextResult.mergedContent}` : "";
16258
17310
  metadata: {
16259
17311
  ...currentSession.metadata,
16260
17312
  totalTokens: currentSession.metadata.totalTokens + result.completionInfo.totalTokens,
17313
+ totalCredits: (currentSession.metadata.totalCredits || 0) + (result.completionInfo.totalCredits || 0),
16261
17314
  toolCallCount: currentSession.metadata.toolCallCount + successfulToolCalls
16262
17315
  }
16263
17316
  };
@@ -16356,7 +17409,8 @@ ${contextResult.mergedContent}` : "";
16356
17409
  prompt: 0,
16357
17410
  completion: 0,
16358
17411
  total: result.completionInfo.totalTokens
16359
- }
17412
+ },
17413
+ creditsUsed: result.completionInfo.totalCredits
16360
17414
  }
16361
17415
  };
16362
17416
  const updatedSession = {
@@ -16366,6 +17420,7 @@ ${contextResult.mergedContent}` : "";
16366
17420
  metadata: {
16367
17421
  ...currentSession.metadata,
16368
17422
  totalTokens: currentSession.metadata.totalTokens + result.completionInfo.totalTokens,
17423
+ totalCredits: (currentSession.metadata.totalCredits || 0) + (result.completionInfo.totalCredits || 0),
16369
17424
  toolCallCount: currentSession.metadata.toolCallCount + successfulToolCalls
16370
17425
  }
16371
17426
  };
@@ -17068,15 +18123,21 @@ No usage data available for the last ${USAGE_DAYS} days.`);
17068
18123
  }
17069
18124
  const tokenCounter2 = getTokenCounter();
17070
18125
  const contextWindow = tokenCounter2.getContextWindow(state.session.model, state.availableModels);
17071
- const corePromptTokens = tokenCounter2.countTokens(buildCoreSystemPrompt(""));
18126
+ const corePromptTokens = tokenCounter2.countTokens(buildCoreSystemPrompt());
17072
18127
  const projectContextTokens = state.contextContent ? tokenCounter2.countTokens(state.contextContent) : 0;
17073
18128
  const commands = state.customCommandStore.getAllCommands();
17074
18129
  const skillsSection = buildSkillsPromptSection(commands);
17075
18130
  const skillsTokens = skillsSection ? tokenCounter2.countTokens(skillsSection) : 0;
18131
+ const agentDirectoryTokens = state.agentStore ? tokenCounter2.countTokens(state.agentStore.getDirectoryContext()) : 0;
17076
18132
  const mcpTools = state.mcpManager?.getTools() || [];
17077
18133
  const mcpToolsTokens = tokenCounter2.countToolSchemaTokens(mcpTools);
17078
18134
  const mcpToolCount = state.mcpManager?.getToolCount() || [];
17079
- const systemPrompt = buildCoreSystemPrompt(state.contextContent || "");
18135
+ const systemPrompt = buildCoreSystemPrompt({
18136
+ contextContent: state.contextContent,
18137
+ agentStore: state.agentStore || void 0,
18138
+ customCommands: commands,
18139
+ enableSkillTool: state.config?.preferences.enableSkillTool !== false
18140
+ });
17080
18141
  const usage = tokenCounter2.countSessionTokens(state.session, systemPrompt);
17081
18142
  const totalWithTools = usage.totalTokens + mcpToolsTokens;
17082
18143
  const usagePercent = totalWithTools / contextWindow * 100;
@@ -17095,6 +18156,10 @@ No usage data available for the last ${USAGE_DAYS} days.`);
17095
18156
  if (commands.length > 0) {
17096
18157
  console.log(` Skills Section: ${skillsTokens.toLocaleString()} tokens (${commands.length} skills)`);
17097
18158
  }
18159
+ if (agentDirectoryTokens > 0) {
18160
+ const agentCount = state.agentStore?.getAgentCount() || 0;
18161
+ console.log(` Agent Directory: ${agentDirectoryTokens.toLocaleString()} tokens (${agentCount} agents)`);
18162
+ }
17098
18163
  if (mcpTools.length > 0) {
17099
18164
  console.log("\nMCP Tools:");
17100
18165
  console.log(` Total: ${mcpToolsTokens.toLocaleString()} tokens (${mcpTools.length} tools)`);
@@ -17499,6 +18564,7 @@ var isDevMode = import.meta.url.includes("/src/") || process.env.NODE_ENV === "d
17499
18564
  if (isDevMode) {
17500
18565
  logger.debug("\u{1F527} Running in development mode (using TypeScript source)\n");
17501
18566
  }
18567
+ warmFileCache();
17502
18568
  render(/* @__PURE__ */ React21.createElement(CliApp, null), {
17503
18569
  exitOnCtrlC: false
17504
18570
  });