@bike4mind/cli 0.2.28 → 0.2.29-cache-mcp-tools-schema-and-refactor.18897

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,16 +5,17 @@ import {
5
5
  getEffectiveApiKey,
6
6
  getOpenWeatherKey,
7
7
  getSerperKey
8
- } from "./chunk-VGYTNJXN.js";
8
+ } from "./chunk-BPXPHQXQ.js";
9
9
  import {
10
- ConfigStore
11
- } from "./chunk-23T2XGSZ.js";
10
+ ConfigStore,
11
+ logger
12
+ } from "./chunk-LBTTUQJM.js";
12
13
  import {
13
14
  selectActiveBackgroundAgents,
14
15
  useCliStore
15
16
  } from "./chunk-TVW4ZESU.js";
16
- import "./chunk-JJBDHUGY.js";
17
- import "./chunk-ZEMWV6IR.js";
17
+ import "./chunk-7DZY5UWH.js";
18
+ import "./chunk-AQMAXCNK.js";
18
19
  import {
19
20
  BFLImageService,
20
21
  BaseStorage,
@@ -26,7 +27,7 @@ import {
26
27
  OpenAIBackend,
27
28
  OpenAIImageService,
28
29
  XAIImageService
29
- } from "./chunk-UNOJBVD2.js";
30
+ } from "./chunk-BAYMEX5W.js";
30
31
  import {
31
32
  AiEvents,
32
33
  ApiKeyEvents,
@@ -82,32 +83,37 @@ import {
82
83
  XAI_IMAGE_MODELS,
83
84
  b4mLLMTools,
84
85
  getMcpProviderMetadata
85
- } from "./chunk-XJRPAAUS.js";
86
+ } from "./chunk-E6AOHC4U.js";
86
87
  import {
87
88
  Logger
88
89
  } from "./chunk-OCYRD7D6.js";
89
90
 
90
91
  // src/index.tsx
91
- import React21, { useState as useState9, useEffect as useEffect6, useCallback as useCallback2, useRef as useRef3 } from "react";
92
+ import React21, { useState as useState10, useEffect as useEffect7, useCallback as useCallback2, useRef as useRef3 } from "react";
92
93
  import { render, Box as Box20, Text as Text20, useApp, useInput as useInput9 } from "ink";
93
94
  import { execSync } from "child_process";
94
95
  import { randomBytes as randomBytes5 } from "crypto";
95
96
  import { v4 as uuidv411 } from "uuid";
96
97
 
97
98
  // src/components/App.tsx
98
- import React15, { useState as useState5, useEffect as useEffect4 } from "react";
99
+ import React15, { useState as useState6, useEffect as useEffect5 } from "react";
99
100
  import { Box as Box14, Text as Text14, Static, useInput as useInput6 } from "ink";
100
101
 
101
102
  // src/components/StatusBar.tsx
102
103
  import React from "react";
103
104
  import { Box, Text } from "ink";
104
- var StatusBar = React.memo(function StatusBar2({ sessionName, model, tokenUsage }) {
105
+ var StatusBar = React.memo(function StatusBar2({
106
+ sessionName,
107
+ model,
108
+ tokenUsage,
109
+ creditsUsage
110
+ }) {
105
111
  const autoAcceptEdits = useCliStore((state) => state.autoAcceptEdits);
106
- 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)));
112
+ 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)));
107
113
  });
108
114
 
109
115
  // src/components/InputPrompt.tsx
110
- import React5, { useState as useState2, useMemo, useEffect as useEffect2, useRef as useRef2 } from "react";
116
+ import React5, { useState as useState3, useMemo, useEffect as useEffect3, useRef as useRef2 } from "react";
111
117
  import { Box as Box4, Text as Text5, useInput as useInput2 } from "ink";
112
118
 
113
119
  // src/components/CustomTextInput.tsx
@@ -320,65 +326,53 @@ function CommandAutocomplete({ commands, selectedIndex }) {
320
326
  // src/components/FileAutocomplete.tsx
321
327
  import React4 from "react";
322
328
  import { Box as Box3, Text as Text4 } from "ink";
329
+ import * as path2 from "path";
323
330
 
324
331
  // src/utils/fileSearch.ts
325
332
  import * as fs from "fs";
326
333
  import * as path from "path";
327
- import Fuse from "fuse.js";
328
- var DEFAULT_IGNORE_PATTERNS = [
329
- "node_modules",
330
- ".git",
331
- ".next",
332
- ".turbo",
333
- "dist",
334
- "build",
335
- ".cache",
336
- "coverage",
337
- ".DS_Store",
338
- ".env",
339
- ".env.local",
340
- ".env.secrets"
341
- ];
342
- function walkDirectory(basePath = process.cwd(), maxDepth = 5, ignorePatterns = DEFAULT_IGNORE_PATTERNS) {
343
- const results = [];
344
- const normalizedBase = path.resolve(basePath);
345
- function walk(currentPath, depth) {
346
- if (depth > maxDepth) return;
334
+ import { AsyncFzf } from "fzf";
335
+ import { fdir } from "fdir";
336
+ import ignore from "ignore";
337
+ function loadIgnoreRules(projectRoot) {
338
+ const ig = ignore();
339
+ ig.add(".git/");
340
+ const gitignorePath = path.join(projectRoot, ".gitignore");
341
+ if (fs.existsSync(gitignorePath)) {
347
342
  try {
348
- const entries = fs.readdirSync(currentPath, { withFileTypes: true });
349
- for (const entry of entries) {
350
- if (ignorePatterns.some((pattern) => entry.name === pattern || entry.name.startsWith(pattern))) {
351
- continue;
352
- }
353
- const fullPath = path.join(currentPath, entry.name);
354
- const relativePath = path.relative(normalizedBase, fullPath);
355
- if (entry.isDirectory()) {
356
- results.push({
357
- path: relativePath,
358
- isDirectory: true
359
- });
360
- walk(fullPath, depth + 1);
361
- } else if (entry.isFile()) {
362
- try {
363
- const stats = fs.statSync(fullPath);
364
- results.push({
365
- path: relativePath,
366
- isDirectory: false,
367
- size: stats.size
368
- });
369
- } catch {
370
- results.push({
371
- path: relativePath,
372
- isDirectory: false
373
- });
374
- }
375
- }
376
- }
343
+ const gitignoreContent = fs.readFileSync(gitignorePath, "utf-8");
344
+ ig.add(gitignoreContent);
377
345
  } catch {
378
346
  }
379
347
  }
380
- walk(normalizedBase, 0);
381
- return results;
348
+ return ig;
349
+ }
350
+ function crawlDirectory(projectRoot, maxDepth = 10, maxFiles = 2e4, ig) {
351
+ let fileCount = 0;
352
+ const crawler = new fdir().withRelativePaths().withDirs().withPathSeparator("/").exclude((dirPath) => {
353
+ const relativePath = path.posix.relative(projectRoot, dirPath);
354
+ if (!relativePath || relativePath === ".") {
355
+ return false;
356
+ }
357
+ const pathWithSlash = `${relativePath}/`;
358
+ return ig.ignores(pathWithSlash);
359
+ }).filter(() => {
360
+ if (fileCount >= maxFiles) {
361
+ return false;
362
+ }
363
+ fileCount++;
364
+ return true;
365
+ });
366
+ if (maxDepth !== void 0) {
367
+ crawler.withMaxDepth(maxDepth);
368
+ }
369
+ const paths = crawler.crawl(projectRoot).sync();
370
+ return paths.filter((p) => {
371
+ if (!p || p === ".") {
372
+ return true;
373
+ }
374
+ return !ig.ignores(p);
375
+ });
382
376
  }
383
377
  function formatFileSize(bytes) {
384
378
  if (bytes < 1024) return `${bytes} B`;
@@ -389,42 +383,112 @@ function formatFileSize(bytes) {
389
383
  var cachedFiles = null;
390
384
  var cacheTimestamp = 0;
391
385
  var CACHE_TTL_MS = 3e4;
392
- function getCachedFiles() {
386
+ function getCachedFiles(projectRoot = process.cwd()) {
393
387
  const now = Date.now();
394
388
  if (!cachedFiles || now - cacheTimestamp > CACHE_TTL_MS) {
395
- cachedFiles = walkDirectory();
389
+ const normalizedBase = path.resolve(projectRoot);
390
+ const ig = loadIgnoreRules(normalizedBase);
391
+ cachedFiles = crawlDirectory(normalizedBase, 10, 2e4, ig);
396
392
  cacheTimestamp = now;
397
393
  }
398
394
  return cachedFiles;
399
395
  }
400
- function searchFiles(query) {
401
- const files = getCachedFiles();
396
+ function warmFileCache() {
397
+ if (!cachedFiles || Date.now() - cacheTimestamp > CACHE_TTL_MS) {
398
+ setImmediate(() => {
399
+ getCachedFiles();
400
+ });
401
+ }
402
+ }
403
+ function listAbsoluteDirectory(absolutePath, filterQuery) {
404
+ try {
405
+ const normalizedPath = path.normalize(absolutePath);
406
+ if (!fs.existsSync(normalizedPath)) {
407
+ return [];
408
+ }
409
+ const stats = fs.statSync(normalizedPath);
410
+ if (!stats.isDirectory()) {
411
+ return [];
412
+ }
413
+ const entries = fs.readdirSync(normalizedPath, { withFileTypes: true });
414
+ let filteredEntries = entries;
415
+ if (filterQuery && filterQuery.length > 0) {
416
+ const lowerQuery = filterQuery.toLowerCase();
417
+ filteredEntries = entries.filter((entry) => entry.name.toLowerCase().includes(lowerQuery));
418
+ }
419
+ return filteredEntries.slice(0, 15).map((entry) => {
420
+ const fullPath = path.join(normalizedPath, entry.name);
421
+ const result = {
422
+ path: fullPath,
423
+ isDirectory: entry.isDirectory()
424
+ };
425
+ if (entry.isFile()) {
426
+ try {
427
+ const fileStats = fs.statSync(fullPath);
428
+ result.size = fileStats.size;
429
+ } catch {
430
+ }
431
+ }
432
+ return result;
433
+ });
434
+ } catch {
435
+ return [];
436
+ }
437
+ }
438
+ async function searchFiles(query, maxResults = 20) {
439
+ const projectRoot = process.cwd();
440
+ if (path.isAbsolute(query)) {
441
+ try {
442
+ const stats = fs.statSync(query);
443
+ if (stats.isDirectory()) {
444
+ return listAbsoluteDirectory(query);
445
+ }
446
+ } catch {
447
+ }
448
+ let dirToList;
449
+ let filterQuery;
450
+ if (query.endsWith("/") || query.endsWith(path.sep)) {
451
+ dirToList = query;
452
+ filterQuery = void 0;
453
+ } else {
454
+ dirToList = path.dirname(query);
455
+ filterQuery = path.basename(query);
456
+ }
457
+ return listAbsoluteDirectory(dirToList, filterQuery);
458
+ }
459
+ const files = getCachedFiles(projectRoot);
402
460
  if (!query || query.trim() === "") {
403
- return files.filter((f) => !f.path.includes(path.sep)).slice(0, 20);
404
- }
405
- const queryDir = path.dirname(query);
406
- let searchPool = files;
407
- if (queryDir && queryDir !== ".") {
408
- const normalizedDir = queryDir.replace(/\\/g, "/");
409
- searchPool = files.filter((f) => {
410
- const normalizedPath = f.path.replace(/\\/g, "/");
411
- return normalizedPath.startsWith(normalizedDir + "/") || normalizedPath === normalizedDir;
461
+ const rootFiles = files.filter((f) => !f.includes("/")).slice(0, maxResults);
462
+ return rootFiles.map((p) => {
463
+ const fullPath = path.join(projectRoot, p);
464
+ try {
465
+ const stats = fs.statSync(fullPath);
466
+ return {
467
+ path: p,
468
+ isDirectory: stats.isDirectory(),
469
+ size: stats.isFile() ? stats.size : void 0
470
+ };
471
+ } catch {
472
+ return { path: p, isDirectory: false };
473
+ }
412
474
  });
413
- if (searchPool.length === 0) {
414
- searchPool = files;
415
- }
416
- }
417
- const fuse = new Fuse(searchPool, {
418
- keys: [{ name: "path", weight: 1 }],
419
- threshold: 0.4,
420
- // Allow some fuzzy matching
421
- includeScore: true,
422
- minMatchCharLength: 1,
423
- // Use extended search for better path matching
424
- useExtendedSearch: false
475
+ }
476
+ const fzf = new AsyncFzf(files);
477
+ const results = await fzf.find(query);
478
+ return results.slice(0, maxResults).map((result) => {
479
+ const p = result.item;
480
+ const fullPath = path.join(projectRoot, p);
481
+ try {
482
+ const stats = fs.statSync(fullPath);
483
+ return {
484
+ path: p,
485
+ isDirectory: stats.isDirectory(),
486
+ size: stats.isFile() ? stats.size : void 0
487
+ };
488
+ } catch {
489
+ return { path: p, isDirectory: false };
490
+ }
425
491
  });
426
- const results = fuse.search(query);
427
- return results.slice(0, 20).map((result) => result.item);
428
492
  }
429
493
  function isPathWithinCwd(filePath) {
430
494
  const cwd = path.resolve(process.cwd());
@@ -508,10 +572,27 @@ var MAX_FILE_SIZE = 10 * 1024 * 1024;
508
572
  // src/components/FileAutocomplete.tsx
509
573
  function FileAutocomplete({ files, selectedIndex, query }) {
510
574
  if (files.length === 0) {
511
- return /* @__PURE__ */ React4.createElement(Box3, { marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "No matching files", query ? ` for "${query}"` : ""));
512
- }
513
- 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) => {
514
- const isSelected = index === selectedIndex;
575
+ if (path2.isAbsolute(query)) {
576
+ 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)"));
577
+ }
578
+ return /* @__PURE__ */ React4.createElement(Box3, { marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "No matching files", query ? ` for "${query}"` : " in root directory"));
579
+ }
580
+ const VIEWPORT_SIZE = 6;
581
+ const totalFiles = files.length;
582
+ let startIndex = 0;
583
+ let endIndex = Math.min(VIEWPORT_SIZE, totalFiles);
584
+ if (totalFiles > VIEWPORT_SIZE) {
585
+ const halfViewport = Math.floor(VIEWPORT_SIZE / 2);
586
+ startIndex = Math.max(0, selectedIndex - halfViewport);
587
+ endIndex = Math.min(totalFiles, startIndex + VIEWPORT_SIZE);
588
+ if (endIndex === totalFiles) {
589
+ startIndex = Math.max(0, totalFiles - VIEWPORT_SIZE);
590
+ }
591
+ }
592
+ const visibleFiles = files.slice(startIndex, endIndex);
593
+ 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) => {
594
+ const actualIndex = startIndex + viewportIndex;
595
+ const isSelected = actualIndex === selectedIndex;
515
596
  const icon = file.isDirectory ? "[folder]" : "[file] ";
516
597
  const sizeDisplay = file.isDirectory ? "" : file.size !== void 0 ? ` (${formatFileSize(file.size)})` : "";
517
598
  const pathDisplay = file.isDirectory ? `${file.path}/` : file.path;
@@ -520,7 +601,7 @@ function FileAutocomplete({ files, selectedIndex, query }) {
520
601
  }
521
602
 
522
603
  // src/utils/fuzzySearch.ts
523
- import Fuse2 from "fuse.js";
604
+ import Fuse from "fuse.js";
524
605
 
525
606
  // src/config/commands.ts
526
607
  var COMMANDS = [
@@ -551,8 +632,9 @@ var COMMANDS = [
551
632
  args: "<name>"
552
633
  },
553
634
  {
554
- name: "sessions",
555
- description: "List saved sessions"
635
+ name: "resume",
636
+ description: "List and resume saved sessions",
637
+ aliases: ["sessions"]
556
638
  },
557
639
  {
558
640
  name: "config",
@@ -696,13 +778,13 @@ function searchCommands(query, commands = COMMANDS) {
696
778
  if (!query || query.trim() === "") {
697
779
  return commands;
698
780
  }
699
- const fuse = new Fuse2(commands, fuseOptions);
781
+ const fuse = new Fuse(commands, fuseOptions);
700
782
  const results = fuse.search(query);
701
783
  return results.map((result) => result.item);
702
784
  }
703
785
 
704
786
  // src/utils/imageDetector.ts
705
- import { readFileSync, existsSync, statSync as statSync2 } from "fs";
787
+ import { readFileSync as readFileSync2, existsSync as existsSync2, statSync as statSync2 } from "fs";
706
788
  import { extname as extname2 } from "path";
707
789
  var ImageInputDetector = class {
708
790
  static {
@@ -843,14 +925,14 @@ var ImageInputDetector = class {
843
925
  let filepath = input.trim();
844
926
  filepath = filepath.replace(/^["']|["']$/g, "");
845
927
  filepath = filepath.replace(/\\(.)/g, "$1");
846
- if (!existsSync(filepath)) return null;
928
+ if (!existsSync2(filepath)) return null;
847
929
  const stats = statSync2(filepath);
848
930
  if (!stats.isFile()) return null;
849
931
  const ext = extname2(filepath).toLowerCase();
850
932
  if (!this.SUPPORTED_EXTENSIONS.includes(ext)) return null;
851
933
  if (stats.size > this.MAX_IMAGE_SIZE) return null;
852
934
  try {
853
- const data = readFileSync(filepath);
935
+ const data = readFileSync2(filepath);
854
936
  const format = ext.substring(1) === "jpeg" ? "jpg" : ext.substring(1);
855
937
  const filename = filepath.split("/").pop() || "image";
856
938
  return {
@@ -905,6 +987,21 @@ var ImageInputDetector = class {
905
987
  }
906
988
  };
907
989
 
990
+ // src/hooks/useDebounce.ts
991
+ import { useEffect as useEffect2, useState as useState2 } from "react";
992
+ function useDebounce(value, delay = 300) {
993
+ const [debouncedValue, setDebouncedValue] = useState2(value);
994
+ useEffect2(() => {
995
+ const timer = setTimeout(() => {
996
+ setDebouncedValue(value);
997
+ }, delay);
998
+ return () => {
999
+ clearTimeout(timer);
1000
+ };
1001
+ }, [value, delay]);
1002
+ return debouncedValue;
1003
+ }
1004
+
908
1005
  // src/components/InputPrompt.tsx
909
1006
  function looksLikeFilePath(input) {
910
1007
  const trimmed = input.trim();
@@ -945,20 +1042,20 @@ function InputPrompt({
945
1042
  const value = useCliStore((state) => state.inputValue);
946
1043
  const setInputValue = useCliStore((state) => state.setInputValue);
947
1044
  const setValue = setInputValue;
948
- const [selectedIndex, setSelectedIndex] = useState2(0);
949
- const [historyIndex, setHistoryIndex] = useState2(-1);
950
- const [tempInput, setTempInput] = useState2("");
1045
+ const [selectedIndex, setSelectedIndex] = useState3(0);
1046
+ const [historyIndex, setHistoryIndex] = useState3(-1);
1047
+ const [tempInput, setTempInput] = useState3("");
951
1048
  const inputKey = useRef2(0);
952
- useEffect2(() => {
1049
+ useEffect3(() => {
953
1050
  if (prefillInput) {
954
1051
  setValue(prefillInput);
955
1052
  onPrefillConsumed?.();
956
1053
  }
957
1054
  }, [prefillInput, onPrefillConsumed]);
958
- const [fileAutocomplete, setFileAutocomplete] = useState2(null);
959
- const [fileSelectedIndex, setFileSelectedIndex] = useState2(0);
1055
+ const [fileAutocomplete, setFileAutocomplete] = useState3(null);
1056
+ const [fileSelectedIndex, setFileSelectedIndex] = useState3(0);
960
1057
  const isBashMode = value.startsWith("!");
961
- useEffect2(() => {
1058
+ useEffect3(() => {
962
1059
  onBashModeChange?.(isBashMode);
963
1060
  }, [isBashMode, onBashModeChange]);
964
1061
  const shouldShowCommandAutocomplete = value.startsWith("/") && !disabled && !fileAutocomplete?.active && !looksLikeFilePath(value);
@@ -967,14 +1064,32 @@ function InputPrompt({
967
1064
  if (!shouldShowCommandAutocomplete) return [];
968
1065
  return searchCommands(commandQuery, commands);
969
1066
  }, [shouldShowCommandAutocomplete, commandQuery, commands]);
970
- const filteredFiles = useMemo(() => {
971
- if (!fileAutocomplete?.active) return [];
972
- return searchFiles(fileAutocomplete.query);
973
- }, [fileAutocomplete?.active, fileAutocomplete?.query]);
974
- useEffect2(() => {
1067
+ const debouncedFileQuery = useDebounce(fileAutocomplete?.query ?? "", 200);
1068
+ const [filteredFiles, setFilteredFiles] = useState3([]);
1069
+ useEffect3(() => {
1070
+ if (!fileAutocomplete?.active) {
1071
+ setFilteredFiles([]);
1072
+ return;
1073
+ }
1074
+ let cancelled = false;
1075
+ searchFiles(debouncedFileQuery).then((results) => {
1076
+ if (!cancelled) {
1077
+ setFilteredFiles(results);
1078
+ }
1079
+ }).catch((error) => {
1080
+ if (!cancelled) {
1081
+ console.error("File search error:", error);
1082
+ setFilteredFiles([]);
1083
+ }
1084
+ });
1085
+ return () => {
1086
+ cancelled = true;
1087
+ };
1088
+ }, [fileAutocomplete?.active, debouncedFileQuery]);
1089
+ useEffect3(() => {
975
1090
  setSelectedIndex(0);
976
1091
  }, [filteredCommands]);
977
- useEffect2(() => {
1092
+ useEffect3(() => {
978
1093
  setFileSelectedIndex(0);
979
1094
  }, [filteredFiles]);
980
1095
  useInput2(
@@ -1112,11 +1227,11 @@ function InputPrompt({
1112
1227
  }
1113
1228
  };
1114
1229
  const getPlaceholder = () => {
1115
- if (disabled) return "Thinking...";
1230
+ if (disabled) return "";
1116
1231
  if (isBashMode) return "Enter shell command to execute...";
1117
1232
  return "Type your message, /help for commands, @file to reference, or ! for bash";
1118
1233
  };
1119
- return /* @__PURE__ */ React5.createElement(Box4, { flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Box4, null, /* @__PURE__ */ React5.createElement(Text5, { bold: true, color: isBashMode ? "yellow" : "cyan" }, isBashMode ? "$ " : "> "), /* @__PURE__ */ React5.createElement(
1234
+ return /* @__PURE__ */ React5.createElement(Box4, { flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Box4, null, /* @__PURE__ */ React5.createElement(Text5, { bold: true, color: disabled ? "gray" : isBashMode ? "yellow" : "cyan", dimColor: disabled }, isBashMode ? "$ " : "> "), /* @__PURE__ */ React5.createElement(
1120
1235
  CustomTextInput,
1121
1236
  {
1122
1237
  key: inputKey.current,
@@ -1139,7 +1254,7 @@ import React6 from "react";
1139
1254
  import { Box as Box5, Text as Text6 } from "ink";
1140
1255
  import Spinner from "ink-spinner";
1141
1256
  var ThoughtStream = React6.memo(function ThoughtStream2({ isThinking }) {
1142
- return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column", gap: 1 }, isThinking && /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, /* @__PURE__ */ React6.createElement(Spinner, { type: "dots" })), /* @__PURE__ */ React6.createElement(Text6, null, " Thinking...")));
1257
+ return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column" }, isThinking && /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, /* @__PURE__ */ React6.createElement(Spinner, { type: "dots" })), /* @__PURE__ */ React6.createElement(Text6, null, " Thinking...")));
1143
1258
  });
1144
1259
 
1145
1260
  // src/components/AgentThinking.tsx
@@ -1212,13 +1327,13 @@ function BackgroundAgentStatus() {
1212
1327
  }
1213
1328
 
1214
1329
  // src/components/CompletedGroupNotification.tsx
1215
- import React9, { useEffect as useEffect3 } from "react";
1330
+ import React9, { useEffect as useEffect4 } from "react";
1216
1331
  import { Box as Box8, Text as Text8 } from "ink";
1217
1332
  var NOTIFICATION_DISPLAY_DURATION_MS = 3e3;
1218
1333
  function CompletedGroupNotification() {
1219
1334
  const notifications = useCliStore((state) => state.completedGroupNotifications);
1220
1335
  const clearNotifications = useCliStore((state) => state.clearCompletedGroupNotifications);
1221
- useEffect3(() => {
1336
+ useEffect4(() => {
1222
1337
  if (notifications.length > 0) {
1223
1338
  const timer = setTimeout(() => {
1224
1339
  clearNotifications();
@@ -1231,7 +1346,7 @@ function CompletedGroupNotification() {
1231
1346
  }
1232
1347
 
1233
1348
  // src/components/PermissionPrompt.tsx
1234
- import React10, { useState as useState3, useCallback } from "react";
1349
+ import React10, { useState as useState4, useCallback } from "react";
1235
1350
  import { Box as Box9, Text as Text9, useInput as useInput3 } from "ink";
1236
1351
  function renderDiffPreview(preview) {
1237
1352
  const lines = preview.split("\n");
@@ -1264,16 +1379,37 @@ function PermissionPrompt({
1264
1379
  { label: "\u2713 Allow once", value: "allow-once" },
1265
1380
  { label: "\u2717 Deny", value: "deny" }
1266
1381
  ];
1267
- const [selectedIndex, setSelectedIndex] = useState3(0);
1268
- const [responded, setResponded] = useState3(false);
1382
+ const [selectedIndex, setSelectedIndex] = useState4(0);
1383
+ const [responded, setResponded] = useState4(false);
1269
1384
  const handleSelect = useCallback(() => {
1270
1385
  if (responded) return;
1271
1386
  setResponded(true);
1272
1387
  onResponse(items[selectedIndex].value);
1273
1388
  }, [responded, onResponse, items, selectedIndex]);
1389
+ const handleSelectIndex = useCallback(
1390
+ (index) => {
1391
+ if (responded) return;
1392
+ setResponded(true);
1393
+ onResponse(items[index].value);
1394
+ },
1395
+ [responded, onResponse, items]
1396
+ );
1274
1397
  useInput3(
1275
- (_input, key) => {
1398
+ (input, key) => {
1276
1399
  if (responded) return;
1400
+ const num = parseInt(input, 10);
1401
+ if (num >= 1 && num <= items.length) {
1402
+ handleSelectIndex(num - 1);
1403
+ return;
1404
+ }
1405
+ if (input.toLowerCase() === "y") {
1406
+ handleSelectIndex(0);
1407
+ return;
1408
+ }
1409
+ if (input.toLowerCase() === "n") {
1410
+ handleSelectIndex(items.length - 1);
1411
+ return;
1412
+ }
1277
1413
  if (key.upArrow) {
1278
1414
  setSelectedIndex((i) => i > 0 ? i - 1 : items.length - 1);
1279
1415
  } else if (key.downArrow) {
@@ -1288,11 +1424,11 @@ function PermissionPrompt({
1288
1424
  const rawArgsString = typeof args === "string" ? args : JSON.stringify(args, null, 2);
1289
1425
  const argsString = rawArgsString.length > MAX_ARGS_LENGTH ? rawArgsString.slice(0, MAX_ARGS_LENGTH) + `
1290
1426
  ... (${rawArgsString.length - MAX_ARGS_LENGTH} more chars)` : rawArgsString;
1291
- return /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column", borderStyle: "bold", borderColor: "yellow", padding: 1, marginY: 1 }, /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Text9, { bold: true, color: "yellow" }, "\u26A0\uFE0F Permission Required")), /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "Tool: "), /* @__PURE__ */ React10.createElement(Text9, { bold: true, color: "cyan" }, toolName)), toolDescription && /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "Action: "), /* @__PURE__ */ React10.createElement(Text9, null, toolDescription)), /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { bold: true }, "Arguments:"), /* @__PURE__ */ React10.createElement(Box9, { paddingLeft: 2, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, argsString))), preview && /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { bold: true }, "Preview:"), /* @__PURE__ */ React10.createElement(Box9, { borderStyle: "single", borderColor: "gray", paddingX: 1, flexDirection: "column" }, renderDiffPreview(preview))), !canBeTrusted && /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(Text9, { color: "red", dimColor: true }, "Note: This tool cannot be trusted due to its dangerous nature.")), /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1, flexDirection: "column" }, items.map((item, index) => /* @__PURE__ */ React10.createElement(Box9, { key: item.value }, /* @__PURE__ */ React10.createElement(Text9, { color: index === selectedIndex ? "cyan" : void 0, bold: index === selectedIndex }, index === selectedIndex ? "\u276F " : " ", item.label)))));
1427
+ return /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column", borderStyle: "bold", borderColor: "yellow", padding: 1, marginY: 1 }, /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Text9, { bold: true, color: "yellow" }, "\u26A0\uFE0F Permission Required")), /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "Tool: "), /* @__PURE__ */ React10.createElement(Text9, { bold: true, color: "cyan" }, toolName)), toolDescription && /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "Action: "), /* @__PURE__ */ React10.createElement(Text9, null, toolDescription)), /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { bold: true }, "Arguments:"), /* @__PURE__ */ React10.createElement(Box9, { paddingLeft: 2, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, argsString))), preview && /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { bold: true }, "Preview:"), /* @__PURE__ */ React10.createElement(Box9, { borderStyle: "single", borderColor: "gray", paddingX: 1, flexDirection: "column" }, renderDiffPreview(preview))), !canBeTrusted && /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(Text9, { color: "red", dimColor: true }, "Note: This tool cannot be trusted due to its dangerous nature.")), /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1, flexDirection: "column" }, items.map((item, index) => /* @__PURE__ */ React10.createElement(Box9, { key: item.value }, /* @__PURE__ */ React10.createElement(Text9, { color: "cyan" }, index + 1, "."), /* @__PURE__ */ React10.createElement(Text9, { color: index === selectedIndex ? "cyan" : void 0, bold: index === selectedIndex }, index === selectedIndex ? " \u276F " : " ", item.label)))), /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "Press 1-", items.length, ", y/n, or \u2191\u2193 + Enter")));
1292
1428
  }
1293
1429
 
1294
1430
  // src/components/ConfigEditor.tsx
1295
- import React11, { useState as useState4, useMemo as useMemo3 } from "react";
1431
+ import React11, { useState as useState5, useMemo as useMemo3 } from "react";
1296
1432
  import { Box as Box10, Text as Text10, useInput as useInput4 } from "ink";
1297
1433
  var MAX_ITERATIONS_OPTIONS = [
1298
1434
  { label: "10", value: 10 },
@@ -1437,10 +1573,10 @@ function buildConfigItems(availableModels) {
1437
1573
  return items;
1438
1574
  }
1439
1575
  function ConfigEditor({ config, availableModels, onSave, onClose }) {
1440
- const [selectedIndex, setSelectedIndex] = useState4(0);
1441
- const [editedConfig, setEditedConfig] = useState4(config);
1442
- const [saveError, setSaveError] = useState4(null);
1443
- const [isSaving, setIsSaving] = useState4(false);
1576
+ const [selectedIndex, setSelectedIndex] = useState5(0);
1577
+ const [editedConfig, setEditedConfig] = useState5(config);
1578
+ const [saveError, setSaveError] = useState5(null);
1579
+ const [isSaving, setIsSaving] = useState5(false);
1444
1580
  const configItems = useMemo3(() => buildConfigItems(availableModels), [availableModels]);
1445
1581
  const hasChanges = useMemo3(() => {
1446
1582
  return JSON.stringify(config.preferences) !== JSON.stringify(editedConfig.preferences) || config.defaultModel !== editedConfig.defaultModel;
@@ -1707,38 +1843,26 @@ function truncateValue(value, maxLength) {
1707
1843
  }
1708
1844
  return str.slice(0, maxLength) + "...";
1709
1845
  }
1710
- function getRoleDisplay(role) {
1711
- switch (role) {
1712
- case "user":
1713
- return { color: "cyan", label: "\u{1F464} You" };
1714
- case "system":
1715
- return { color: "gray", label: "\u2139\uFE0F System" };
1716
- default:
1717
- return { color: "green", label: "\u{1F916} Assistant" };
1718
- }
1719
- }
1720
1846
  var MessageItem = React14.memo(function MessageItem2({ message }) {
1721
1847
  const isUser = message.role === "user";
1722
- const isContinuation = message.metadata?.isContinuation === true;
1723
- const roleDisplay = getRoleDisplay(message.role);
1724
- return /* @__PURE__ */ React14.createElement(Box13, { flexDirection: "column", marginBottom: 1 }, !isContinuation && /* @__PURE__ */ React14.createElement(Box13, null, /* @__PURE__ */ React14.createElement(Text13, { bold: true, color: roleDisplay.color }, roleDisplay.label), isUser && /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, " \u2022 ", new Date(message.timestamp).toLocaleTimeString())), isUser && message.content && /* @__PURE__ */ React14.createElement(Box13, { paddingLeft: 2 }, /* @__PURE__ */ React14.createElement(Text13, { backgroundColor: "whiteBright", color: "black" }, " ", message.content, " ")), !isUser && message.metadata?.steps && message.metadata.steps.length > 0 && /* @__PURE__ */ React14.createElement(Box13, { paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React14.createElement(Text13, { dimColor: true, bold: true }, "\u{1F527} Agent Reasoning Trace (", message.metadata.steps.filter((s) => s.type === "action").length, " ", "tools used, ", message.metadata.steps.length, " total steps)", message.metadata.tokenUsage && ` \u2022 ${message.metadata.tokenUsage.total} tokens`), /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "Step types: ", message.metadata.steps.map((s) => s.type).join(", ")), message.metadata.steps.map((step, idx) => {
1848
+ return /* @__PURE__ */ React14.createElement(Box13, { flexDirection: "column" }, isUser && message.content && /* @__PURE__ */ React14.createElement(Box13, { marginBottom: 1 }, /* @__PURE__ */ React14.createElement(Text13, { backgroundColor: "whiteBright", color: "black" }, "\u276F ", message.content, " ")), !isUser && message.metadata?.steps && message.metadata.steps.filter((s) => ["thought", "action"].includes(s.type)).length > 0 && /* @__PURE__ */ React14.createElement(Box13, { paddingLeft: 2, flexDirection: "column", marginBottom: 1 }, message.metadata.steps.map((step, idx) => {
1725
1849
  if (step.type === "thought") {
1726
- return /* @__PURE__ */ React14.createElement(Box13, { key: idx, paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React14.createElement(Text13, { color: "blue" }, "\u{1F4AD} Thought:"), /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, ` ${step.content}`));
1850
+ return /* @__PURE__ */ React14.createElement(Box13, { key: idx, flexDirection: "column" }, /* @__PURE__ */ React14.createElement(Text13, { color: "blue" }, "\u{1F4AD} Thought:"), /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, ` ${step.content}`));
1727
1851
  }
1728
1852
  if (step.type === "action") {
1729
1853
  const toolName = step.metadata?.toolName || "unknown";
1730
1854
  const toolInput = step.metadata?.toolInput;
1731
1855
  const observationStep = message.metadata?.steps?.[idx + 1];
1732
1856
  const result = observationStep?.type === "observation" ? observationStep.content : null;
1733
- return /* @__PURE__ */ React14.createElement(Box13, { key: idx, paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React14.createElement(Text13, { color: "yellow" }, "\u{1F527} Action: ", toolName), toolInput && /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, ` Input: ${truncateValue(toolInput, 100)}`), result && /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, ` Result: ${truncateValue(result, 200)}`));
1857
+ 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)}`));
1734
1858
  }
1735
1859
  return null;
1736
- }).filter(Boolean)), !isUser && message.content !== "..." && /* @__PURE__ */ React14.createElement(Box13, { paddingLeft: 2, marginTop: message.metadata?.steps?.length ? 1 : 0 }, 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`)));
1860
+ }).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"}` : "", ")")));
1737
1861
  });
1738
1862
 
1739
1863
  // src/utils/processFileReferences.ts
1740
1864
  import * as fs2 from "fs";
1741
- import * as path2 from "path";
1865
+ import * as path3 from "path";
1742
1866
 
1743
1867
  // src/utils/constants.ts
1744
1868
  var NAME_SUFFIXES = ["jr", "sr", "ii", "iii", "iv", "v", "phd", "md", "esq"];
@@ -1749,7 +1873,7 @@ function isNameSuffix(value) {
1749
1873
  // src/utils/processFileReferences.ts
1750
1874
  var FILE_REFERENCE_REGEX = /(?:^|\s)@([^\s@]+)/g;
1751
1875
  function looksLikeFilePath2(ref) {
1752
- if (ref.includes("/") || ref.includes(path2.sep)) {
1876
+ if (ref.includes("/") || ref.includes(path3.sep)) {
1753
1877
  return true;
1754
1878
  }
1755
1879
  const extensionMatch = /\.(\w+)$/.exec(ref);
@@ -1779,9 +1903,13 @@ function extractFileReferences(message) {
1779
1903
  }
1780
1904
  function readFileContents(filePath) {
1781
1905
  const cwd = process.cwd();
1782
- const absolutePath = path2.resolve(cwd, filePath);
1783
- if (!isPathWithinCwd(filePath)) {
1784
- return { error: `Security: Path "${filePath}" is outside the current working directory` };
1906
+ const isAbsolutePath = path3.isAbsolute(filePath);
1907
+ if (filePath.includes("..")) {
1908
+ return { error: `Security: Path traversal detected in "${filePath}"` };
1909
+ }
1910
+ const absolutePath = isAbsolutePath ? path3.normalize(filePath) : path3.resolve(cwd, filePath);
1911
+ if (!isAbsolutePath && !isPathWithinCwd(filePath)) {
1912
+ return { error: `Security: Relative path "${filePath}" escapes the current working directory` };
1785
1913
  }
1786
1914
  if (!fs2.existsSync(absolutePath)) {
1787
1915
  return { error: `File not found: "${filePath}"` };
@@ -1872,6 +2000,7 @@ function App({
1872
2000
  const sessionName = useCliStore((state) => state.session?.name || "New Session");
1873
2001
  const currentModel = useCliStore((state) => state.session?.model || "claude-sonnet-4-5-20250929");
1874
2002
  const totalTokens = useCliStore((state) => state.session?.metadata.totalTokens || 0);
2003
+ const totalCredits = useCliStore((state) => state.session?.metadata.totalCredits);
1875
2004
  const isThinking = useCliStore((state) => state.isThinking);
1876
2005
  const permissionPrompt = useCliStore((state) => state.permissionPrompt);
1877
2006
  const showConfigEditor = useCliStore((state) => state.showConfigEditor);
@@ -1882,7 +2011,7 @@ function App({
1882
2011
  const setIsThinking = useCliStore((state) => state.setIsThinking);
1883
2012
  const pendingBackgroundTrigger = useCliStore((state) => state.pendingBackgroundTrigger);
1884
2013
  const setPendingBackgroundTrigger = useCliStore((state) => state.setPendingBackgroundTrigger);
1885
- useEffect4(() => {
2014
+ useEffect5(() => {
1886
2015
  if (pendingBackgroundTrigger && !isThinking && onBackgroundCompletion) {
1887
2016
  setPendingBackgroundTrigger(false);
1888
2017
  onBackgroundCompletion();
@@ -1894,7 +2023,7 @@ function App({
1894
2023
  toggleAutoAcceptEdits();
1895
2024
  }
1896
2025
  });
1897
- const [isBashMode, setIsBashMode] = useState5(false);
2026
+ const [isBashMode, setIsBashMode] = useState6(false);
1898
2027
  const handleSubmit = React15.useCallback(
1899
2028
  async (input) => {
1900
2029
  const trimmed = input.trim();
@@ -1957,7 +2086,15 @@ ${errorBlock}`;
1957
2086
  onPrefillConsumed,
1958
2087
  onBashModeChange: setIsBashMode
1959
2088
  }
1960
- )), /* @__PURE__ */ React15.createElement(StatusBar, { sessionName, model: currentModel, tokenUsage: totalTokens })));
2089
+ )), /* @__PURE__ */ React15.createElement(
2090
+ StatusBar,
2091
+ {
2092
+ sessionName,
2093
+ model: currentModel,
2094
+ tokenUsage: totalTokens,
2095
+ creditsUsage: totalCredits
2096
+ }
2097
+ )));
1961
2098
  }
1962
2099
 
1963
2100
  // src/components/MessageList.tsx
@@ -2033,12 +2170,12 @@ function TrustLocationSelector({ inProject, onSelect, onCancel }) {
2033
2170
  }
2034
2171
 
2035
2172
  // src/components/RewindSelector.tsx
2036
- import React18, { useState as useState6 } from "react";
2173
+ import React18, { useState as useState7 } from "react";
2037
2174
  import { Box as Box17, Text as Text17, useInput as useInput7 } from "ink";
2038
2175
  import SelectInput2 from "ink-select-input";
2039
2176
  function RewindSelector({ messages, onSelect, onCancel }) {
2040
- const [step, setStep] = useState6("selection");
2041
- const [selectedMessageIndex, setSelectedMessageIndex] = useState6(null);
2177
+ const [step, setStep] = useState7("selection");
2178
+ const [selectedMessageIndex, setSelectedMessageIndex] = useState7(null);
2042
2179
  useInput7((input, key) => {
2043
2180
  if (key.escape) {
2044
2181
  if (step === "confirmation") {
@@ -2102,12 +2239,12 @@ function RewindSelector({ messages, onSelect, onCancel }) {
2102
2239
  }
2103
2240
 
2104
2241
  // src/components/SessionSelector.tsx
2105
- import React19, { useState as useState7 } from "react";
2242
+ import React19, { useState as useState8 } from "react";
2106
2243
  import { Box as Box18, Text as Text18, useInput as useInput8 } from "ink";
2107
2244
  import SelectInput3 from "ink-select-input";
2108
2245
  function SessionSelector({ sessions, currentSession, onSelect, onCancel }) {
2109
- const [step, setStep] = useState7("selection");
2110
- const [selectedSession, setSelectedSession] = useState7(null);
2246
+ const [step, setStep] = useState8("selection");
2247
+ const [selectedSession, setSelectedSession] = useState8(null);
2111
2248
  useInput8((_input, key) => {
2112
2249
  if (key.escape) {
2113
2250
  if (step === "confirmation") {
@@ -2163,7 +2300,7 @@ function SessionSelector({ sessions, currentSession, onSelect, onCancel }) {
2163
2300
  {
2164
2301
  items,
2165
2302
  onSelect: handleSelectionSelect,
2166
- itemComponent: ({ isSelected, label }) => /* @__PURE__ */ React19.createElement(Box18, null, /* @__PURE__ */ React19.createElement(Text18, { color: isSelected ? "cyan" : void 0 }, isSelected ? "\u276F " : " ", label))
2303
+ itemComponent: ({ isSelected, label }) => /* @__PURE__ */ React19.createElement(Box18, null, /* @__PURE__ */ React19.createElement(Text18, { color: isSelected ? "cyan" : void 0 }, label))
2167
2304
  }
2168
2305
  ), /* @__PURE__ */ React19.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text18, { dimColor: true }, "Use \u2191\u2193 arrows to navigate, Enter to select, Esc to cancel")));
2169
2306
  }
@@ -2176,13 +2313,13 @@ function SessionSelector({ sessions, currentSession, onSelect, onCancel }) {
2176
2313
  {
2177
2314
  items: confirmationItems,
2178
2315
  onSelect: handleConfirmationSelect,
2179
- itemComponent: ({ isSelected, label }) => /* @__PURE__ */ React19.createElement(Box18, null, /* @__PURE__ */ React19.createElement(Text18, { color: isSelected ? "cyan" : void 0 }, isSelected ? "\u276F " : " ", label))
2316
+ itemComponent: ({ isSelected, label }) => /* @__PURE__ */ React19.createElement(Box18, null, /* @__PURE__ */ React19.createElement(Text18, { color: isSelected ? "cyan" : void 0 }, label))
2180
2317
  }
2181
2318
  ), /* @__PURE__ */ React19.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text18, { dimColor: true }, "Use \u2191\u2193 arrows to navigate, Enter to select, Esc to go back")));
2182
2319
  }
2183
2320
 
2184
2321
  // src/components/LoginFlow.tsx
2185
- import React20, { useState as useState8, useEffect as useEffect5 } from "react";
2322
+ import React20, { useState as useState9, useEffect as useEffect6 } from "react";
2186
2323
  import { Box as Box19, Text as Text19 } from "ink";
2187
2324
  import Spinner3 from "ink-spinner";
2188
2325
  import jwt from "jsonwebtoken";
@@ -2299,11 +2436,11 @@ var OAuthClient = class {
2299
2436
 
2300
2437
  // src/components/LoginFlow.tsx
2301
2438
  function LoginFlow({ apiUrl = "http://localhost:3000", configStore, onSuccess, onError }) {
2302
- const [status, setStatus] = useState8("initiating");
2303
- const [deviceFlow, setDeviceFlow] = useState8(null);
2304
- const [statusMessage, setStatusMessage] = useState8("Initiating device authorization...");
2305
- const [error, setError] = useState8(null);
2306
- useEffect5(() => {
2439
+ const [status, setStatus] = useState9("initiating");
2440
+ const [deviceFlow, setDeviceFlow] = useState9(null);
2441
+ const [statusMessage, setStatusMessage] = useState9("Initiating device authorization...");
2442
+ const [error, setError] = useState9(null);
2443
+ useEffect6(() => {
2307
2444
  const runLoginFlow = async () => {
2308
2445
  const oauth = new OAuthClient(apiUrl);
2309
2446
  try {
@@ -2338,7 +2475,7 @@ function LoginFlow({ apiUrl = "http://localhost:3000", configStore, onSuccess, o
2338
2475
  };
2339
2476
  runLoginFlow();
2340
2477
  }, [apiUrl, configStore, onSuccess, onError]);
2341
- useEffect5(() => {
2478
+ useEffect6(() => {
2342
2479
  if (deviceFlow && status === "waiting") {
2343
2480
  open(deviceFlow.verification_uri_complete).catch((err) => {
2344
2481
  console.error("Failed to auto-open browser:", err);
@@ -2359,12 +2496,12 @@ function LoginFlow({ apiUrl = "http://localhost:3000", configStore, onSuccess, o
2359
2496
 
2360
2497
  // src/storage/SessionStore.ts
2361
2498
  import { promises as fs3 } from "fs";
2362
- import path3 from "path";
2499
+ import path4 from "path";
2363
2500
  import { homedir } from "os";
2364
2501
  import { v4 as uuidv4 } from "uuid";
2365
2502
  var SessionStore = class {
2366
2503
  constructor(basePath) {
2367
- this.basePath = basePath || path3.join(homedir(), ".bike4mind", "sessions");
2504
+ this.basePath = basePath || path4.join(homedir(), ".bike4mind", "sessions");
2368
2505
  }
2369
2506
  /**
2370
2507
  * Initialize storage directory
@@ -2385,7 +2522,7 @@ var SessionStore = class {
2385
2522
  throw new Error("Cannot save session with no messages");
2386
2523
  }
2387
2524
  await this.init();
2388
- const filePath = path3.join(this.basePath, `${session.id}.json`);
2525
+ const filePath = path4.join(this.basePath, `${session.id}.json`);
2389
2526
  try {
2390
2527
  await fs3.writeFile(filePath, JSON.stringify(session, null, 2), "utf-8");
2391
2528
  } catch (error) {
@@ -2397,7 +2534,7 @@ var SessionStore = class {
2397
2534
  * Load a session from disk by ID
2398
2535
  */
2399
2536
  async load(id) {
2400
- const filePath = path3.join(this.basePath, `${id}.json`);
2537
+ const filePath = path4.join(this.basePath, `${id}.json`);
2401
2538
  try {
2402
2539
  const data = await fs3.readFile(filePath, "utf-8");
2403
2540
  const session = JSON.parse(data);
@@ -2435,7 +2572,7 @@ var SessionStore = class {
2435
2572
  const jsonFiles = files.filter((f) => f.endsWith(".json"));
2436
2573
  const sessionsWithFiles = await Promise.all(
2437
2574
  jsonFiles.map(async (file) => {
2438
- const filePath = path3.join(this.basePath, file);
2575
+ const filePath = path4.join(this.basePath, file);
2439
2576
  const data = await fs3.readFile(filePath, "utf-8");
2440
2577
  const session = JSON.parse(data);
2441
2578
  session.messages = session.messages.map((msg) => {
@@ -2470,7 +2607,7 @@ var SessionStore = class {
2470
2607
  * Delete a session
2471
2608
  */
2472
2609
  async delete(id) {
2473
- const filePath = path3.join(this.basePath, `${id}.json`);
2610
+ const filePath = path4.join(this.basePath, `${id}.json`);
2474
2611
  try {
2475
2612
  await fs3.unlink(filePath);
2476
2613
  return true;
@@ -2509,19 +2646,19 @@ var SessionStore = class {
2509
2646
 
2510
2647
  // src/storage/CommandHistoryStore.ts
2511
2648
  import { promises as fs4 } from "fs";
2512
- import path4 from "path";
2649
+ import path5 from "path";
2513
2650
  import { homedir as homedir2 } from "os";
2514
2651
  var MAX_HISTORY_ENTRIES = 1e3;
2515
2652
  var CommandHistoryStore = class {
2516
2653
  constructor(historyPath) {
2517
2654
  this.history = null;
2518
- this.historyPath = historyPath || path4.join(homedir2(), ".bike4mind", "history.jsonl");
2655
+ this.historyPath = historyPath || path5.join(homedir2(), ".bike4mind", "history.jsonl");
2519
2656
  }
2520
2657
  /**
2521
2658
  * Initialize history directory
2522
2659
  */
2523
2660
  async init() {
2524
- const dir = path4.dirname(this.historyPath);
2661
+ const dir = path5.dirname(this.historyPath);
2525
2662
  try {
2526
2663
  await fs4.mkdir(dir, { recursive: true });
2527
2664
  } catch (error) {
@@ -2623,271 +2760,13 @@ var CommandHistoryStore = class {
2623
2760
  };
2624
2761
 
2625
2762
  // src/storage/CustomCommandStore.ts
2626
- import fs6 from "fs/promises";
2763
+ import fs5 from "fs/promises";
2627
2764
  import path6 from "path";
2628
- import os2 from "os";
2765
+ import os from "os";
2629
2766
 
2630
2767
  // src/utils/commandParser.ts
2631
2768
  import matter from "gray-matter";
2632
2769
  import { z } from "zod";
2633
-
2634
- // src/utils/Logger.ts
2635
- import fs5 from "fs/promises";
2636
- import path5 from "path";
2637
- import os from "os";
2638
- var Logger2 = class _Logger {
2639
- constructor() {
2640
- this.logFilePath = null;
2641
- this.sessionId = null;
2642
- this.fileLoggingEnabled = true;
2643
- this.consoleVerbose = false;
2644
- }
2645
- static {
2646
- this.instance = null;
2647
- }
2648
- static getInstance() {
2649
- if (!_Logger.instance) {
2650
- _Logger.instance = new _Logger();
2651
- }
2652
- return _Logger.instance;
2653
- }
2654
- /**
2655
- * Initialize the logger with a session ID
2656
- */
2657
- async initialize(sessionId) {
2658
- this.sessionId = sessionId;
2659
- const debugDir = path5.join(os.homedir(), ".bike4mind", "debug");
2660
- await fs5.mkdir(debugDir, { recursive: true });
2661
- this.logFilePath = path5.join(debugDir, `${sessionId}.txt`);
2662
- await this.writeToFile("INFO", "=== CLI SESSION START ===");
2663
- }
2664
- /**
2665
- * Set whether verbose console logging is enabled
2666
- */
2667
- setVerbose(enabled) {
2668
- this.consoleVerbose = enabled;
2669
- }
2670
- /**
2671
- * Set whether file logging is enabled
2672
- */
2673
- setFileLoggingEnabled(enabled) {
2674
- this.fileLoggingEnabled = enabled;
2675
- }
2676
- /**
2677
- * DEBUG level - verbose-only console, always file
2678
- */
2679
- debug(message) {
2680
- this.writeToFile("DEBUG", message).catch(() => {
2681
- });
2682
- if (this.consoleVerbose) {
2683
- console.log(message);
2684
- }
2685
- }
2686
- /**
2687
- * INFO level - always shown to user
2688
- */
2689
- info(message) {
2690
- this.writeToFile("INFO", message).catch(() => {
2691
- });
2692
- console.log(message);
2693
- }
2694
- /**
2695
- * WARN level - always shown to user
2696
- */
2697
- warn(message) {
2698
- this.writeToFile("WARN", message).catch(() => {
2699
- });
2700
- console.warn(message);
2701
- }
2702
- /**
2703
- * ERROR level - always shown to user
2704
- */
2705
- error(message, err) {
2706
- this.writeToFile("ERROR", message).catch(() => {
2707
- });
2708
- if (err) {
2709
- this.logErrorDetailsToFile(err).catch(() => {
2710
- });
2711
- }
2712
- console.error(message);
2713
- }
2714
- /**
2715
- * Write log entry to file
2716
- */
2717
- async writeToFile(level, message) {
2718
- if (!this.fileLoggingEnabled || !this.logFilePath) {
2719
- return;
2720
- }
2721
- try {
2722
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19);
2723
- const logEntry = `[${timestamp}] [${level}] ${message}
2724
- `;
2725
- await fs5.appendFile(this.logFilePath, logEntry, "utf-8");
2726
- } catch (error) {
2727
- console.error("File logging failed:", error);
2728
- }
2729
- }
2730
- /**
2731
- * Log error details to file
2732
- */
2733
- async logErrorDetailsToFile(err) {
2734
- if (!this.fileLoggingEnabled || !this.logFilePath) {
2735
- return;
2736
- }
2737
- try {
2738
- if (err && typeof err === "object" && "response" in err && err.response) {
2739
- const response = err.response;
2740
- const config = err && typeof err === "object" && "config" in err ? err.config : void 0;
2741
- await this.writeToFile("ERROR", ` Status: ${response.status} ${response.statusText || ""}`);
2742
- await this.writeToFile("ERROR", ` URL: ${config?.url || "unknown"}`);
2743
- await this.writeToFile("ERROR", ` Headers: ${this.safeStringify(response.headers)}`);
2744
- if (response.data) {
2745
- const errorText = this.extractErrorMessage(response.data);
2746
- if (errorText.trim().startsWith("<!DOCTYPE") || errorText.trim().startsWith("<html")) {
2747
- await this.writeToFile("ERROR", ` Response Type: HTML Error Page`);
2748
- const parsedError = this.parseHtmlError(errorText);
2749
- if (parsedError) {
2750
- await this.writeToFile("ERROR", ` Error Message: ${parsedError}`);
2751
- }
2752
- await this.writeToFile("ERROR", ` Raw HTML: ${this.truncate(errorText, 1e3)}`);
2753
- } else {
2754
- const preview = this.truncate(errorText, 500);
2755
- await this.writeToFile("ERROR", ` Response: ${preview}`);
2756
- }
2757
- } else {
2758
- await this.writeToFile("ERROR", ` Response: (no data)`);
2759
- }
2760
- }
2761
- if (err && typeof err === "object" && "stack" in err && typeof err.stack === "string") {
2762
- const stackLines = err.stack.split("\n").slice(0, 5).join("\n ");
2763
- await this.writeToFile("ERROR", ` Stack: ${stackLines}`);
2764
- } else if (err && typeof err === "object" && "message" in err && typeof err.message === "string") {
2765
- await this.writeToFile("ERROR", ` Message: ${err.message}`);
2766
- }
2767
- } catch (error) {
2768
- }
2769
- }
2770
- /**
2771
- * Safely stringify object, handling circular references
2772
- */
2773
- safeStringify(obj) {
2774
- try {
2775
- return JSON.stringify(obj);
2776
- } catch (error) {
2777
- if (error instanceof Error && error.message.includes("circular")) {
2778
- try {
2779
- const seen = /* @__PURE__ */ new WeakSet();
2780
- return JSON.stringify(obj, (key, value) => {
2781
- if (typeof value === "object" && value !== null) {
2782
- if (seen.has(value)) {
2783
- return "[Circular]";
2784
- }
2785
- seen.add(value);
2786
- }
2787
- return value;
2788
- });
2789
- } catch {
2790
- return "[Unable to stringify]";
2791
- }
2792
- }
2793
- return "[Stringify error]";
2794
- }
2795
- }
2796
- /**
2797
- * Extract readable text from error response data
2798
- */
2799
- extractErrorMessage(data) {
2800
- if (Buffer.isBuffer(data)) {
2801
- return data.toString("utf-8");
2802
- }
2803
- if (typeof data === "string") {
2804
- return data;
2805
- }
2806
- if (data && typeof data === "object" && "_readableState" in data && data._readableState && typeof data._readableState === "object" && "buffer" in data._readableState && Array.isArray(data._readableState.buffer) && data._readableState.buffer.length > 0) {
2807
- const chunks = [];
2808
- for (const chunk of data._readableState.buffer) {
2809
- if (chunk && typeof chunk === "object" && "type" in chunk && chunk.type === "Buffer" && "data" in chunk && Array.isArray(chunk.data)) {
2810
- chunks.push(Buffer.from(chunk.data));
2811
- } else if (Buffer.isBuffer(chunk)) {
2812
- chunks.push(chunk);
2813
- } else if (chunk && typeof chunk === "object" && "data" in chunk) {
2814
- if (Buffer.isBuffer(chunk.data)) {
2815
- chunks.push(chunk.data);
2816
- } else if (Array.isArray(chunk.data)) {
2817
- chunks.push(Buffer.from(chunk.data));
2818
- }
2819
- }
2820
- }
2821
- if (chunks.length > 0) {
2822
- return Buffer.concat(chunks).toString("utf-8");
2823
- }
2824
- }
2825
- return this.safeStringify(data);
2826
- }
2827
- /**
2828
- * Parse HTML error page to extract error message
2829
- */
2830
- parseHtmlError(html) {
2831
- const titleMatch = html.match(/<title>(.*?)<\/title>/i);
2832
- const h1Match = html.match(/<h1>(.*?)<\/h1>/i);
2833
- const bodyMatch = html.match(/<body[^>]*>(.*?)<\/body>/is);
2834
- if (titleMatch && titleMatch[1] !== "Error") {
2835
- return titleMatch[1].trim();
2836
- }
2837
- if (h1Match) {
2838
- return h1Match[1].trim();
2839
- }
2840
- if (bodyMatch) {
2841
- const text = bodyMatch[1].replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
2842
- return text.substring(0, 200);
2843
- }
2844
- return null;
2845
- }
2846
- /**
2847
- * Truncate string to max length with ellipsis
2848
- */
2849
- truncate(str, maxLength) {
2850
- if (str.length <= maxLength) {
2851
- return str;
2852
- }
2853
- return str.substring(0, maxLength) + "... [truncated]";
2854
- }
2855
- /**
2856
- * Format bytes to human-readable size
2857
- */
2858
- formatBytes(bytes) {
2859
- if (bytes === 0) return "0 B";
2860
- const k = 1024;
2861
- const sizes = ["B", "KB", "MB", "GB"];
2862
- const i = Math.floor(Math.log(bytes) / Math.log(k));
2863
- return Math.round(bytes / Math.pow(k, i) * 100) / 100 + " " + sizes[i];
2864
- }
2865
- /**
2866
- * Clean up old debug logs (older than 30 days)
2867
- */
2868
- async cleanupOldLogs() {
2869
- if (!this.fileLoggingEnabled) return;
2870
- try {
2871
- const debugDir = path5.join(os.homedir(), ".bike4mind", "debug");
2872
- const files = await fs5.readdir(debugDir);
2873
- const now = Date.now();
2874
- const thirtyDaysAgo = now - 30 * 24 * 60 * 60 * 1e3;
2875
- for (const file of files) {
2876
- const filePath = path5.join(debugDir, file);
2877
- const stats = await fs5.stat(filePath);
2878
- if (stats.mtime.getTime() < thirtyDaysAgo) {
2879
- await fs5.unlink(filePath);
2880
- }
2881
- }
2882
- } catch (error) {
2883
- console.error("Failed to cleanup old logs:", error);
2884
- }
2885
- }
2886
- };
2887
- var logger = Logger2.getInstance();
2888
- logger.setVerbose(process.env.B4M_VERBOSE === "1");
2889
-
2890
- // src/utils/commandParser.ts
2891
2770
  var flexibleString = z.union([z.string(), z.array(z.string())]).transform((val) => Array.isArray(val) ? val.join(" ") : val).optional();
2892
2771
  var flexibleStringArray = z.union([z.string(), z.array(z.string())]).transform((val) => Array.isArray(val) ? val : [val]).optional();
2893
2772
  function needsQuoting(value) {
@@ -3042,7 +2921,7 @@ function extractCommandName(filename) {
3042
2921
  var CustomCommandStore = class {
3043
2922
  constructor(projectRoot) {
3044
2923
  this.commands = /* @__PURE__ */ new Map();
3045
- const home = os2.homedir();
2924
+ const home = os.homedir();
3046
2925
  const root = projectRoot || process.cwd();
3047
2926
  this.globalCommandsDirs = [
3048
2927
  path6.join(home, ".bike4mind", "commands"),
@@ -3077,7 +2956,7 @@ var CustomCommandStore = class {
3077
2956
  */
3078
2957
  async loadCommandsFromDirectory(directory, source) {
3079
2958
  try {
3080
- const stats = await fs6.stat(directory);
2959
+ const stats = await fs5.stat(directory);
3081
2960
  if (!stats.isDirectory()) {
3082
2961
  return;
3083
2962
  }
@@ -3110,7 +2989,7 @@ var CustomCommandStore = class {
3110
2989
  async findCommandFiles(directory) {
3111
2990
  const files = [];
3112
2991
  try {
3113
- const entries = await fs6.readdir(directory, { withFileTypes: true });
2992
+ const entries = await fs5.readdir(directory, { withFileTypes: true });
3114
2993
  for (const entry of entries) {
3115
2994
  const fullPath = path6.join(directory, entry.name);
3116
2995
  if (entry.isDirectory()) {
@@ -3139,7 +3018,7 @@ var CustomCommandStore = class {
3139
3018
  console.warn(`Invalid command filename: ${filename} (must end with .md and have valid name)`);
3140
3019
  return;
3141
3020
  }
3142
- const fileContent = await fs6.readFile(filePath, "utf-8");
3021
+ const fileContent = await fs5.readFile(filePath, "utf-8");
3143
3022
  const command = parseCommandFile(fileContent, filePath, commandName, source);
3144
3023
  this.commands.set(commandName, command);
3145
3024
  }
@@ -3214,14 +3093,14 @@ var CustomCommandStore = class {
3214
3093
  async createCommandFile(name, isGlobal = false) {
3215
3094
  const targetDir = isGlobal ? this.globalCommandsDirs[0] : this.projectCommandsDirs[0];
3216
3095
  const filePath = path6.join(targetDir, `${name}.md`);
3217
- const fileExists = await fs6.access(filePath).then(
3096
+ const fileExists = await fs5.access(filePath).then(
3218
3097
  () => true,
3219
3098
  () => false
3220
3099
  );
3221
3100
  if (fileExists) {
3222
3101
  throw new Error(`Command file already exists: ${filePath}`);
3223
3102
  }
3224
- await fs6.mkdir(targetDir, { recursive: true });
3103
+ await fs5.mkdir(targetDir, { recursive: true });
3225
3104
  const template = `---
3226
3105
  description: ${name} command
3227
3106
  argument-hint: [args]
@@ -3236,7 +3115,7 @@ You can use:
3236
3115
  - $1, $2, etc. for positional arguments
3237
3116
  - @filename for file references
3238
3117
  `;
3239
- await fs6.writeFile(filePath, template, "utf-8");
3118
+ await fs5.writeFile(filePath, template, "utf-8");
3240
3119
  return filePath;
3241
3120
  }
3242
3121
  };
@@ -3369,6 +3248,7 @@ var ReActAgent = class extends EventEmitter {
3369
3248
  super();
3370
3249
  this.steps = [];
3371
3250
  this.totalTokens = 0;
3251
+ this.totalCredits = 0;
3372
3252
  this.toolCallCount = 0;
3373
3253
  this.observationQueue = [];
3374
3254
  this.context = {
@@ -3388,6 +3268,7 @@ var ReActAgent = class extends EventEmitter {
3388
3268
  async run(query, options = {}) {
3389
3269
  this.steps = [];
3390
3270
  this.totalTokens = 0;
3271
+ this.totalCredits = 0;
3391
3272
  this.toolCallCount = 0;
3392
3273
  const maxIterations = options.maxIterations ?? this.context.maxIterations ?? 5;
3393
3274
  const temperature = options.temperature ?? this.context.temperature ?? 0.7;
@@ -3424,6 +3305,7 @@ ${options.context}` : this.getSystemPrompt()
3424
3305
  steps: this.steps,
3425
3306
  completionInfo: {
3426
3307
  totalTokens: this.totalTokens,
3308
+ totalCredits: this.totalCredits > 0 ? this.totalCredits : void 0,
3427
3309
  iterations,
3428
3310
  toolCalls: this.toolCallCount,
3429
3311
  reachedMaxIterations: false
@@ -3459,6 +3341,9 @@ ${options.context}` : this.getSystemPrompt()
3459
3341
  const inputTokens = completionInfo.inputTokens || 0;
3460
3342
  const outputTokens = completionInfo.outputTokens || 0;
3461
3343
  this.totalTokens += inputTokens + outputTokens;
3344
+ if (completionInfo.creditsUsed) {
3345
+ this.totalCredits += completionInfo.creditsUsed;
3346
+ }
3462
3347
  if (currentText.trim() && completionInfo.toolsUsed?.length) {
3463
3348
  const thoughtStep = {
3464
3349
  type: "thought",
@@ -3572,6 +3457,7 @@ ${options.context}` : this.getSystemPrompt()
3572
3457
  steps: this.steps,
3573
3458
  completionInfo: {
3574
3459
  totalTokens: this.totalTokens,
3460
+ totalCredits: this.totalCredits > 0 ? this.totalCredits : void 0,
3575
3461
  iterations,
3576
3462
  toolCalls: this.toolCallCount,
3577
3463
  reachedMaxIterations
@@ -3589,6 +3475,7 @@ ${options.context}` : this.getSystemPrompt()
3589
3475
  steps: this.steps,
3590
3476
  completionInfo: {
3591
3477
  totalTokens: this.totalTokens,
3478
+ totalCredits: this.totalCredits > 0 ? this.totalCredits : void 0,
3592
3479
  iterations,
3593
3480
  toolCalls: this.toolCallCount,
3594
3481
  reachedMaxIterations: false
@@ -3717,18 +3604,23 @@ Remember: You are an autonomous AGENT. Act independently and solve problems proa
3717
3604
  async executeToolWithQueueFallback(toolUse) {
3718
3605
  const queuedObs = this.observationQueue.find((obs) => obs.toolId === getToolId(toolUse));
3719
3606
  if (queuedObs) {
3720
- const result2 = queuedObs.result;
3607
+ const result = queuedObs.result;
3721
3608
  const index = this.observationQueue.indexOf(queuedObs);
3722
3609
  this.observationQueue.splice(index, 1);
3723
- return typeof result2 === "string" ? result2 : JSON.stringify(result2);
3610
+ return typeof result === "string" ? result : JSON.stringify(result);
3724
3611
  }
3725
3612
  const tool = this.context.tools.find((t) => t.toolSchema.name === toolUse.name);
3726
3613
  if (!tool) {
3727
- throw new Error(`Tool ${toolUse.name} not found in agent context`);
3614
+ return `Error: Tool ${toolUse.name} not found in agent context`;
3615
+ }
3616
+ try {
3617
+ const params = this.parseToolArguments(toolUse.arguments);
3618
+ const result = await tool.toolFn(params);
3619
+ return typeof result === "string" ? result : JSON.stringify(result);
3620
+ } catch (error) {
3621
+ const message = error instanceof Error ? error.message : String(error);
3622
+ return `Error: ${message}`;
3728
3623
  }
3729
- const params = this.parseToolArguments(toolUse.arguments);
3730
- const result = await tool.toolFn(params);
3731
- return typeof result === "string" ? result : JSON.stringify(result);
3732
3624
  }
3733
3625
  /**
3734
3626
  * Build and append tool call/result messages for the conversation history
@@ -3806,6 +3698,7 @@ var DEFAULT_TOOL_CATEGORIES = {
3806
3698
  grep_search: "prompt_default",
3807
3699
  glob_files: "prompt_default",
3808
3700
  get_file_tree: "prompt_default",
3701
+ get_file_structure: "prompt_default",
3809
3702
  git_status: "prompt_default",
3810
3703
  git_diff: "prompt_default",
3811
3704
  git_log: "prompt_default",
@@ -3828,6 +3721,50 @@ function isReadOnlyTool(toolName, customCategories) {
3828
3721
  return getToolCategory(toolName, customCategories) !== "prompt_always";
3829
3722
  }
3830
3723
 
3724
+ // src/core/skillsPrompt.ts
3725
+ function getSkillDisplayName(cmd) {
3726
+ return cmd.displayName || cmd.name;
3727
+ }
3728
+ function formatSkillEntry(cmd) {
3729
+ const displayName = getSkillDisplayName(cmd);
3730
+ const argHint = cmd.argumentHint ? ` ${cmd.argumentHint}` : "";
3731
+ const nameDisplay = cmd.displayName && cmd.displayName !== cmd.name ? `**${displayName}** (\`${cmd.name}\`)` : `**${cmd.name}**`;
3732
+ return `- ${nameDisplay}${argHint}: ${cmd.description}
3733
+ `;
3734
+ }
3735
+ function formatSkillGroup(heading, commands) {
3736
+ if (commands.length === 0) {
3737
+ return "";
3738
+ }
3739
+ return `
3740
+ ### ${heading}
3741
+ ${commands.map(formatSkillEntry).join("")}`;
3742
+ }
3743
+ function filterAIVisibleSkills(commands) {
3744
+ return commands.filter((cmd) => !cmd.disableModelInvocation);
3745
+ }
3746
+ function filterSkillsByAllowedList(commands, allowedSkills) {
3747
+ if (!allowedSkills || allowedSkills.length === 0) {
3748
+ return commands;
3749
+ }
3750
+ return commands.filter((cmd) => allowedSkills.includes(cmd.name));
3751
+ }
3752
+ function buildSkillsPromptSection(commands, allowedSkills) {
3753
+ const filteredByAllowed = filterSkillsByAllowedList(commands, allowedSkills);
3754
+ const visibleCommands = filterAIVisibleSkills(filteredByAllowed);
3755
+ if (visibleCommands.length === 0) {
3756
+ return "";
3757
+ }
3758
+ const projectSkills = visibleCommands.filter((c) => c.source === "project");
3759
+ const globalSkills = visibleCommands.filter((c) => c.source === "global");
3760
+ return `
3761
+
3762
+ ## Available Skills
3763
+
3764
+ Use the \`skill\` tool to invoke these. Example: skill({ skill: "commit" })
3765
+ ` + formatSkillGroup("Project Skills", projectSkills) + formatSkillGroup("Global Skills", globalSkills);
3766
+ }
3767
+
3831
3768
  // src/core/prompts.ts
3832
3769
  var TOOL_GREP_SEARCH = "grep_search";
3833
3770
  var TOOL_GLOB_FILES = "glob_files";
@@ -3838,7 +3775,38 @@ var TOOL_BASH_EXECUTE = "bash_execute";
3838
3775
  var TOOL_SUBAGENT_DELEGATE = "subagent_delegate";
3839
3776
  var TOOL_WRITE_TODOS = "write_todos";
3840
3777
  var EXPLORE_SUBAGENT_TYPE = "explore";
3841
- function buildCoreSystemPrompt(contextSection = "") {
3778
+ function buildCoreSystemPrompt(contextSection, config) {
3779
+ let projectContextSection = "";
3780
+ let agentDirectoryContext = "";
3781
+ let skillsSection = "";
3782
+ if (typeof contextSection === "string") {
3783
+ projectContextSection = contextSection;
3784
+ if (config) {
3785
+ if (config.enableSkillTool !== false && config.customCommands && config.customCommands.length > 0) {
3786
+ skillsSection = buildSkillsPromptSection(config.customCommands);
3787
+ }
3788
+ if (config.agentStore) {
3789
+ agentDirectoryContext = config.agentStore.getDirectoryContext();
3790
+ }
3791
+ }
3792
+ } else if (contextSection && typeof contextSection === "object") {
3793
+ config = contextSection;
3794
+ if (config.contextContent) {
3795
+ projectContextSection = `
3796
+
3797
+ ## Project Context
3798
+
3799
+ Follow these project-specific instructions:
3800
+
3801
+ ${config.contextContent}`;
3802
+ }
3803
+ if (config.enableSkillTool !== false && config.customCommands && config.customCommands.length > 0) {
3804
+ skillsSection = buildSkillsPromptSection(config.customCommands);
3805
+ }
3806
+ if (config.agentStore) {
3807
+ agentDirectoryContext = config.agentStore.getDirectoryContext();
3808
+ }
3809
+ }
3842
3810
  return `You are an autonomous AI assistant with access to tools. Your job is to help users by taking action and solving problems proactively.
3843
3811
 
3844
3812
  CORE BEHAVIOR:
@@ -3868,12 +3836,8 @@ When requested to perform tasks like fixing bugs, adding features, refactoring,
3868
3836
 
3869
3837
  SUBAGENT DELEGATION:
3870
3838
  - You have access to specialized subagents via the ${TOOL_SUBAGENT_DELEGATE} tool
3871
- - Use subagents for focused exploration, planning, or review tasks:
3872
- * explore: Fast read-only codebase search (e.g., "find all auth files", "locate API endpoints")
3873
- * plan: Break down complex tasks into actionable steps
3874
- * review: Analyze code quality and identify issues
3839
+ - ${agentDirectoryContext ? `${agentDirectoryContext}` : ""}
3875
3840
  - Subagents keep the main conversation clean and run faster with optimized models
3876
- - Delegate when you need to search extensively or analyze code structure
3877
3841
 
3878
3842
  CODE SEARCH BEST PRACTICES:
3879
3843
  When searching code, follow this hierarchy for speed and efficiency:
@@ -3930,7 +3894,7 @@ EXAMPLES:
3930
3894
  - "what packages installed?" \u2192 ${TOOL_GLOB_FILES} "**/package.json" \u2192 ${TOOL_FILE_READ}
3931
3895
  - "find all components using React hooks" \u2192 ${TOOL_SUBAGENT_DELEGATE}(task="find all components using React hooks", type="explore")
3932
3896
 
3933
- Remember: Use context from previous messages to understand follow-up questions.${contextSection}`;
3897
+ Remember: Use context from previous messages to understand follow-up questions.${projectContextSection}${skillsSection}`;
3934
3898
  }
3935
3899
 
3936
3900
  // ../../b4m-core/packages/services/dist/src/referService/generateCodes.js
@@ -4380,7 +4344,7 @@ import { GoogleGenerativeAI } from "@google/generative-ai";
4380
4344
  import OpenAI2 from "openai";
4381
4345
 
4382
4346
  // ../../b4m-core/packages/services/dist/src/importHistoryService/index.js
4383
- import fs7, { unlinkSync } from "fs";
4347
+ import fs6, { unlinkSync } from "fs";
4384
4348
  import yauzl from "yauzl";
4385
4349
  import axios3 from "axios";
4386
4350
 
@@ -6059,8 +6023,8 @@ async function processAndStoreImages(images, context) {
6059
6023
  const buffer = await downloadImage(image);
6060
6024
  const fileType = await fileTypeFromBuffer2(buffer);
6061
6025
  const filename = `${uuidv45()}.${fileType?.ext}`;
6062
- const path18 = await context.imageGenerateStorage.upload(buffer, filename, {});
6063
- return path18;
6026
+ const path19 = await context.imageGenerateStorage.upload(buffer, filename, {});
6027
+ return path19;
6064
6028
  }));
6065
6029
  }
6066
6030
  async function updateQuestAndReturnMarkdown(storedImageUrls, context) {
@@ -7386,8 +7350,8 @@ async function processAndStoreImage(imageUrl, context) {
7386
7350
  const buffer = await downloadImage2(imageUrl);
7387
7351
  const fileType = await fileTypeFromBuffer3(buffer);
7388
7352
  const filename = `${uuidv46()}.${fileType?.ext}`;
7389
- const path18 = await context.imageGenerateStorage.upload(buffer, filename, {});
7390
- return path18;
7353
+ const path19 = await context.imageGenerateStorage.upload(buffer, filename, {});
7354
+ return path19;
7391
7355
  }
7392
7356
  async function generateFullMask(imageBuffer) {
7393
7357
  const sharp = (await import("sharp")).default;
@@ -9328,8 +9292,8 @@ var planetVisibilityTool = {
9328
9292
  };
9329
9293
 
9330
9294
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/fileRead/index.js
9331
- import { promises as fs8 } from "fs";
9332
- import { existsSync as existsSync3, statSync as statSync4 } from "fs";
9295
+ import { promises as fs7 } from "fs";
9296
+ import { existsSync as existsSync4, statSync as statSync4 } from "fs";
9333
9297
  import path7 from "path";
9334
9298
  var MAX_FILE_SIZE2 = 10 * 1024 * 1024;
9335
9299
  async function readFileContent(params) {
@@ -9340,7 +9304,7 @@ async function readFileContent(params) {
9340
9304
  if (!resolvedPath.startsWith(cwd)) {
9341
9305
  throw new Error(`Access denied: Cannot read files outside of current working directory`);
9342
9306
  }
9343
- if (!existsSync3(resolvedPath)) {
9307
+ if (!existsSync4(resolvedPath)) {
9344
9308
  throw new Error(`File not found: ${filePath}`);
9345
9309
  }
9346
9310
  const stats = statSync4(resolvedPath);
@@ -9354,7 +9318,7 @@ async function readFileContent(params) {
9354
9318
  if (isBinary && encoding === "utf-8") {
9355
9319
  throw new Error(`File appears to be binary. Use encoding 'base64' to read binary files, or specify a different encoding.`);
9356
9320
  }
9357
- const content = await fs8.readFile(resolvedPath, encoding);
9321
+ const content = await fs7.readFile(resolvedPath, encoding);
9358
9322
  if (typeof content === "string") {
9359
9323
  const lines = content.split("\n");
9360
9324
  const totalLines = lines.length;
@@ -9392,7 +9356,7 @@ ${content}`;
9392
9356
  }
9393
9357
  async function checkIfBinary(filePath) {
9394
9358
  const buffer = Buffer.alloc(8192);
9395
- const fd = await fs8.open(filePath, "r");
9359
+ const fd = await fs7.open(filePath, "r");
9396
9360
  try {
9397
9361
  const { bytesRead } = await fd.read(buffer, 0, 8192, 0);
9398
9362
  const chunk = buffer.slice(0, bytesRead);
@@ -9453,8 +9417,8 @@ var fileReadTool = {
9453
9417
  };
9454
9418
 
9455
9419
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/createFile/index.js
9456
- import { promises as fs9 } from "fs";
9457
- import { existsSync as existsSync4 } from "fs";
9420
+ import { promises as fs8 } from "fs";
9421
+ import { existsSync as existsSync5 } from "fs";
9458
9422
  import path8 from "path";
9459
9423
  async function createFile(params) {
9460
9424
  const { path: filePath, content, createDirectories = true } = params;
@@ -9464,14 +9428,14 @@ async function createFile(params) {
9464
9428
  if (!resolvedPath.startsWith(cwd)) {
9465
9429
  throw new Error(`Access denied: Cannot create files outside of current working directory`);
9466
9430
  }
9467
- const fileExists = existsSync4(resolvedPath);
9431
+ const fileExists = existsSync5(resolvedPath);
9468
9432
  const action = fileExists ? "overwritten" : "created";
9469
9433
  if (createDirectories) {
9470
9434
  const dir = path8.dirname(resolvedPath);
9471
- await fs9.mkdir(dir, { recursive: true });
9435
+ await fs8.mkdir(dir, { recursive: true });
9472
9436
  }
9473
- await fs9.writeFile(resolvedPath, content, "utf-8");
9474
- const stats = await fs9.stat(resolvedPath);
9437
+ await fs8.writeFile(resolvedPath, content, "utf-8");
9438
+ const stats = await fs8.stat(resolvedPath);
9475
9439
  const lines = content.split("\n").length;
9476
9440
  return `File ${action} successfully: ${filePath}
9477
9441
  Size: ${stats.size} bytes
@@ -9482,7 +9446,7 @@ var createFileTool = {
9482
9446
  implementation: (context) => ({
9483
9447
  toolFn: async (value) => {
9484
9448
  const params = value;
9485
- const fileExists = existsSync4(path8.resolve(process.cwd(), path8.normalize(params.path)));
9449
+ const fileExists = existsSync5(path8.resolve(process.cwd(), path8.normalize(params.path)));
9486
9450
  context.logger.info(`\u{1F4DD} CreateFile: ${fileExists ? "Overwriting" : "Creating"} file`, {
9487
9451
  path: params.path,
9488
9452
  size: params.content.length
@@ -9525,7 +9489,7 @@ var createFileTool = {
9525
9489
  import { glob } from "glob";
9526
9490
  import { stat } from "fs/promises";
9527
9491
  import path9 from "path";
9528
- var DEFAULT_IGNORE_PATTERNS2 = [
9492
+ var DEFAULT_IGNORE_PATTERNS = [
9529
9493
  "**/node_modules/**",
9530
9494
  "**/.git/**",
9531
9495
  "**/dist/**",
@@ -9544,7 +9508,7 @@ async function findFiles(params) {
9544
9508
  if (!targetDir.startsWith(baseCwd)) {
9545
9509
  throw new Error(`Access denied: Cannot search outside of current working directory`);
9546
9510
  }
9547
- const ignorePatterns = respect_git_ignore ? DEFAULT_IGNORE_PATTERNS2 : [];
9511
+ const ignorePatterns = respect_git_ignore ? DEFAULT_IGNORE_PATTERNS : [];
9548
9512
  const matches = await glob(pattern, {
9549
9513
  cwd: targetDir,
9550
9514
  dot: false,
@@ -9825,8 +9789,8 @@ var grepSearchTool = {
9825
9789
  };
9826
9790
 
9827
9791
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/deleteFile/index.js
9828
- import { promises as fs10 } from "fs";
9829
- import { existsSync as existsSync5, statSync as statSync5 } from "fs";
9792
+ import { promises as fs9 } from "fs";
9793
+ import { existsSync as existsSync6, statSync as statSync5 } from "fs";
9830
9794
  import path11 from "path";
9831
9795
  async function deleteFile(params) {
9832
9796
  const { path: filePath, recursive = false } = params;
@@ -9836,7 +9800,7 @@ async function deleteFile(params) {
9836
9800
  if (!resolvedPath.startsWith(cwd)) {
9837
9801
  throw new Error(`Access denied: Cannot delete files outside of current working directory`);
9838
9802
  }
9839
- if (!existsSync5(resolvedPath)) {
9803
+ if (!existsSync6(resolvedPath)) {
9840
9804
  throw new Error(`File or directory not found: ${filePath}`);
9841
9805
  }
9842
9806
  const stats = statSync5(resolvedPath);
@@ -9846,10 +9810,10 @@ async function deleteFile(params) {
9846
9810
  throw new Error(`Path is a directory: ${filePath}. Use recursive=true to delete directories and their contents.`);
9847
9811
  }
9848
9812
  if (isDirectory) {
9849
- await fs10.rm(resolvedPath, { recursive: true, force: true });
9813
+ await fs9.rm(resolvedPath, { recursive: true, force: true });
9850
9814
  return `Directory deleted successfully: ${filePath}`;
9851
9815
  } else {
9852
- await fs10.unlink(resolvedPath);
9816
+ await fs9.unlink(resolvedPath);
9853
9817
  return `File deleted successfully: ${filePath}
9854
9818
  Size: ${size} bytes`;
9855
9819
  }
@@ -9860,7 +9824,7 @@ var deleteFileTool = {
9860
9824
  toolFn: async (value) => {
9861
9825
  const params = value;
9862
9826
  const resolvedPath = path11.resolve(process.cwd(), path11.normalize(params.path));
9863
- const isDirectory = existsSync5(resolvedPath) && statSync5(resolvedPath).isDirectory();
9827
+ const isDirectory = existsSync6(resolvedPath) && statSync5(resolvedPath).isDirectory();
9864
9828
  context.logger.info(`\u{1F5D1}\uFE0F DeleteFile: Deleting ${isDirectory ? "directory" : "file"}`, {
9865
9829
  path: params.path,
9866
9830
  recursive: params.recursive
@@ -10359,8 +10323,8 @@ BLOCKED OPERATIONS:
10359
10323
  };
10360
10324
 
10361
10325
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/editLocalFile/index.js
10362
- import { promises as fs11 } from "fs";
10363
- import { existsSync as existsSync6 } from "fs";
10326
+ import { promises as fs10 } from "fs";
10327
+ import { existsSync as existsSync7 } from "fs";
10364
10328
  import path13 from "path";
10365
10329
  import { diffLines as diffLines3 } from "diff";
10366
10330
  function generateDiff(original, modified) {
@@ -10391,10 +10355,10 @@ async function editLocalFile(params) {
10391
10355
  if (!resolvedPath.startsWith(cwd)) {
10392
10356
  throw new Error(`Access denied: Cannot edit files outside of current working directory`);
10393
10357
  }
10394
- if (!existsSync6(resolvedPath)) {
10358
+ if (!existsSync7(resolvedPath)) {
10395
10359
  throw new Error(`File not found: ${filePath}`);
10396
10360
  }
10397
- const currentContent = await fs11.readFile(resolvedPath, "utf-8");
10361
+ const currentContent = await fs10.readFile(resolvedPath, "utf-8");
10398
10362
  if (!currentContent.includes(old_string)) {
10399
10363
  const preview = old_string.length > 100 ? old_string.substring(0, 100) + "..." : old_string;
10400
10364
  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}"`);
@@ -10404,7 +10368,7 @@ async function editLocalFile(params) {
10404
10368
  throw new Error(`Found ${occurrences} occurrences of the string to replace. Please provide a more specific old_string that matches exactly one location.`);
10405
10369
  }
10406
10370
  const newContent = currentContent.replace(old_string, new_string);
10407
- await fs11.writeFile(resolvedPath, newContent, "utf-8");
10371
+ await fs10.writeFile(resolvedPath, newContent, "utf-8");
10408
10372
  const diffResult = generateDiff(old_string, new_string);
10409
10373
  return `File edited successfully: ${filePath}
10410
10374
  Changes: +${diffResult.additions} lines, -${diffResult.deletions} lines
@@ -10979,7 +10943,9 @@ var QuestStartBodySchema = z139.object({
10979
10943
  fabFileIds: z139.array(z139.string()).optional()
10980
10944
  })).optional(),
10981
10945
  /** User's timezone (IANA format, e.g., "America/New_York") */
10982
- timezone: z139.string().optional()
10946
+ timezone: z139.string().optional(),
10947
+ /** Pre-fetched API key table from invoke phase — avoids redundant DB call in process (#6616 P1-a) */
10948
+ apiKeyTable: z139.record(z139.string(), z139.string().nullable()).optional()
10983
10949
  });
10984
10950
 
10985
10951
  // ../../b4m-core/packages/services/dist/src/llm/StatusManager.js
@@ -11580,10 +11546,10 @@ var ToolErrorType;
11580
11546
  // src/utils/diffPreview.ts
11581
11547
  import * as Diff from "diff";
11582
11548
  import { readFile } from "fs/promises";
11583
- import { existsSync as existsSync7 } from "fs";
11549
+ import { existsSync as existsSync8 } from "fs";
11584
11550
  async function generateFileDiffPreview(args) {
11585
11551
  try {
11586
- if (!existsSync7(args.path)) {
11552
+ if (!existsSync8(args.path)) {
11587
11553
  const lines2 = args.content.split("\n");
11588
11554
  const preview = lines2.slice(0, 20).join("\n");
11589
11555
  const hasMore = lines2.length > 20;
@@ -11621,7 +11587,7 @@ ${diffLines4.join("\n")}`;
11621
11587
  }
11622
11588
  async function generateFileDeletePreview(args) {
11623
11589
  try {
11624
- if (!existsSync7(args.path)) {
11590
+ if (!existsSync8(args.path)) {
11625
11591
  return `[File does not exist: ${args.path}]`;
11626
11592
  }
11627
11593
  const stats = await import("fs/promises").then((fs14) => fs14.stat(args.path));
@@ -11915,21 +11881,21 @@ var NoOpStorage = class extends BaseStorage {
11915
11881
  async upload(input, destination, options) {
11916
11882
  return `/tmp/${destination}`;
11917
11883
  }
11918
- async download(path18) {
11884
+ async download(path19) {
11919
11885
  throw new Error("Download not supported in CLI");
11920
11886
  }
11921
- async delete(path18) {
11887
+ async delete(path19) {
11922
11888
  }
11923
- async getSignedUrl(path18) {
11924
- return `/tmp/${path18}`;
11889
+ async getSignedUrl(path19) {
11890
+ return `/tmp/${path19}`;
11925
11891
  }
11926
- getPublicUrl(path18) {
11927
- return `/tmp/${path18}`;
11892
+ getPublicUrl(path19) {
11893
+ return `/tmp/${path19}`;
11928
11894
  }
11929
- async getPreview(path18) {
11930
- return `/tmp/${path18}`;
11895
+ async getPreview(path19) {
11896
+ return `/tmp/${path19}`;
11931
11897
  }
11932
- async getMetadata(path18) {
11898
+ async getMetadata(path19) {
11933
11899
  return { size: 0, contentType: "application/octet-stream" };
11934
11900
  }
11935
11901
  };
@@ -12335,7 +12301,7 @@ function getEnvironmentName(configApiConfig) {
12335
12301
  }
12336
12302
 
12337
12303
  // src/utils/contextLoader.ts
12338
- import * as fs12 from "fs";
12304
+ import * as fs11 from "fs";
12339
12305
  import * as path14 from "path";
12340
12306
  import { homedir as homedir3 } from "os";
12341
12307
  var CONTEXT_FILE_SIZE_LIMIT = 100 * 1024;
@@ -12359,7 +12325,7 @@ function formatFileSize2(bytes) {
12359
12325
  function tryReadContextFile(dir, filename, source) {
12360
12326
  const filePath = path14.join(dir, filename);
12361
12327
  try {
12362
- const stats = fs12.lstatSync(filePath);
12328
+ const stats = fs11.lstatSync(filePath);
12363
12329
  if (stats.isDirectory()) {
12364
12330
  return null;
12365
12331
  }
@@ -12373,7 +12339,7 @@ function tryReadContextFile(dir, filename, source) {
12373
12339
  error: `${source === "global" ? "Global" : "Project"} ${filename} exceeds 100KB limit (${formatFileSize2(stats.size)})`
12374
12340
  };
12375
12341
  }
12376
- const content = fs12.readFileSync(filePath, "utf-8");
12342
+ const content = fs11.readFileSync(filePath, "utf-8");
12377
12343
  return {
12378
12344
  filename,
12379
12345
  content,
@@ -12697,7 +12663,7 @@ function substituteArguments(template, args) {
12697
12663
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
12698
12664
  import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
12699
12665
  import path15 from "path";
12700
- import { existsSync as existsSync8, readdirSync as readdirSync3 } from "fs";
12666
+ import { existsSync as existsSync9, readdirSync as readdirSync3 } from "fs";
12701
12667
  var MCPClient = class {
12702
12668
  // Note: This class handles MCP server communication with repository filtering
12703
12669
  mcp;
@@ -12734,7 +12700,6 @@ var MCPClient = class {
12734
12700
  if (this.customCommand && this.customCommand.trim() !== "") {
12735
12701
  command = this.customCommand;
12736
12702
  args = this.customArgs ?? [];
12737
- console.log(`[MCP] Using external command for ${this.serverName}: ${command} ${args.join(" ")}`);
12738
12703
  } else {
12739
12704
  const root = process.env.INIT_CWD || process.cwd();
12740
12705
  const candidatePaths = [
@@ -12750,7 +12715,7 @@ var MCPClient = class {
12750
12715
  path15.join(root, `/b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
12751
12716
  path15.join(root, "core", "mcp", "servers", this.serverName, "dist", "index.js")
12752
12717
  ];
12753
- const serverScriptPath = candidatePaths.find((p) => existsSync8(p));
12718
+ const serverScriptPath = candidatePaths.find((p) => existsSync9(p));
12754
12719
  if (!serverScriptPath) {
12755
12720
  const getDirectories = (source) => readdirSync3(source, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
12756
12721
  console.error(`[MCP] Server script not found. Tried paths:`, candidatePaths);
@@ -13310,6 +13275,14 @@ ${totalText}`);
13310
13275
  function stripThinkingBlocks(text) {
13311
13276
  return text.replace(/<think>[\s\S]*?<\/think>/g, "").trim();
13312
13277
  }
13278
+ function extractUsageInfo(parsed) {
13279
+ return {
13280
+ inputTokens: parsed.usage?.inputTokens,
13281
+ outputTokens: parsed.usage?.outputTokens,
13282
+ creditsUsed: parsed.credits?.used,
13283
+ usdCost: parsed.credits?.usdCost
13284
+ };
13285
+ }
13313
13286
  var ServerLlmBackend = class {
13314
13287
  constructor(options) {
13315
13288
  this.completionsEndpoint = "/api/ai/v1/completions";
@@ -13461,11 +13434,8 @@ var ServerLlmBackend = class {
13461
13434
  if (parsed.type === "content") {
13462
13435
  const textChunk = parsed.text || "";
13463
13436
  accumulatedText += textChunk;
13464
- if (parsed.usage) {
13465
- lastUsageInfo = {
13466
- inputTokens: parsed.usage.inputTokens,
13467
- outputTokens: parsed.usage.outputTokens
13468
- };
13437
+ if (parsed.usage || parsed.credits) {
13438
+ lastUsageInfo = extractUsageInfo(parsed);
13469
13439
  }
13470
13440
  streamLogger.onContent(eventCount, textChunk, accumulatedText);
13471
13441
  } else if (parsed.type === "tool_use") {
@@ -13492,11 +13462,8 @@ var ServerLlmBackend = class {
13492
13462
  thinkingBlocks = parsed.thinking;
13493
13463
  streamLogger.onCriticalEvent(eventCount, "THINKING", `${thinkingBlocks.length} thinking blocks`);
13494
13464
  }
13495
- if (parsed.usage) {
13496
- lastUsageInfo = {
13497
- inputTokens: parsed.usage.inputTokens,
13498
- outputTokens: parsed.usage.outputTokens
13499
- };
13465
+ if (parsed.usage || parsed.credits) {
13466
+ lastUsageInfo = extractUsageInfo(parsed);
13500
13467
  }
13501
13468
  }
13502
13469
  } catch (parseError) {
@@ -13563,7 +13530,7 @@ var ServerLlmBackend = class {
13563
13530
  logger.info("\u26A0\uFE0F Using fallback model list (no CLI-compatible models available)");
13564
13531
  return this.getFallbackModels();
13565
13532
  }
13566
- logger.info(`\u{1F4CB} Loaded ${filteredModels.length} models from server`);
13533
+ logger.debug(`\u{1F4CB} Loaded ${filteredModels.length} models from server`);
13567
13534
  return filteredModels;
13568
13535
  } catch (error) {
13569
13536
  logger.warn(
@@ -13801,7 +13768,7 @@ import { isAxiosError as isAxiosError2 } from "axios";
13801
13768
  // package.json
13802
13769
  var package_default = {
13803
13770
  name: "@bike4mind/cli",
13804
- version: "0.2.28",
13771
+ version: "0.2.29-cache-mcp-tools-schema-and-refactor.18897+4545eccff",
13805
13772
  type: "module",
13806
13773
  description: "Interactive CLI tool for Bike4Mind with ReAct agents",
13807
13774
  license: "UNLICENSED",
@@ -13872,10 +13839,13 @@ var package_default = {
13872
13839
  diff: "^8.0.2",
13873
13840
  dotenv: "^16.3.1",
13874
13841
  "eventsource-parser": "^3.0.6",
13842
+ fdir: "^6.5.0",
13875
13843
  "file-type": "^18.7.0",
13876
13844
  "fuse.js": "^7.1.0",
13845
+ fzf: "^0.5.2",
13877
13846
  glob: "^13.0.0",
13878
13847
  "gray-matter": "^4.0.3",
13848
+ ignore: "^7.0.5",
13879
13849
  ink: "^6.5.1",
13880
13850
  "ink-select-input": "^6.2.0",
13881
13851
  "ink-spinner": "^5.0.0",
@@ -13891,15 +13861,18 @@ var package_default = {
13891
13861
  open: "^11.0.0",
13892
13862
  openai: "^6.18.0",
13893
13863
  "p-limit": "^6.2.0",
13864
+ picomatch: "^4.0.3",
13894
13865
  qrcode: "^1.5.4",
13895
13866
  react: "^19.2.3",
13896
13867
  sharp: "^0.34.5",
13897
13868
  speakeasy: "^2.0.0",
13898
13869
  tiktoken: "^1.0.16",
13870
+ "tree-sitter-wasms": "^0.1.13",
13899
13871
  turndown: "^7.2.0",
13900
13872
  unpdf: "^0.10.0",
13901
13873
  uuid: "^9.0.1",
13902
13874
  voyageai: "^0.0.4",
13875
+ "web-tree-sitter": "0.25.10",
13903
13876
  xlsx: "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
13904
13877
  yargs: "^17.7.2",
13905
13878
  yauzl: "^3.1.3",
@@ -13909,14 +13882,15 @@ var package_default = {
13909
13882
  },
13910
13883
  devDependencies: {
13911
13884
  "@bike4mind/agents": "0.1.0",
13912
- "@bike4mind/common": "2.50.0",
13913
- "@bike4mind/mcp": "1.29.0",
13914
- "@bike4mind/services": "2.48.0",
13915
- "@bike4mind/utils": "2.5.0",
13885
+ "@bike4mind/common": "2.50.1-cache-mcp-tools-schema-and-refactor.18897+4545eccff",
13886
+ "@bike4mind/mcp": "1.29.1-cache-mcp-tools-schema-and-refactor.18897+4545eccff",
13887
+ "@bike4mind/services": "2.48.1-cache-mcp-tools-schema-and-refactor.18897+4545eccff",
13888
+ "@bike4mind/utils": "2.5.1-cache-mcp-tools-schema-and-refactor.18897+4545eccff",
13916
13889
  "@types/better-sqlite3": "^7.6.13",
13917
13890
  "@types/diff": "^5.0.9",
13918
13891
  "@types/jsonwebtoken": "^9.0.4",
13919
13892
  "@types/node": "^22.9.0",
13893
+ "@types/picomatch": "^4.0.2",
13920
13894
  "@types/react": "^19.2.7",
13921
13895
  "@types/uuid": "^9.0.7",
13922
13896
  "@types/yargs": "^17.0.32",
@@ -13929,7 +13903,7 @@ var package_default = {
13929
13903
  optionalDependencies: {
13930
13904
  "@vscode/ripgrep": "^1.17.0"
13931
13905
  },
13932
- gitHead: "28d4afd2bd097efcd751a14cbda1eee68cae2ad2"
13906
+ gitHead: "4545eccff3e4227edc2e97613d9d12607cac9530"
13933
13907
  };
13934
13908
 
13935
13909
  // src/config/constants.ts
@@ -14166,50 +14140,6 @@ ${expandedBody}
14166
14140
  };
14167
14141
  }
14168
14142
 
14169
- // src/core/skillsPrompt.ts
14170
- function getSkillDisplayName(cmd) {
14171
- return cmd.displayName || cmd.name;
14172
- }
14173
- function formatSkillEntry(cmd) {
14174
- const displayName = getSkillDisplayName(cmd);
14175
- const argHint = cmd.argumentHint ? ` ${cmd.argumentHint}` : "";
14176
- const nameDisplay = cmd.displayName && cmd.displayName !== cmd.name ? `**${displayName}** (\`${cmd.name}\`)` : `**${cmd.name}**`;
14177
- return `- ${nameDisplay}${argHint}: ${cmd.description}
14178
- `;
14179
- }
14180
- function formatSkillGroup(heading, commands) {
14181
- if (commands.length === 0) {
14182
- return "";
14183
- }
14184
- return `
14185
- ### ${heading}
14186
- ${commands.map(formatSkillEntry).join("")}`;
14187
- }
14188
- function filterAIVisibleSkills(commands) {
14189
- return commands.filter((cmd) => !cmd.disableModelInvocation);
14190
- }
14191
- function filterSkillsByAllowedList(commands, allowedSkills) {
14192
- if (!allowedSkills || allowedSkills.length === 0) {
14193
- return commands;
14194
- }
14195
- return commands.filter((cmd) => allowedSkills.includes(cmd.name));
14196
- }
14197
- function buildSkillsPromptSection(commands, allowedSkills) {
14198
- const filteredByAllowed = filterSkillsByAllowedList(commands, allowedSkills);
14199
- const visibleCommands = filterAIVisibleSkills(filteredByAllowed);
14200
- if (visibleCommands.length === 0) {
14201
- return "";
14202
- }
14203
- const projectSkills = visibleCommands.filter((c) => c.source === "project");
14204
- const globalSkills = visibleCommands.filter((c) => c.source === "global");
14205
- return `
14206
-
14207
- ## Available Skills
14208
-
14209
- Use the \`skill\` tool to invoke these. Example: skill({ skill: "commit" })
14210
- ` + formatSkillGroup("Project Skills", projectSkills) + formatSkillGroup("Global Skills", globalSkills);
14211
- }
14212
-
14213
14143
  // src/agents/SubagentOrchestrator.ts
14214
14144
  var SubagentOrchestrator = class {
14215
14145
  constructor(deps) {
@@ -14422,9 +14352,9 @@ var SubagentOrchestrator = class {
14422
14352
  };
14423
14353
 
14424
14354
  // src/agents/AgentStore.ts
14425
- import fs13 from "fs/promises";
14355
+ import fs12 from "fs/promises";
14426
14356
  import path16 from "path";
14427
- import os3 from "os";
14357
+ import os2 from "os";
14428
14358
  import matter2 from "gray-matter";
14429
14359
  var FULL_MODEL_ID_PREFIXES = [
14430
14360
  "claude-",
@@ -14570,7 +14500,7 @@ var AgentStore = class {
14570
14500
  constructor(builtinDir, projectRoot) {
14571
14501
  this.agents = /* @__PURE__ */ new Map();
14572
14502
  const root = projectRoot || process.cwd();
14573
- const home = os3.homedir();
14503
+ const home = os2.homedir();
14574
14504
  this.builtinAgentsDir = builtinDir;
14575
14505
  this.globalB4MAgentsDir = path16.join(home, ".bike4mind", "agents");
14576
14506
  this.globalClaudeAgentsDir = path16.join(home, ".claude", "agents");
@@ -14599,7 +14529,7 @@ var AgentStore = class {
14599
14529
  */
14600
14530
  async loadAgentsFromDirectory(directory, source) {
14601
14531
  try {
14602
- const stats = await fs13.stat(directory);
14532
+ const stats = await fs12.stat(directory);
14603
14533
  if (!stats.isDirectory()) {
14604
14534
  return;
14605
14535
  }
@@ -14627,7 +14557,7 @@ var AgentStore = class {
14627
14557
  async findAgentFiles(directory) {
14628
14558
  const files = [];
14629
14559
  try {
14630
- const entries = await fs13.readdir(directory, { withFileTypes: true });
14560
+ const entries = await fs12.readdir(directory, { withFileTypes: true });
14631
14561
  for (const entry of entries) {
14632
14562
  const fullPath = path16.join(directory, entry.name);
14633
14563
  if (entry.isDirectory()) {
@@ -14646,7 +14576,7 @@ var AgentStore = class {
14646
14576
  * Parse a single agent markdown file
14647
14577
  */
14648
14578
  async parseAgentFile(filePath, source) {
14649
- const content = await fs13.readFile(filePath, "utf-8");
14579
+ const content = await fs12.readFile(filePath, "utf-8");
14650
14580
  const { data: frontmatter, content: body } = matter2(content);
14651
14581
  const parsed = AgentFrontmatterSchema.parse(frontmatter);
14652
14582
  const name = path16.basename(filePath, ".md");
@@ -14731,14 +14661,14 @@ var AgentStore = class {
14731
14661
  const targetDir = isGlobal ? useClaude ? this.globalClaudeAgentsDir : this.globalB4MAgentsDir : useClaude ? this.projectClaudeAgentsDir : this.projectB4MAgentsDir;
14732
14662
  const filePath = path16.join(targetDir, `${name}.md`);
14733
14663
  try {
14734
- await fs13.access(filePath);
14664
+ await fs12.access(filePath);
14735
14665
  throw new Error(`Agent file already exists: ${filePath}`);
14736
14666
  } catch (error) {
14737
14667
  if (error.code !== "ENOENT") {
14738
14668
  throw error;
14739
14669
  }
14740
14670
  }
14741
- await fs13.mkdir(targetDir, { recursive: true });
14671
+ await fs12.mkdir(targetDir, { recursive: true });
14742
14672
  const template = `---
14743
14673
  description: ${name} agent description
14744
14674
  model: claude-3-5-haiku-20241022
@@ -14773,7 +14703,7 @@ You are a ${name} specialist. Your job is to [describe primary task].
14773
14703
  ## Output Format
14774
14704
  Describe the expected output format here.
14775
14705
  `;
14776
- await fs13.writeFile(filePath, template, "utf-8");
14706
+ await fs12.writeFile(filePath, template, "utf-8");
14777
14707
  return filePath;
14778
14708
  }
14779
14709
  /**
@@ -14790,6 +14720,21 @@ Describe the expected output format here.
14790
14720
  }
14791
14721
  return { builtin, global, project, total: this.agents.size };
14792
14722
  }
14723
+ /**
14724
+ * Generates a markdown "Phone Book" of available agents and their schemas.
14725
+ * This MUST be injected into the System Prompt of the parent agent.
14726
+ */
14727
+ getDirectoryContext() {
14728
+ if (this.agents.size === 0) {
14729
+ return "No sub-agents are currently available.";
14730
+ }
14731
+ let context = "Use `subagent_delegate` for complex tasks requiring specialized analysis.\n";
14732
+ for (const [name, def] of this.agents) {
14733
+ context += ` - **${name}**: ${def.description}
14734
+ `;
14735
+ }
14736
+ return context;
14737
+ }
14793
14738
  };
14794
14739
 
14795
14740
  // src/agents/delegateTool.ts
@@ -15609,6 +15554,99 @@ function createFindDefinitionTool() {
15609
15554
  };
15610
15555
  }
15611
15556
 
15557
+ // src/tools/getFileStructure/index.ts
15558
+ import { existsSync as existsSync10, promises as fs13, statSync as statSync6 } from "fs";
15559
+ import path18 from "path";
15560
+
15561
+ // src/tools/getFileStructure/formatter.ts
15562
+ var SECTIONS = [
15563
+ { title: "IMPORTS", kind: "import", exportable: false },
15564
+ { title: "EXPORTS", kind: "export", exportable: false },
15565
+ { title: "FUNCTIONS", kind: "function", exportable: true },
15566
+ { title: "CLASSES", kind: "class", exportable: true },
15567
+ { title: "INTERFACES", kind: "interface", exportable: true },
15568
+ { title: "TYPES", kind: "type", exportable: true },
15569
+ { title: "ENUMS", kind: "enum", exportable: true }
15570
+ ];
15571
+ function formatStructureOutput(filePath, items, fileSize, lineCount) {
15572
+ const sizeStr = fileSize < 1024 ? `${fileSize} B` : `${(fileSize / 1024).toFixed(1)} KB`;
15573
+ const lines = [`File: ${filePath} (${sizeStr}, ${lineCount} lines)`, ""];
15574
+ for (const section of SECTIONS) {
15575
+ const sectionItems = items.filter((i) => i.kind === section.kind);
15576
+ formatSection(lines, section.title, sectionItems, (item) => {
15577
+ const prefix = section.exportable && item.exported ? "export " : "";
15578
+ return `L${item.line}: ${prefix}${item.name}`;
15579
+ });
15580
+ }
15581
+ return lines.join("\n").trimEnd();
15582
+ }
15583
+ function formatSection(lines, title, items, format) {
15584
+ lines.push(`${title} (${items.length}):`);
15585
+ if (items.length === 0) {
15586
+ lines.push(" (none)");
15587
+ } else {
15588
+ for (const item of items) {
15589
+ lines.push(` ${format(item)}`);
15590
+ }
15591
+ }
15592
+ lines.push("");
15593
+ }
15594
+
15595
+ // src/tools/getFileStructure/index.ts
15596
+ var MAX_FILE_SIZE3 = 10 * 1024 * 1024;
15597
+ function createGetFileStructureTool() {
15598
+ return {
15599
+ toolFn: async (value) => {
15600
+ const params = value;
15601
+ try {
15602
+ const cwd = process.cwd();
15603
+ const resolvedPath = path18.resolve(cwd, params.path);
15604
+ if (!resolvedPath.startsWith(cwd)) {
15605
+ return "Error: Access denied - cannot read files outside of current working directory";
15606
+ }
15607
+ if (!existsSync10(resolvedPath)) {
15608
+ return `Error: File not found: ${params.path}`;
15609
+ }
15610
+ const stats = statSync6(resolvedPath);
15611
+ if (stats.isDirectory()) {
15612
+ return `Error: Path is a directory, not a file: ${params.path}`;
15613
+ }
15614
+ if (stats.size > MAX_FILE_SIZE3) {
15615
+ return `Error: File too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Max: ${MAX_FILE_SIZE3 / 1024 / 1024}MB`;
15616
+ }
15617
+ const ext = path18.extname(resolvedPath).toLowerCase();
15618
+ const { getLanguageForExtension, parseFileStructure, getSupportedLanguages } = await import("./treeSitterEngine-4SGFQDY3.js");
15619
+ const languageId = getLanguageForExtension(ext);
15620
+ if (!languageId) {
15621
+ const supported = getSupportedLanguages();
15622
+ return `Error: Unsupported file type "${ext}". Supported languages: ${supported.join(", ")}`;
15623
+ }
15624
+ const sourceCode = await fs13.readFile(resolvedPath, "utf-8");
15625
+ const lineCount = sourceCode.split("\n").length;
15626
+ const items = await parseFileStructure(sourceCode, languageId);
15627
+ return formatStructureOutput(params.path, items, stats.size, lineCount);
15628
+ } catch (error) {
15629
+ const errorMessage = error instanceof Error ? error.message : String(error);
15630
+ return `Error analyzing file structure: ${errorMessage}`;
15631
+ }
15632
+ },
15633
+ toolSchema: {
15634
+ name: "get_file_structure",
15635
+ description: "Extract the high-level structure of a source file (imports, exports, functions, classes, interfaces, types) using AST parsing. Returns a concise outline with line numbers. Much more efficient than file_read when you only need to understand what a file contains or exports. Use this before file_read to decide which parts of a large file to read.",
15636
+ parameters: {
15637
+ type: "object",
15638
+ properties: {
15639
+ path: {
15640
+ type: "string",
15641
+ description: "Path to the source file (relative to cwd or absolute within cwd). Supported: .ts, .tsx, .js, .jsx, .mjs, .cjs, .py"
15642
+ }
15643
+ },
15644
+ required: ["path"]
15645
+ }
15646
+ }
15647
+ };
15648
+ }
15649
+
15612
15650
  // src/index.tsx
15613
15651
  process.removeAllListeners("warning");
15614
15652
  process.on("warning", (warning) => {
@@ -15623,7 +15661,7 @@ var usageCache = null;
15623
15661
  function CliApp() {
15624
15662
  const { exit } = useApp();
15625
15663
  const imageRenderer = new ImageRenderer();
15626
- const [state, setState] = useState9({
15664
+ const [state, setState] = useState10({
15627
15665
  session: null,
15628
15666
  sessionStore: new SessionStore(),
15629
15667
  configStore: new ConfigStore(),
@@ -15646,9 +15684,9 @@ function CliApp() {
15646
15684
  contextContent: "",
15647
15685
  backgroundManager: null
15648
15686
  });
15649
- const [isInitialized, setIsInitialized] = useState9(false);
15650
- const [initError, setInitError] = useState9(null);
15651
- const [commandHistory, setCommandHistory] = useState9([]);
15687
+ const [isInitialized, setIsInitialized] = useState10(false);
15688
+ const [initError, setInitError] = useState10(null);
15689
+ const [commandHistory, setCommandHistory] = useState10([]);
15652
15690
  const imageStoreInitPromise = useRef3(null);
15653
15691
  const setStoreSession = useCliStore((state2) => state2.setSession);
15654
15692
  const enqueuePermissionPrompt = useCliStore((state2) => state2.enqueuePermissionPrompt);
@@ -15725,6 +15763,7 @@ function CliApp() {
15725
15763
  });
15726
15764
  const init = useCallback2(async () => {
15727
15765
  try {
15766
+ const startupLog = [];
15728
15767
  const config = await state.configStore.load();
15729
15768
  const history = await state.commandHistoryStore.load();
15730
15769
  setCommandHistory(history);
@@ -15732,7 +15771,7 @@ function CliApp() {
15732
15771
  await state.customCommandStore.loadCommands();
15733
15772
  const customCommandCount = state.customCommandStore.getCommandCount();
15734
15773
  if (customCommandCount > 0) {
15735
- console.log(`\u{1F4DD} Loaded ${customCommandCount} custom command${customCommandCount !== 1 ? "s" : ""}`);
15774
+ startupLog.push(`\u{1F4DD} Loaded ${customCommandCount} custom command${customCommandCount !== 1 ? "s" : ""}`);
15736
15775
  }
15737
15776
  } catch (error) {
15738
15777
  console.warn("Failed to load custom commands:", error instanceof Error ? error.message : String(error));
@@ -15753,7 +15792,7 @@ function CliApp() {
15753
15792
  } else {
15754
15793
  isAuthenticated = true;
15755
15794
  const daysUntilExpiry = Math.floor((expiresAt.getTime() - Date.now()) / (1e3 * 60 * 60 * 24));
15756
- console.log(`\u2705 Authenticated (expires in ${daysUntilExpiry} day${daysUntilExpiry !== 1 ? "s" : ""})`);
15795
+ startupLog.push(`\u2705 Authenticated (expires in ${daysUntilExpiry} day${daysUntilExpiry !== 1 ? "s" : ""})`);
15757
15796
  }
15758
15797
  }
15759
15798
  if (!isAuthenticated) {
@@ -15867,22 +15906,23 @@ function CliApp() {
15867
15906
  state.configStore,
15868
15907
  apiClient
15869
15908
  );
15870
- console.log(`\u{1F6E0}\uFE0F Loaded ${b4mTools2.length} B4M tool(s)`);
15909
+ startupLog.push(`\u{1F6E0}\uFE0F Loaded ${b4mTools2.length} B4M tool(s)`);
15871
15910
  const mcpManager = new McpManager(config);
15872
15911
  await mcpManager.initialize();
15873
15912
  const mcpTools = mcpManager.getTools();
15874
15913
  if (mcpTools.length > 0) {
15875
- const toolNames = mcpTools.map((t) => t.toolSchema.name).join(", ");
15876
- console.log(`\u{1F4E1} Loaded ${mcpTools.length} MCP tool(s): ${toolNames}`);
15914
+ const toolCountByServer = mcpManager.getToolCount();
15915
+ const serverSummaries = toolCountByServer.map((s) => `${s.serverName} (${s.count})`).join(", ");
15916
+ startupLog.push(`\u{1F4E1} Loaded ${mcpTools.length} MCP tool(s): ${serverSummaries}`);
15877
15917
  } else {
15878
- console.log(`\u{1F4E1} No MCP tools loaded`);
15918
+ startupLog.push(`\u{1F4E1} No MCP tools loaded`);
15879
15919
  }
15880
15920
  const builtinAgentsDir = new URL("./agents/defaults/", import.meta.url).pathname;
15881
15921
  const agentProjectDir = state.configStore.getProjectConfigDir();
15882
15922
  const agentStore = new AgentStore(builtinAgentsDir, agentProjectDir || process.cwd());
15883
15923
  await agentStore.loadAgents();
15884
15924
  const agentSummary = agentStore.getSummary();
15885
- console.log(
15925
+ startupLog.push(
15886
15926
  `\u{1F916} Loaded ${agentSummary.total} agent(s): ${agentSummary.builtin} built-in, ${agentSummary.global} global, ${agentSummary.project} project`
15887
15927
  );
15888
15928
  const orchestrator = new SubagentOrchestrator({
@@ -15917,18 +15957,25 @@ function CliApp() {
15917
15957
  sessionId: newSession.id
15918
15958
  }) : null;
15919
15959
  const findDefinitionTool = createFindDefinitionTool();
15920
- const cliTools = [agentDelegateTool, ...backgroundTools, writeTodosTool, findDefinitionTool];
15960
+ const getFileStructureTool = createGetFileStructureTool();
15961
+ const cliTools = [
15962
+ agentDelegateTool,
15963
+ ...backgroundTools,
15964
+ writeTodosTool,
15965
+ findDefinitionTool,
15966
+ getFileStructureTool
15967
+ ];
15921
15968
  if (skillTool) {
15922
15969
  cliTools.push(skillTool);
15923
15970
  }
15924
15971
  const allTools = [...b4mTools2, ...mcpTools, ...cliTools];
15925
- console.log(`\u{1F4C2} Working directory: ${process.cwd()}`);
15972
+ startupLog.push(`\u{1F4C2} Working directory: ${process.cwd()}`);
15926
15973
  const agentNamesList = agentStore.getAgentNames().join(", ");
15927
- console.log(`\u{1F916} Subagent delegation enabled (${agentNamesList}) + background execution`);
15974
+ startupLog.push(`\u{1F916} Subagent delegation enabled (${agentNamesList}) + background execution`);
15928
15975
  if (skillTool) {
15929
15976
  const skillCount = state.customCommandStore.getCommandCount();
15930
15977
  if (skillCount > 0) {
15931
- console.log(`\u{1F6E0}\uFE0F Skill tool enabled (${skillCount} skills available)`);
15978
+ startupLog.push(`\u{1F6E0}\uFE0F Skill tool enabled (${skillCount} skills available)`);
15932
15979
  }
15933
15980
  }
15934
15981
  logger.debug(
@@ -15937,26 +15984,20 @@ function CliApp() {
15937
15984
  const projectDir = state.configStore.getProjectConfigDir();
15938
15985
  const contextResult = await loadContextFiles(projectDir);
15939
15986
  if (contextResult.globalContext) {
15940
- console.log(`\u{1F4C4} Global context: ${contextResult.globalContext.filename}`);
15987
+ startupLog.push(`\u{1F4C4} Global context: ${contextResult.globalContext.filename}`);
15941
15988
  }
15942
15989
  if (contextResult.projectContext) {
15943
- console.log(`\u{1F4C4} Project context: ${contextResult.projectContext.filename}`);
15990
+ startupLog.push(`\u{1F4C4} Project context: ${contextResult.projectContext.filename}`);
15944
15991
  }
15945
15992
  for (const error of contextResult.errors) {
15946
- console.log(`\u26A0\uFE0F Context file error: ${error}`);
15993
+ startupLog.push(`\u26A0\uFE0F Context file error: ${error}`);
15947
15994
  }
15948
- let contextSection = contextResult.mergedContent ? `
15949
-
15950
- ## Project Context
15951
-
15952
- Follow these project-specific instructions:
15953
-
15954
- ${contextResult.mergedContent}` : "";
15955
- if (enableSkillTool) {
15956
- const commands = state.customCommandStore.getAllCommands();
15957
- contextSection += buildSkillsPromptSection(commands);
15958
- }
15959
- const cliSystemPrompt = buildCoreSystemPrompt(contextSection);
15995
+ const cliSystemPrompt = buildCoreSystemPrompt({
15996
+ contextContent: contextResult.mergedContent,
15997
+ agentStore,
15998
+ customCommands: state.customCommandStore.getAllCommands(),
15999
+ enableSkillTool
16000
+ });
15960
16001
  const maxIterations = config.preferences.maxIterations === null ? 999999 : config.preferences.maxIterations;
15961
16002
  const agent = new ReActAgent({
15962
16003
  userId: config.userId,
@@ -16019,8 +16060,36 @@ ${contextResult.mergedContent}` : "";
16019
16060
  // Store for grouped notification turn tracking
16020
16061
  }));
16021
16062
  setStoreSession(newSession);
16063
+ const bannerLines = [
16064
+ { text: "\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557", ansi: "\x1B[36m\x1B[1m" },
16065
+ { text: "\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551", ansi: "\x1B[36m\x1B[1m" },
16066
+ { text: "\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551", ansi: "\x1B[36m\x1B[1m" },
16067
+ { text: "\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551", ansi: "\x1B[36m\x1B[1m" },
16068
+ { text: "\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551", ansi: "\x1B[36m\x1B[1m" },
16069
+ { text: "\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D", ansi: "\x1B[36m\x1B[1m" },
16070
+ { text: "", ansi: "" },
16071
+ { text: `v${package_default.version} - AI-Powered CLI`, ansi: "\x1B[2m" },
16072
+ { text: "/help for more information", ansi: "\x1B[2m" }
16073
+ ];
16074
+ const bannerWidth = 30;
16075
+ const termWidth = process.stdout.columns || 80;
16076
+ const rightColWidth = termWidth - bannerWidth - 2;
16077
+ const truncate = (str, max) => {
16078
+ if (str.length > max) return str.slice(0, max - 1) + "\u2026";
16079
+ return str;
16080
+ };
16081
+ const totalLines = Math.max(bannerLines.length, startupLog.length);
16082
+ for (let i = 0; i < totalLines; i++) {
16083
+ const banner = bannerLines[i];
16084
+ const leftText = banner?.text || "";
16085
+ const leftAnsi = banner?.ansi || "";
16086
+ const right = startupLog[i] || "";
16087
+ const coloredLeft = leftText ? `${leftAnsi}${leftText}\x1B[0m` : "";
16088
+ const gap = " ".repeat(bannerWidth - leftText.length + 2);
16089
+ console.log(coloredLeft + gap + truncate(right, rightColWidth));
16090
+ }
16022
16091
  setIsInitialized(true);
16023
- console.log("\n");
16092
+ console.log("");
16024
16093
  } catch (error) {
16025
16094
  console.error("Initialization error:", error);
16026
16095
  setInitError(error instanceof Error ? error.message : "Unknown error");
@@ -16034,7 +16103,7 @@ ${contextResult.mergedContent}` : "";
16034
16103
  setStoreSession,
16035
16104
  setState
16036
16105
  ]);
16037
- useEffect6(() => {
16106
+ useEffect7(() => {
16038
16107
  init();
16039
16108
  }, [init]);
16040
16109
  const handleCustomCommandMessage = async (fullTemplate, displayMessage) => {
@@ -16145,6 +16214,7 @@ ${contextResult.mergedContent}` : "";
16145
16214
  completion: 0,
16146
16215
  total: result.completionInfo.totalTokens
16147
16216
  },
16217
+ creditsUsed: result.completionInfo.totalCredits,
16148
16218
  model: state.session.model,
16149
16219
  permissionDenied
16150
16220
  }
@@ -16156,6 +16226,7 @@ ${contextResult.mergedContent}` : "";
16156
16226
  metadata: {
16157
16227
  ...currentSession.metadata,
16158
16228
  totalTokens: currentSession.metadata.totalTokens + result.completionInfo.totalTokens,
16229
+ totalCredits: (currentSession.metadata.totalCredits || 0) + (result.completionInfo.totalCredits || 0),
16159
16230
  toolCallCount: currentSession.metadata.toolCallCount + successfulToolCalls
16160
16231
  }
16161
16232
  };
@@ -16258,7 +16329,12 @@ ${contextResult.mergedContent}` : "";
16258
16329
  const tokenCounter2 = getTokenCounter();
16259
16330
  const contextWindow = tokenCounter2.getContextWindow(activeSession.model, state.availableModels);
16260
16331
  const threshold = contextWindow * 0.8;
16261
- const systemPrompt = buildCoreSystemPrompt(state.contextContent || "");
16332
+ const systemPrompt = buildCoreSystemPrompt({
16333
+ contextContent: state.contextContent,
16334
+ agentStore: state.agentStore || void 0,
16335
+ customCommands: state.customCommandStore.getAllCommands(),
16336
+ enableSkillTool: config?.preferences.enableSkillTool !== false
16337
+ });
16262
16338
  const contextUsage = tokenCounter2.countSessionTokens(activeSession, systemPrompt);
16263
16339
  if (contextUsage.totalTokens >= threshold) {
16264
16340
  console.log("\n\u26A0\uFE0F Context window 80% full. Auto-compacting...\n");
@@ -16353,6 +16429,7 @@ ${contextResult.mergedContent}` : "";
16353
16429
  completion: 0,
16354
16430
  total: result.completionInfo.totalTokens
16355
16431
  },
16432
+ creditsUsed: result.completionInfo.totalCredits,
16356
16433
  steps: result.steps.map(formatStep),
16357
16434
  // Complete history: thoughts, actions, observations
16358
16435
  permissionDenied
@@ -16366,6 +16443,7 @@ ${contextResult.mergedContent}` : "";
16366
16443
  metadata: {
16367
16444
  ...currentSession.metadata,
16368
16445
  totalTokens: currentSession.metadata.totalTokens + result.completionInfo.totalTokens,
16446
+ totalCredits: (currentSession.metadata.totalCredits || 0) + (result.completionInfo.totalCredits || 0),
16369
16447
  toolCallCount: currentSession.metadata.toolCallCount + successfulToolCalls
16370
16448
  }
16371
16449
  };
@@ -16464,7 +16542,8 @@ ${contextResult.mergedContent}` : "";
16464
16542
  prompt: 0,
16465
16543
  completion: 0,
16466
16544
  total: result.completionInfo.totalTokens
16467
- }
16545
+ },
16546
+ creditsUsed: result.completionInfo.totalCredits
16468
16547
  }
16469
16548
  };
16470
16549
  const updatedSession = {
@@ -16474,6 +16553,7 @@ ${contextResult.mergedContent}` : "";
16474
16553
  metadata: {
16475
16554
  ...currentSession.metadata,
16476
16555
  totalTokens: currentSession.metadata.totalTokens + result.completionInfo.totalTokens,
16556
+ totalCredits: (currentSession.metadata.totalCredits || 0) + (result.completionInfo.totalCredits || 0),
16477
16557
  toolCallCount: currentSession.metadata.toolCallCount + successfulToolCalls
16478
16558
  }
16479
16559
  };
@@ -16675,7 +16755,7 @@ Available commands:
16675
16755
  /whoami - Show current authenticated user
16676
16756
  /usage - Show credit usage and balance
16677
16757
  /save <name> - Save current session
16678
- /sessions - List saved sessions
16758
+ /resume - List and resume saved sessions
16679
16759
  /config - Show configuration
16680
16760
 
16681
16761
  API Configuration:
@@ -16694,7 +16774,24 @@ Project Configuration:
16694
16774
  Custom Commands:
16695
16775
  /commands - List all custom commands
16696
16776
  /commands:new <name> - Create a new custom command
16697
- /commands:reload - Reload custom commands from disk${hasCustomCommands ? "\n\n\u{1F4DD} Custom Commands Available:" : ""}${hasCustomCommands ? customCommands.map((cmd) => {
16777
+ /commands:reload - Reload custom commands from disk
16778
+
16779
+ Keyboard Shortcuts:
16780
+ Ctrl+C - Press twice to exit
16781
+ Esc - Abort current operation
16782
+ Shift+Tab - Toggle auto-accept edits
16783
+ Ctrl+U - Clear current line
16784
+ Ctrl+K - Clear from cursor to end of line
16785
+ Ctrl+W - Delete word before cursor
16786
+ Ctrl+A - Move cursor to beginning
16787
+ Ctrl+E - Move cursor to end
16788
+ Ctrl+B / \u2190 - Move cursor left
16789
+ Ctrl+F / \u2192 - Move cursor right
16790
+ Ctrl+D - Delete character at cursor
16791
+ Ctrl+L - Clear input
16792
+ \u2191 / \u2193 - Navigate history / autocomplete
16793
+ Tab - Accept autocomplete suggestion
16794
+ Shift+Cmd+Click - Open links in browser${hasCustomCommands ? "\n\n\u{1F4DD} Custom Commands Available:" : ""}${hasCustomCommands ? customCommands.map((cmd) => {
16698
16795
  const source = cmd.source === "global" ? "\u{1F3E0}" : "\u{1F4C1}";
16699
16796
  const argHint = cmd.argumentHint ? ` ${cmd.argumentHint}` : "";
16700
16797
  return `
@@ -16724,6 +16821,7 @@ Custom Commands:
16724
16821
  console.log(`\u2705 Session saved as "${sessionName}"`);
16725
16822
  break;
16726
16823
  }
16824
+ case "resume":
16727
16825
  case "sessions": {
16728
16826
  const sessions = await state.sessionStore.list(20);
16729
16827
  if (sessions.length === 0) {
@@ -16734,7 +16832,6 @@ Custom Commands:
16734
16832
  const handleSessionSelect = async (selectedSession) => {
16735
16833
  setState((prev) => ({ ...prev, sessionSelector: null }));
16736
16834
  if (!selectedSession) {
16737
- console.log("\u274C Session selection cancelled");
16738
16835
  return;
16739
16836
  }
16740
16837
  const loadedSession = await state.sessionStore.load(selectedSession.id);
@@ -17159,15 +17256,21 @@ No usage data available for the last ${USAGE_DAYS} days.`);
17159
17256
  }
17160
17257
  const tokenCounter2 = getTokenCounter();
17161
17258
  const contextWindow = tokenCounter2.getContextWindow(state.session.model, state.availableModels);
17162
- const corePromptTokens = tokenCounter2.countTokens(buildCoreSystemPrompt(""));
17259
+ const corePromptTokens = tokenCounter2.countTokens(buildCoreSystemPrompt());
17163
17260
  const projectContextTokens = state.contextContent ? tokenCounter2.countTokens(state.contextContent) : 0;
17164
17261
  const commands = state.customCommandStore.getAllCommands();
17165
17262
  const skillsSection = buildSkillsPromptSection(commands);
17166
17263
  const skillsTokens = skillsSection ? tokenCounter2.countTokens(skillsSection) : 0;
17264
+ const agentDirectoryTokens = state.agentStore ? tokenCounter2.countTokens(state.agentStore.getDirectoryContext()) : 0;
17167
17265
  const mcpTools = state.mcpManager?.getTools() || [];
17168
17266
  const mcpToolsTokens = tokenCounter2.countToolSchemaTokens(mcpTools);
17169
17267
  const mcpToolCount = state.mcpManager?.getToolCount() || [];
17170
- const systemPrompt = buildCoreSystemPrompt(state.contextContent || "");
17268
+ const systemPrompt = buildCoreSystemPrompt({
17269
+ contextContent: state.contextContent,
17270
+ agentStore: state.agentStore || void 0,
17271
+ customCommands: commands,
17272
+ enableSkillTool: state.config?.preferences.enableSkillTool !== false
17273
+ });
17171
17274
  const usage = tokenCounter2.countSessionTokens(state.session, systemPrompt);
17172
17275
  const totalWithTools = usage.totalTokens + mcpToolsTokens;
17173
17276
  const usagePercent = totalWithTools / contextWindow * 100;
@@ -17186,6 +17289,10 @@ No usage data available for the last ${USAGE_DAYS} days.`);
17186
17289
  if (commands.length > 0) {
17187
17290
  console.log(` Skills Section: ${skillsTokens.toLocaleString()} tokens (${commands.length} skills)`);
17188
17291
  }
17292
+ if (agentDirectoryTokens > 0) {
17293
+ const agentCount = state.agentStore?.getAgentCount() || 0;
17294
+ console.log(` Agent Directory: ${agentDirectoryTokens.toLocaleString()} tokens (${agentCount} agents)`);
17295
+ }
17189
17296
  if (mcpTools.length > 0) {
17190
17297
  console.log("\nMCP Tools:");
17191
17298
  console.log(` Total: ${mcpToolsTokens.toLocaleString()} tokens (${mcpTools.length} tools)`);
@@ -17586,21 +17693,11 @@ No usage data available for the last ${USAGE_DAYS} days.`);
17586
17693
  }
17587
17694
  );
17588
17695
  }
17589
- var BANNER = `
17590
- \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557
17591
- \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551
17592
- \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551
17593
- \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551
17594
- \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551
17595
- \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D
17596
- `;
17597
- console.log("\x1B[36m\x1B[1m%s\x1B[0m", BANNER);
17598
- console.log(`\x1B[2m v${package_default.version} - AI-Powered CLI\x1B[0m`);
17599
- console.log(" /help for more information\n");
17600
17696
  var isDevMode = import.meta.url.includes("/src/") || process.env.NODE_ENV === "development";
17601
17697
  if (isDevMode) {
17602
17698
  logger.debug("\u{1F527} Running in development mode (using TypeScript source)\n");
17603
17699
  }
17700
+ warmFileCache();
17604
17701
  render(/* @__PURE__ */ React21.createElement(CliApp, null), {
17605
17702
  exitOnCtrlC: false
17606
17703
  });