@bike4mind/cli 0.2.7-feat-cli-rewind-command.17128 → 0.2.7-feat-cli-tool-lambda-v2.17129

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.
Files changed (2) hide show
  1. package/dist/index.js +544 -683
  2. package/package.json +6 -6
package/dist/index.js CHANGED
@@ -79,8 +79,8 @@ import {
79
79
  } from "./chunk-PDX44BCA.js";
80
80
 
81
81
  // src/index.tsx
82
- import React14, { useState as useState5, useEffect as useEffect3, useCallback, useRef } from "react";
83
- import { render, Box as Box14, Text as Text13, useApp, useInput as useInput4 } from "ink";
82
+ import React13, { useState as useState4, useEffect as useEffect3, useCallback, useRef } from "react";
83
+ import { render, Box as Box13, Text as Text12, useApp, useInput as useInput3 } from "ink";
84
84
  import { v4 as uuidv49 } from "uuid";
85
85
 
86
86
  // src/components/App.tsx
@@ -389,11 +389,6 @@ var COMMANDS = [
389
389
  name: "clear",
390
390
  description: "Start a new session",
391
391
  aliases: ["new"]
392
- },
393
- {
394
- name: "rewind",
395
- description: "Rewind conversation to a previous point",
396
- aliases: ["undo"]
397
392
  }
398
393
  ];
399
394
 
@@ -630,26 +625,12 @@ function findAtTrigger(value) {
630
625
  }
631
626
  return null;
632
627
  }
633
- function InputPrompt({
634
- onSubmit,
635
- onImageDetected,
636
- disabled = false,
637
- history = [],
638
- prefillInput,
639
- onPrefillConsumed
640
- }) {
628
+ function InputPrompt({ onSubmit, onImageDetected, disabled = false, history = [] }) {
641
629
  const [value, setValue] = useState("");
642
630
  const [selectedIndex, setSelectedIndex] = useState(0);
643
631
  const [historyIndex, setHistoryIndex] = useState(-1);
644
632
  const [tempInput, setTempInput] = useState("");
645
633
  const [inputKey, setInputKey] = useState(0);
646
- useEffect(() => {
647
- if (prefillInput) {
648
- setValue(prefillInput);
649
- setInputKey((prev) => prev + 1);
650
- onPrefillConsumed?.();
651
- }
652
- }, [prefillInput, onPrefillConsumed]);
653
634
  const [fileAutocomplete, setFileAutocomplete] = useState(null);
654
635
  const [fileSelectedIndex, setFileSelectedIndex] = useState(0);
655
636
  const shouldShowCommandAutocomplete = value.startsWith("/") && !disabled && !fileAutocomplete?.active;
@@ -1243,9 +1224,7 @@ function App({
1243
1224
  commandHistory = [],
1244
1225
  config,
1245
1226
  availableModels = [],
1246
- onSaveConfig,
1247
- prefillInput,
1248
- onPrefillConsumed
1227
+ onSaveConfig
1249
1228
  }) {
1250
1229
  const messages = useCliStore((state) => state.session?.messages || []);
1251
1230
  const sessionName = useCliStore((state) => state.session?.name || "New Session");
@@ -1309,9 +1288,7 @@ ${errorBlock}`;
1309
1288
  onSubmit: handleSubmit,
1310
1289
  onImageDetected,
1311
1290
  disabled: isThinking,
1312
- history: commandHistory,
1313
- prefillInput,
1314
- onPrefillConsumed
1291
+ history: commandHistory
1315
1292
  }
1316
1293
  ))));
1317
1294
  }
@@ -1403,78 +1380,9 @@ function TrustLocationSelector({ inProject, onSelect, onCancel }) {
1403
1380
  ), /* @__PURE__ */ React11.createElement(Box11, { marginTop: 1 }, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "Use \u2191\u2193 arrows to navigate, Enter to select, Ctrl+C to cancel")));
1404
1381
  }
1405
1382
 
1406
- // src/components/RewindSelector.tsx
1407
- import React12, { useState as useState3 } from "react";
1408
- import { Box as Box12, Text as Text11, useInput as useInput3 } from "ink";
1409
- import SelectInput3 from "ink-select-input";
1410
- function RewindSelector({ messages, onSelect, onCancel }) {
1411
- const [step, setStep] = useState3("selection");
1412
- const [selectedMessageIndex, setSelectedMessageIndex] = useState3(null);
1413
- useInput3((input, key) => {
1414
- if (key.escape) {
1415
- if (step === "confirmation") {
1416
- setStep("selection");
1417
- setSelectedMessageIndex(null);
1418
- } else {
1419
- onCancel();
1420
- }
1421
- }
1422
- });
1423
- const userMessages = messages.map((msg, idx) => ({ msg, originalIndex: idx })).filter(({ msg }) => msg.role === "user");
1424
- const items = userMessages.map(({ msg, originalIndex }, index) => {
1425
- const preview = msg.content.length > 50 ? msg.content.substring(0, 50) + "..." : msg.content;
1426
- const timestamp = new Date(msg.timestamp).toLocaleString();
1427
- const position = index + 1;
1428
- return {
1429
- label: `[${position}] ${timestamp} - ${preview}`,
1430
- value: originalIndex
1431
- };
1432
- });
1433
- const handleSelectionSelect = (item) => {
1434
- setSelectedMessageIndex(item.value);
1435
- setStep("confirmation");
1436
- };
1437
- const handleConfirmationSelect = (item) => {
1438
- if (item.value === "confirm" && selectedMessageIndex !== null) {
1439
- onSelect(selectedMessageIndex);
1440
- } else {
1441
- setStep("selection");
1442
- setSelectedMessageIndex(null);
1443
- }
1444
- };
1445
- const messagesToRemove = selectedMessageIndex !== null && selectedMessageIndex >= 0 && selectedMessageIndex < messages.length ? messages.slice(selectedMessageIndex) : [];
1446
- const tokensToRemove = messagesToRemove.reduce((sum2, msg) => {
1447
- return sum2 + (msg.metadata?.tokenUsage?.total || 0);
1448
- }, 0);
1449
- if (step === "selection") {
1450
- return /* @__PURE__ */ React12.createElement(Box12, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React12.createElement(Text11, { bold: true }, "Select a point to rewind to:")), /* @__PURE__ */ React12.createElement(
1451
- SelectInput3,
1452
- {
1453
- items,
1454
- onSelect: handleSelectionSelect,
1455
- itemComponent: ({ isSelected, label }) => /* @__PURE__ */ React12.createElement(Box12, null, /* @__PURE__ */ React12.createElement(Text11, { color: isSelected ? "cyan" : void 0 }, isSelected ? "\u276F " : " ", label))
1456
- }
1457
- ), /* @__PURE__ */ React12.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "Use \u2191\u2193 arrows to navigate, Enter to select, Esc to cancel")));
1458
- }
1459
- const confirmationItems = [
1460
- { label: "Yes, rewind to this point", value: "confirm" },
1461
- { label: "No, go back to selection", value: "cancel" }
1462
- ];
1463
- const selectedMessage = selectedMessageIndex !== null && selectedMessageIndex >= 0 && selectedMessageIndex < messages.length ? messages[selectedMessageIndex] : null;
1464
- const selectedPreview = selectedMessage ? selectedMessage.content.length > 50 ? selectedMessage.content.substring(0, 50) + "..." : selectedMessage.content : "";
1465
- return /* @__PURE__ */ React12.createElement(Box12, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1, flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text11, { bold: true }, "Confirm rewind operation:"), /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "Message: ", selectedPreview), /* @__PURE__ */ React12.createElement(Text11, { color: "yellow" }, "This will remove ", messagesToRemove.length, " message(s) (", tokensToRemove.toLocaleString(), " tokens)"), /* @__PURE__ */ React12.createElement(Text11, { color: "cyan" }, "The selected message will be placed in your input for editing.")), /* @__PURE__ */ React12.createElement(
1466
- SelectInput3,
1467
- {
1468
- items: confirmationItems,
1469
- onSelect: handleConfirmationSelect,
1470
- itemComponent: ({ isSelected, label }) => /* @__PURE__ */ React12.createElement(Box12, null, /* @__PURE__ */ React12.createElement(Text11, { color: isSelected ? "cyan" : void 0 }, isSelected ? "\u276F " : " ", label))
1471
- }
1472
- ), /* @__PURE__ */ React12.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "Use \u2191\u2193 arrows to navigate, Enter to select, Esc to go back")));
1473
- }
1474
-
1475
1383
  // src/components/LoginFlow.tsx
1476
- import React13, { useState as useState4, useEffect as useEffect2 } from "react";
1477
- import { Box as Box13, Text as Text12 } from "ink";
1384
+ import React12, { useState as useState3, useEffect as useEffect2 } from "react";
1385
+ import { Box as Box12, Text as Text11 } from "ink";
1478
1386
  import Spinner2 from "ink-spinner";
1479
1387
  import jwt from "jsonwebtoken";
1480
1388
  import open from "open";
@@ -1590,10 +1498,10 @@ var OAuthClient = class {
1590
1498
 
1591
1499
  // src/components/LoginFlow.tsx
1592
1500
  function LoginFlow({ apiUrl = "http://localhost:3000", configStore, onSuccess, onError }) {
1593
- const [status, setStatus] = useState4("initiating");
1594
- const [deviceFlow, setDeviceFlow] = useState4(null);
1595
- const [statusMessage, setStatusMessage] = useState4("Initiating device authorization...");
1596
- const [error, setError] = useState4(null);
1501
+ const [status, setStatus] = useState3("initiating");
1502
+ const [deviceFlow, setDeviceFlow] = useState3(null);
1503
+ const [statusMessage, setStatusMessage] = useState3("Initiating device authorization...");
1504
+ const [error, setError] = useState3(null);
1597
1505
  useEffect2(() => {
1598
1506
  const runLoginFlow = async () => {
1599
1507
  const oauth = new OAuthClient(apiUrl);
@@ -1637,15 +1545,15 @@ function LoginFlow({ apiUrl = "http://localhost:3000", configStore, onSuccess, o
1637
1545
  }
1638
1546
  }, [deviceFlow, status]);
1639
1547
  if (status === "initiating") {
1640
- return /* @__PURE__ */ React13.createElement(Box13, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React13.createElement(Box13, null, /* @__PURE__ */ React13.createElement(Text12, { color: "cyan" }, /* @__PURE__ */ React13.createElement(Spinner2, { type: "dots" }), " Initiating device authorization...")));
1548
+ return /* @__PURE__ */ React12.createElement(Box12, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React12.createElement(Box12, null, /* @__PURE__ */ React12.createElement(Text11, { color: "cyan" }, /* @__PURE__ */ React12.createElement(Spinner2, { type: "dots" }), " Initiating device authorization...")));
1641
1549
  }
1642
1550
  if (status === "error") {
1643
- return /* @__PURE__ */ React13.createElement(Box13, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React13.createElement(Box13, { marginBottom: 1 }, /* @__PURE__ */ React13.createElement(Text12, { color: "red", bold: true }, "\u2716 Authentication Failed")), /* @__PURE__ */ React13.createElement(Box13, null, /* @__PURE__ */ React13.createElement(Text12, { color: "red" }, error)));
1551
+ return /* @__PURE__ */ React12.createElement(Box12, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React12.createElement(Text11, { color: "red", bold: true }, "\u2716 Authentication Failed")), /* @__PURE__ */ React12.createElement(Box12, null, /* @__PURE__ */ React12.createElement(Text11, { color: "red" }, error)));
1644
1552
  }
1645
1553
  if (status === "success") {
1646
- return /* @__PURE__ */ React13.createElement(Box13, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React13.createElement(Box13, { marginBottom: 1 }, /* @__PURE__ */ React13.createElement(Text12, { color: "green", bold: true }, "\u2714 Successfully authenticated!")), /* @__PURE__ */ React13.createElement(Box13, null, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, "You can now use B4M CLI with your account.")));
1554
+ return /* @__PURE__ */ React12.createElement(Box12, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React12.createElement(Text11, { color: "green", bold: true }, "\u2714 Successfully authenticated!")), /* @__PURE__ */ React12.createElement(Box12, null, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "You can now use B4M CLI with your account.")));
1647
1555
  }
1648
- return /* @__PURE__ */ React13.createElement(Box13, { flexDirection: "column", padding: 1, borderStyle: "round", borderColor: "cyan" }, /* @__PURE__ */ React13.createElement(Box13, { marginBottom: 1 }, /* @__PURE__ */ React13.createElement(Text12, { color: "cyan", bold: true }, "\u{1F510} Device Authorization")), /* @__PURE__ */ React13.createElement(Box13, { marginBottom: 1 }, /* @__PURE__ */ React13.createElement(Text12, null, "Opening browser automatically... If it doesn't open, please visit:")), /* @__PURE__ */ React13.createElement(Box13, { marginBottom: 1, paddingLeft: 2 }, /* @__PURE__ */ React13.createElement(Text12, { color: "blue", bold: true }, deviceFlow?.verification_uri)), /* @__PURE__ */ React13.createElement(Box13, { marginBottom: 1 }, /* @__PURE__ */ React13.createElement(Text12, null, "And enter this code when prompted:")), /* @__PURE__ */ React13.createElement(Box13, { marginBottom: 1, paddingLeft: 2 }, /* @__PURE__ */ React13.createElement(Text12, { color: "yellow", bold: true }, deviceFlow?.user_code)), /* @__PURE__ */ React13.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React13.createElement(Text12, { color: "cyan" }, /* @__PURE__ */ React13.createElement(Spinner2, { type: "dots" }), " ", statusMessage)), /* @__PURE__ */ React13.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, "Expires in ", deviceFlow ? Math.floor(deviceFlow.expires_in / 60) : 0, " minutes")));
1556
+ return /* @__PURE__ */ React12.createElement(Box12, { flexDirection: "column", padding: 1, borderStyle: "round", borderColor: "cyan" }, /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React12.createElement(Text11, { color: "cyan", bold: true }, "\u{1F510} Device Authorization")), /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React12.createElement(Text11, null, "Opening browser automatically... If it doesn't open, please visit:")), /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1, paddingLeft: 2 }, /* @__PURE__ */ React12.createElement(Text11, { color: "blue", bold: true }, deviceFlow?.verification_uri)), /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React12.createElement(Text11, null, "And enter this code when prompted:")), /* @__PURE__ */ React12.createElement(Box12, { marginBottom: 1, paddingLeft: 2 }, /* @__PURE__ */ React12.createElement(Text11, { color: "yellow", bold: true }, deviceFlow?.user_code)), /* @__PURE__ */ React12.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text11, { color: "cyan" }, /* @__PURE__ */ React12.createElement(Spinner2, { type: "dots" }), " ", statusMessage)), /* @__PURE__ */ React12.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "Expires in ", deviceFlow ? Math.floor(deviceFlow.expires_in / 60) : 0, " minutes")));
1649
1557
  }
1650
1558
 
1651
1559
  // src/storage/SessionStore.ts
@@ -8855,174 +8763,489 @@ Last modified: ${stats.mtime.toLocaleString()}`;
8855
8763
  }
8856
8764
  }
8857
8765
 
8858
- // src/utils/toolsAdapter.ts
8859
- var NoOpStorage = class extends BaseStorage {
8860
- async upload(input, destination, options) {
8861
- return `/tmp/${destination}`;
8862
- }
8863
- async download(path13) {
8864
- throw new Error("Download not supported in CLI");
8865
- }
8866
- async delete(path13) {
8766
+ // src/utils/Logger.ts
8767
+ import fs10 from "fs/promises";
8768
+ import path11 from "path";
8769
+ import os from "os";
8770
+ var Logger2 = class _Logger {
8771
+ constructor() {
8772
+ this.logFilePath = null;
8773
+ this.sessionId = null;
8774
+ this.fileLoggingEnabled = true;
8775
+ this.consoleVerbose = false;
8867
8776
  }
8868
- async getSignedUrl(path13) {
8869
- return `/tmp/${path13}`;
8777
+ static {
8778
+ this.instance = null;
8870
8779
  }
8871
- getPublicUrl(path13) {
8872
- return `/tmp/${path13}`;
8780
+ static getInstance() {
8781
+ if (!_Logger.instance) {
8782
+ _Logger.instance = new _Logger();
8783
+ }
8784
+ return _Logger.instance;
8873
8785
  }
8874
- async getPreview(path13) {
8875
- return `/tmp/${path13}`;
8786
+ /**
8787
+ * Initialize the logger with a session ID
8788
+ */
8789
+ async initialize(sessionId) {
8790
+ this.sessionId = sessionId;
8791
+ const debugDir = path11.join(os.homedir(), ".bike4mind", "debug");
8792
+ await fs10.mkdir(debugDir, { recursive: true });
8793
+ this.logFilePath = path11.join(debugDir, `${sessionId}.txt`);
8794
+ await this.writeToFile("INFO", "=== CLI SESSION START ===");
8876
8795
  }
8877
- async getMetadata(path13) {
8878
- return { size: 0, contentType: "application/octet-stream" };
8796
+ /**
8797
+ * Set whether verbose console logging is enabled
8798
+ */
8799
+ setVerbose(enabled) {
8800
+ this.consoleVerbose = enabled;
8879
8801
  }
8880
- };
8881
- var CliLogger = class extends Logger {
8882
- constructor() {
8883
- super({ logInJson: false });
8802
+ /**
8803
+ * Set whether file logging is enabled
8804
+ */
8805
+ setFileLoggingEnabled(enabled) {
8806
+ this.fileLoggingEnabled = enabled;
8884
8807
  }
8885
- // Override all logging methods to be silent
8886
- info(...args) {
8808
+ /**
8809
+ * DEBUG level - verbose-only console, always file
8810
+ */
8811
+ debug(message) {
8812
+ this.writeToFile("DEBUG", message).catch(() => {
8813
+ });
8814
+ if (this.consoleVerbose) {
8815
+ console.log(message);
8816
+ }
8887
8817
  }
8888
- warn(...args) {
8818
+ /**
8819
+ * INFO level - always shown to user
8820
+ */
8821
+ info(message) {
8822
+ this.writeToFile("INFO", message).catch(() => {
8823
+ });
8824
+ console.log(message);
8889
8825
  }
8890
- error(...args) {
8826
+ /**
8827
+ * WARN level - always shown to user
8828
+ */
8829
+ warn(message) {
8830
+ this.writeToFile("WARN", message).catch(() => {
8831
+ });
8832
+ console.warn(message);
8891
8833
  }
8892
- debug(...args) {
8834
+ /**
8835
+ * ERROR level - always shown to user
8836
+ */
8837
+ error(message, err) {
8838
+ this.writeToFile("ERROR", message).catch(() => {
8839
+ });
8840
+ if (err) {
8841
+ this.logErrorDetailsToFile(err).catch(() => {
8842
+ });
8843
+ }
8844
+ console.error(message);
8893
8845
  }
8894
- log(...args) {
8846
+ /**
8847
+ * Write log entry to file
8848
+ */
8849
+ async writeToFile(level, message) {
8850
+ if (!this.fileLoggingEnabled || !this.logFilePath) {
8851
+ return;
8852
+ }
8853
+ try {
8854
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19);
8855
+ const logEntry = `[${timestamp}] [${level}] ${message}
8856
+ `;
8857
+ await fs10.appendFile(this.logFilePath, logEntry, "utf-8");
8858
+ } catch (error) {
8859
+ console.error("File logging failed:", error);
8860
+ }
8895
8861
  }
8896
- };
8897
- function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore) {
8898
- const originalFn = tool.toolFn;
8899
- const toolName = tool.toolSchema.name;
8900
- return {
8901
- ...tool,
8902
- toolFn: async (args) => {
8903
- if (!permissionManager.needsPermission(toolName)) {
8904
- const result2 = await originalFn(args);
8905
- agentContext.observationQueue.push({ toolName, result: result2 });
8906
- return result2;
8907
- }
8908
- let preview;
8909
- if (toolName === "edit_file" && args?.path && args?.content) {
8910
- try {
8911
- preview = await generateFileDiffPreview({
8912
- path: args.path,
8913
- content: args.content
8914
- });
8915
- } catch (error) {
8916
- preview = `[Could not generate preview: ${error instanceof Error ? error.message : "Unknown error"}]`;
8917
- }
8918
- } else if (toolName === "create_file" && args?.path && args?.content) {
8919
- try {
8920
- preview = await generateFileDiffPreview({
8921
- path: args.path,
8922
- content: args.content
8923
- });
8924
- } catch (error) {
8925
- preview = `[Could not generate preview: ${error instanceof Error ? error.message : "Unknown error"}]`;
8926
- }
8927
- } else if (toolName === "delete_file" && args?.path) {
8928
- try {
8929
- preview = await generateFileDeletePreview({
8930
- path: args.path
8931
- });
8932
- } catch (error) {
8933
- preview = `[Could not generate preview: ${error instanceof Error ? error.message : "Unknown error"}]`;
8934
- }
8935
- }
8936
- const response = await showPermissionPrompt(toolName, args, preview);
8937
- if (response.action === "deny") {
8938
- throw new PermissionDeniedError(toolName, args);
8939
- }
8940
- if (response.action === "allow-always") {
8941
- const canTrust = permissionManager.trustTool(toolName);
8942
- if (canTrust) {
8943
- const projectDir = configStore.getProjectConfigDir();
8944
- if (projectDir) {
8945
- try {
8946
- await configStore.initProjectConfig();
8947
- const existingLocal = await configStore.loadRawProjectLocalConfig() || {};
8948
- const updatedLocal = {
8949
- ...existingLocal,
8950
- trustedTools: [...existingLocal.trustedTools || [], toolName]
8951
- };
8952
- await configStore.saveProjectLocalConfig(updatedLocal);
8953
- } catch (error) {
8954
- await configStore.trustTool(toolName);
8862
+ /**
8863
+ * Log error details to file
8864
+ */
8865
+ async logErrorDetailsToFile(err) {
8866
+ if (!this.fileLoggingEnabled || !this.logFilePath) {
8867
+ return;
8868
+ }
8869
+ try {
8870
+ if (err && typeof err === "object" && "response" in err && err.response) {
8871
+ const response = err.response;
8872
+ const config = err && typeof err === "object" && "config" in err ? err.config : void 0;
8873
+ await this.writeToFile("ERROR", ` Status: ${response.status} ${response.statusText || ""}`);
8874
+ await this.writeToFile("ERROR", ` URL: ${config?.url || "unknown"}`);
8875
+ await this.writeToFile("ERROR", ` Headers: ${this.safeStringify(response.headers)}`);
8876
+ if (response.data) {
8877
+ const errorText = this.extractErrorMessage(response.data);
8878
+ if (errorText.trim().startsWith("<!DOCTYPE") || errorText.trim().startsWith("<html")) {
8879
+ await this.writeToFile("ERROR", ` Response Type: HTML Error Page`);
8880
+ const parsedError = this.parseHtmlError(errorText);
8881
+ if (parsedError) {
8882
+ await this.writeToFile("ERROR", ` Error Message: ${parsedError}`);
8955
8883
  }
8884
+ await this.writeToFile("ERROR", ` Raw HTML: ${this.truncate(errorText, 1e3)}`);
8956
8885
  } else {
8957
- await configStore.trustTool(toolName);
8886
+ const preview = this.truncate(errorText, 500);
8887
+ await this.writeToFile("ERROR", ` Response: ${preview}`);
8958
8888
  }
8889
+ } else {
8890
+ await this.writeToFile("ERROR", ` Response: (no data)`);
8959
8891
  }
8960
8892
  }
8961
- const result = await originalFn(args);
8962
- agentContext.observationQueue.push({ toolName, result });
8963
- return result;
8964
- }
8965
- };
8966
- }
8967
- function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, toolApiKeys) {
8968
- const logger2 = new CliLogger();
8969
- const storage = new NoOpStorage();
8970
- const user = {
8971
- _id: userId,
8972
- email: "cli-user@bike4mind.local",
8973
- firstName: "CLI",
8974
- lastName: "User"
8975
- };
8976
- const statusUpdate = async (q, status) => {
8977
- };
8978
- const onStart = async (toolName, data) => {
8979
- };
8980
- const onFinish = async (toolName, data) => {
8981
- };
8982
- const mockAdminSettings = {
8983
- findBySettingName: async (settingName) => {
8984
- if (settingName === "OpenWeatherKey") {
8985
- const key = toolApiKeys?.openweather || process.env.OPENWEATHER_API_KEY;
8986
- if (key) {
8987
- return { settingValue: key };
8988
- }
8989
- }
8990
- if (settingName === "SerperKey") {
8991
- const key = toolApiKeys?.serper || process.env.SERPER_API_KEY;
8992
- if (key) {
8993
- return { settingValue: key };
8994
- }
8893
+ if (err && typeof err === "object" && "stack" in err && typeof err.stack === "string") {
8894
+ const stackLines = err.stack.split("\n").slice(0, 5).join("\n ");
8895
+ await this.writeToFile("ERROR", ` Stack: ${stackLines}`);
8896
+ } else if (err && typeof err === "object" && "message" in err && typeof err.message === "string") {
8897
+ await this.writeToFile("ERROR", ` Message: ${err.message}`);
8995
8898
  }
8996
- return null;
8899
+ } catch (error) {
8997
8900
  }
8998
- };
8999
- const dbAdapters = {
9000
- db: {
8901
+ }
8902
+ /**
8903
+ * Safely stringify object, handling circular references
8904
+ */
8905
+ safeStringify(obj) {
8906
+ try {
8907
+ return JSON.stringify(obj);
8908
+ } catch (error) {
8909
+ if (error instanceof Error && error.message.includes("circular")) {
8910
+ try {
8911
+ const seen = /* @__PURE__ */ new WeakSet();
8912
+ return JSON.stringify(obj, (key, value) => {
8913
+ if (typeof value === "object" && value !== null) {
8914
+ if (seen.has(value)) {
8915
+ return "[Circular]";
8916
+ }
8917
+ seen.add(value);
8918
+ }
8919
+ return value;
8920
+ });
8921
+ } catch {
8922
+ return "[Unable to stringify]";
8923
+ }
8924
+ }
8925
+ return "[Stringify error]";
8926
+ }
8927
+ }
8928
+ /**
8929
+ * Extract readable text from error response data
8930
+ */
8931
+ extractErrorMessage(data) {
8932
+ if (Buffer.isBuffer(data)) {
8933
+ return data.toString("utf-8");
8934
+ }
8935
+ if (typeof data === "string") {
8936
+ return data;
8937
+ }
8938
+ 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) {
8939
+ const chunks = [];
8940
+ for (const chunk of data._readableState.buffer) {
8941
+ if (chunk && typeof chunk === "object" && "type" in chunk && chunk.type === "Buffer" && "data" in chunk && Array.isArray(chunk.data)) {
8942
+ chunks.push(Buffer.from(chunk.data));
8943
+ } else if (Buffer.isBuffer(chunk)) {
8944
+ chunks.push(chunk);
8945
+ } else if (chunk && typeof chunk === "object" && "data" in chunk) {
8946
+ if (Buffer.isBuffer(chunk.data)) {
8947
+ chunks.push(chunk.data);
8948
+ } else if (Array.isArray(chunk.data)) {
8949
+ chunks.push(Buffer.from(chunk.data));
8950
+ }
8951
+ }
8952
+ }
8953
+ if (chunks.length > 0) {
8954
+ return Buffer.concat(chunks).toString("utf-8");
8955
+ }
8956
+ }
8957
+ return this.safeStringify(data);
8958
+ }
8959
+ /**
8960
+ * Parse HTML error page to extract error message
8961
+ */
8962
+ parseHtmlError(html) {
8963
+ const titleMatch = html.match(/<title>(.*?)<\/title>/i);
8964
+ const h1Match = html.match(/<h1>(.*?)<\/h1>/i);
8965
+ const bodyMatch = html.match(/<body[^>]*>(.*?)<\/body>/is);
8966
+ if (titleMatch && titleMatch[1] !== "Error") {
8967
+ return titleMatch[1].trim();
8968
+ }
8969
+ if (h1Match) {
8970
+ return h1Match[1].trim();
8971
+ }
8972
+ if (bodyMatch) {
8973
+ const text = bodyMatch[1].replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
8974
+ return text.substring(0, 200);
8975
+ }
8976
+ return null;
8977
+ }
8978
+ /**
8979
+ * Truncate string to max length with ellipsis
8980
+ */
8981
+ truncate(str, maxLength) {
8982
+ if (str.length <= maxLength) {
8983
+ return str;
8984
+ }
8985
+ return str.substring(0, maxLength) + "... [truncated]";
8986
+ }
8987
+ /**
8988
+ * Format bytes to human-readable size
8989
+ */
8990
+ formatBytes(bytes) {
8991
+ if (bytes === 0) return "0 B";
8992
+ const k = 1024;
8993
+ const sizes = ["B", "KB", "MB", "GB"];
8994
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
8995
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + " " + sizes[i];
8996
+ }
8997
+ /**
8998
+ * Clean up old debug logs (older than 30 days)
8999
+ */
9000
+ async cleanupOldLogs() {
9001
+ if (!this.fileLoggingEnabled) return;
9002
+ try {
9003
+ const debugDir = path11.join(os.homedir(), ".bike4mind", "debug");
9004
+ const files = await fs10.readdir(debugDir);
9005
+ const now = Date.now();
9006
+ const thirtyDaysAgo = now - 30 * 24 * 60 * 60 * 1e3;
9007
+ for (const file of files) {
9008
+ const filePath = path11.join(debugDir, file);
9009
+ const stats = await fs10.stat(filePath);
9010
+ if (stats.mtime.getTime() < thirtyDaysAgo) {
9011
+ await fs10.unlink(filePath);
9012
+ }
9013
+ }
9014
+ } catch (error) {
9015
+ console.error("Failed to cleanup old logs:", error);
9016
+ }
9017
+ }
9018
+ };
9019
+ var logger = Logger2.getInstance();
9020
+
9021
+ // src/llm/ServerToolExecutor.ts
9022
+ var ServerToolExecutor = class {
9023
+ constructor(apiClient) {
9024
+ this.toolsEndpoint = "/api/ai/v1/tools";
9025
+ this.apiClient = apiClient;
9026
+ }
9027
+ /**
9028
+ * Execute a tool server-side
9029
+ *
9030
+ * @param toolName - Name of the tool to execute (e.g., 'weather_info', 'web_search')
9031
+ * @param input - Tool input parameters
9032
+ * @returns Tool execution result (content or error)
9033
+ */
9034
+ async executeTool(toolName, input) {
9035
+ logger.debug(`[ServerToolExecutor] Executing tool: ${toolName}`);
9036
+ logger.debug(`[ServerToolExecutor] Endpoint: ${this.toolsEndpoint}`);
9037
+ logger.debug(`[ServerToolExecutor] Input: ${JSON.stringify(input)}`);
9038
+ try {
9039
+ const result = await this.apiClient.post(this.toolsEndpoint, {
9040
+ toolName,
9041
+ input
9042
+ });
9043
+ logger.debug(`[ServerToolExecutor] Response: ${JSON.stringify(result)}`);
9044
+ if (!result.success) {
9045
+ const errorMessage = result.error || "Tool execution failed";
9046
+ logger.error(`[ServerToolExecutor] Tool ${toolName} failed: ${errorMessage}`);
9047
+ return `Error executing ${toolName}: ${errorMessage}`;
9048
+ }
9049
+ logger.debug(`[ServerToolExecutor] Tool ${toolName} succeeded in ${result.executionTimeMs}ms`);
9050
+ return result.content || "";
9051
+ } catch (error) {
9052
+ const err = error;
9053
+ const errorMessage = err?.response?.data?.error || err?.message || "Unknown error";
9054
+ const status = err?.response?.status || "unknown";
9055
+ logger.error(`[ServerToolExecutor] Request failed for ${toolName}: ${errorMessage} (status: ${status})`);
9056
+ logger.error(`[ServerToolExecutor] Full error: ${JSON.stringify(err, null, 2)}`);
9057
+ return `Error executing ${toolName}: ${errorMessage}`;
9058
+ }
9059
+ }
9060
+ };
9061
+
9062
+ // src/llm/ToolRouter.ts
9063
+ var SERVER_TOOLS = ["weather_info", "web_search"];
9064
+ var LOCAL_TOOLS = [
9065
+ "file_read",
9066
+ "create_file",
9067
+ "glob_files",
9068
+ "grep_search",
9069
+ "delete_file",
9070
+ "edit_file",
9071
+ "dice_roll",
9072
+ "math_evaluate",
9073
+ "current_datetime"
9074
+ ];
9075
+ function isServerTool(toolName) {
9076
+ return SERVER_TOOLS.includes(toolName);
9077
+ }
9078
+ function isLocalTool(toolName) {
9079
+ return LOCAL_TOOLS.includes(toolName);
9080
+ }
9081
+ async function executeTool(toolName, input, apiClient, localToolFn) {
9082
+ if (isServerTool(toolName)) {
9083
+ logger.debug(`[ToolRouter] Routing ${toolName} to server`);
9084
+ const executor = new ServerToolExecutor(apiClient);
9085
+ return await executor.executeTool(toolName, input);
9086
+ } else if (isLocalTool(toolName)) {
9087
+ logger.debug(`[ToolRouter] Executing ${toolName} locally`);
9088
+ if (!localToolFn) {
9089
+ throw new Error(`Local tool ${toolName} has no implementation`);
9090
+ }
9091
+ const result = await localToolFn(input);
9092
+ return typeof result === "string" ? result : JSON.stringify(result);
9093
+ } else {
9094
+ throw new Error(`Unknown tool: ${toolName}`);
9095
+ }
9096
+ }
9097
+
9098
+ // src/utils/toolsAdapter.ts
9099
+ var NoOpStorage = class extends BaseStorage {
9100
+ async upload(input, destination, options) {
9101
+ return `/tmp/${destination}`;
9102
+ }
9103
+ async download(path13) {
9104
+ throw new Error("Download not supported in CLI");
9105
+ }
9106
+ async delete(path13) {
9107
+ }
9108
+ async getSignedUrl(path13) {
9109
+ return `/tmp/${path13}`;
9110
+ }
9111
+ getPublicUrl(path13) {
9112
+ return `/tmp/${path13}`;
9113
+ }
9114
+ async getPreview(path13) {
9115
+ return `/tmp/${path13}`;
9116
+ }
9117
+ async getMetadata(path13) {
9118
+ return { size: 0, contentType: "application/octet-stream" };
9119
+ }
9120
+ };
9121
+ var CliLogger = class extends Logger {
9122
+ constructor() {
9123
+ super({ logInJson: false });
9124
+ }
9125
+ // Override all logging methods to be silent
9126
+ info(...args) {
9127
+ }
9128
+ warn(...args) {
9129
+ }
9130
+ error(...args) {
9131
+ }
9132
+ debug(...args) {
9133
+ }
9134
+ log(...args) {
9135
+ }
9136
+ };
9137
+ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient) {
9138
+ const originalFn = tool.toolFn;
9139
+ const toolName = tool.toolSchema.name;
9140
+ return {
9141
+ ...tool,
9142
+ toolFn: async (args) => {
9143
+ if (!permissionManager.needsPermission(toolName)) {
9144
+ const result2 = await executeTool(toolName, args, apiClient, originalFn);
9145
+ agentContext.observationQueue.push({ toolName, result: result2 });
9146
+ return result2;
9147
+ }
9148
+ let preview;
9149
+ if (toolName === "edit_file" && args?.path && args?.content) {
9150
+ try {
9151
+ preview = await generateFileDiffPreview({
9152
+ path: args.path,
9153
+ content: args.content
9154
+ });
9155
+ } catch (error) {
9156
+ preview = `[Could not generate preview: ${error instanceof Error ? error.message : "Unknown error"}]`;
9157
+ }
9158
+ } else if (toolName === "create_file" && args?.path && args?.content) {
9159
+ try {
9160
+ preview = await generateFileDiffPreview({
9161
+ path: args.path,
9162
+ content: args.content
9163
+ });
9164
+ } catch (error) {
9165
+ preview = `[Could not generate preview: ${error instanceof Error ? error.message : "Unknown error"}]`;
9166
+ }
9167
+ } else if (toolName === "delete_file" && args?.path) {
9168
+ try {
9169
+ preview = await generateFileDeletePreview({
9170
+ path: args.path
9171
+ });
9172
+ } catch (error) {
9173
+ preview = `[Could not generate preview: ${error instanceof Error ? error.message : "Unknown error"}]`;
9174
+ }
9175
+ }
9176
+ const response = await showPermissionPrompt(toolName, args, preview);
9177
+ if (response.action === "deny") {
9178
+ throw new PermissionDeniedError(toolName, args);
9179
+ }
9180
+ if (response.action === "allow-always") {
9181
+ const canTrust = permissionManager.trustTool(toolName);
9182
+ if (canTrust) {
9183
+ const projectDir = configStore.getProjectConfigDir();
9184
+ if (projectDir) {
9185
+ try {
9186
+ await configStore.initProjectConfig();
9187
+ const existingLocal = await configStore.loadRawProjectLocalConfig() || {};
9188
+ const updatedLocal = {
9189
+ ...existingLocal,
9190
+ trustedTools: [...existingLocal.trustedTools || [], toolName]
9191
+ };
9192
+ await configStore.saveProjectLocalConfig(updatedLocal);
9193
+ } catch (error) {
9194
+ await configStore.trustTool(toolName);
9195
+ }
9196
+ } else {
9197
+ await configStore.trustTool(toolName);
9198
+ }
9199
+ }
9200
+ }
9201
+ const result = await executeTool(toolName, args, apiClient, originalFn);
9202
+ agentContext.observationQueue.push({ toolName, result });
9203
+ return result;
9204
+ }
9205
+ };
9206
+ }
9207
+ function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient) {
9208
+ const logger2 = new CliLogger();
9209
+ const storage = new NoOpStorage();
9210
+ const user = {
9211
+ _id: userId,
9212
+ email: "cli-user@bike4mind.local",
9213
+ firstName: "CLI",
9214
+ lastName: "User"
9215
+ };
9216
+ const statusUpdate = async (q, status) => {
9217
+ };
9218
+ const onStart = async (toolName, data) => {
9219
+ };
9220
+ const onFinish = async (toolName, data) => {
9221
+ };
9222
+ const mockAdminSettings = {
9223
+ findBySettingName: async (settingName) => {
9224
+ return null;
9225
+ }
9226
+ };
9227
+ const dbAdapters = {
9228
+ db: {
9001
9229
  apiKeys: null,
9002
9230
  adminSettings: mockAdminSettings
9003
9231
  }
9004
9232
  };
9005
- const hasSerperKey = !!(toolApiKeys?.serper || process.env.SERPER_API_KEY);
9006
- const hasOpenWeatherKey = !!(toolApiKeys?.openweather || process.env.OPENWEATHER_API_KEY);
9007
9233
  const toolConfig = {
9008
- // Enable these tools for CLI (no external API keys needed)
9234
+ // Local-only tools (no external API keys needed)
9009
9235
  dice_roll: {},
9010
9236
  math_evaluate: {},
9011
9237
  current_datetime: {},
9012
9238
  prompt_enhancement: {},
9013
- // File operation tools (CLI-specific)
9239
+ // File operation tools (CLI-specific, local execution)
9014
9240
  file_read: {},
9015
9241
  create_file: {},
9016
9242
  glob_files: {},
9017
9243
  grep_search: {},
9018
9244
  delete_file: {},
9019
- // Tools requiring API keys (only include if API key is available)
9020
- ...hasSerperKey && { web_search: {} },
9021
- // Requires SERPER_API_KEY
9022
- ...hasOpenWeatherKey && { weather_info: {} },
9023
- // Requires OPENWEATHER_API_KEY
9024
- ...hasSerperKey && { deep_research: {} }
9025
- // Requires SERPER_API_KEY
9245
+ // Server-side tools (executed via /api/ai/v1/tools Lambda)
9246
+ // Routed by ToolRouter to use B4M company API keys
9247
+ weather_info: {},
9248
+ web_search: {}
9026
9249
  // Disable web-only tools
9027
9250
  // blog_publish: undefined,
9028
9251
  // blog_edit: undefined,
@@ -9032,6 +9255,7 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
9032
9255
  // mermaid_chart: undefined,
9033
9256
  // recharts: undefined,
9034
9257
  // edit_file: undefined,
9258
+ // deep_research: undefined,
9035
9259
  };
9036
9260
  const toolsMap = generateTools(
9037
9261
  userId,
@@ -9049,7 +9273,7 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
9049
9273
  model
9050
9274
  );
9051
9275
  const tools2 = Object.entries(toolsMap).filter(([key]) => toolConfig[key] !== void 0).map(
9052
- ([_, tool]) => wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore)
9276
+ ([_, tool]) => wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient)
9053
9277
  );
9054
9278
  return { tools: tools2, agentContext };
9055
9279
  }
@@ -9159,345 +9383,90 @@ var PermissionManager = class {
9159
9383
  if (!canTrustTool(toolName, categoryMap)) {
9160
9384
  return false;
9161
9385
  }
9162
- this.trustedTools.add(toolName);
9163
- return true;
9164
- }
9165
- /**
9166
- * Remove a tool from the trusted list
9167
- */
9168
- untrustTool(toolName) {
9169
- this.trustedTools.delete(toolName);
9170
- }
9171
- /**
9172
- * Get list of all trusted tools
9173
- */
9174
- getTrustedTools() {
9175
- return Array.from(this.trustedTools).sort();
9176
- }
9177
- /**
9178
- * Check if a tool is currently trusted
9179
- */
9180
- isTrusted(toolName) {
9181
- return this.trustedTools.has(toolName);
9182
- }
9183
- /**
9184
- * Get the category for a tool
9185
- */
9186
- getCategory(toolName) {
9187
- const categoryMap = Object.fromEntries(this.customCategories);
9188
- return getToolCategory(toolName, categoryMap);
9189
- }
9190
- /**
9191
- * Check if a tool can be trusted (not in prompt_always category or denied by project)
9192
- */
9193
- canBeTrusted(toolName) {
9194
- const categoryMap = Object.fromEntries(this.customCategories);
9195
- if (this.deniedTools.has(toolName)) {
9196
- return false;
9197
- }
9198
- return canTrustTool(toolName, categoryMap);
9199
- }
9200
- /**
9201
- * Check if a tool is denied by project config
9202
- */
9203
- isDenied(toolName) {
9204
- return this.deniedTools.has(toolName);
9205
- }
9206
- /**
9207
- * Get list of denied tools
9208
- */
9209
- getDeniedTools() {
9210
- return Array.from(this.deniedTools).sort();
9211
- }
9212
- /**
9213
- * Clear all trusted tools
9214
- */
9215
- clearTrustedTools() {
9216
- this.trustedTools.clear();
9217
- }
9218
- /**
9219
- * Get statistics about permissions
9220
- */
9221
- getStats() {
9222
- return {
9223
- trustedCount: this.trustedTools.size,
9224
- autoApproveCount: 0,
9225
- // TODO: Would need full tool list
9226
- promptAlwaysCount: 0,
9227
- promptDefaultCount: 0
9228
- };
9229
- }
9230
- };
9231
-
9232
- // src/utils/apiUrl.ts
9233
- var BIKE4MIND_URL = "https://app.bike4mind.com";
9234
- function getApiUrl(configApiConfig) {
9235
- if (configApiConfig?.customUrl) {
9236
- return configApiConfig.customUrl;
9237
- }
9238
- return BIKE4MIND_URL;
9239
- }
9240
- function getEnvironmentName(configApiConfig) {
9241
- if (configApiConfig?.customUrl) {
9242
- return "Self-Hosted";
9243
- }
9244
- return "Bike4Mind";
9245
- }
9246
-
9247
- // src/utils/Logger.ts
9248
- import fs10 from "fs/promises";
9249
- import path11 from "path";
9250
- import os from "os";
9251
- var Logger2 = class _Logger {
9252
- constructor() {
9253
- this.logFilePath = null;
9254
- this.sessionId = null;
9255
- this.fileLoggingEnabled = true;
9256
- this.consoleVerbose = false;
9257
- }
9258
- static {
9259
- this.instance = null;
9260
- }
9261
- static getInstance() {
9262
- if (!_Logger.instance) {
9263
- _Logger.instance = new _Logger();
9264
- }
9265
- return _Logger.instance;
9266
- }
9267
- /**
9268
- * Initialize the logger with a session ID
9269
- */
9270
- async initialize(sessionId) {
9271
- this.sessionId = sessionId;
9272
- const debugDir = path11.join(os.homedir(), ".bike4mind", "debug");
9273
- await fs10.mkdir(debugDir, { recursive: true });
9274
- this.logFilePath = path11.join(debugDir, `${sessionId}.txt`);
9275
- await this.writeToFile("INFO", "=== CLI SESSION START ===");
9276
- }
9277
- /**
9278
- * Set whether verbose console logging is enabled
9279
- */
9280
- setVerbose(enabled) {
9281
- this.consoleVerbose = enabled;
9282
- }
9283
- /**
9284
- * Set whether file logging is enabled
9285
- */
9286
- setFileLoggingEnabled(enabled) {
9287
- this.fileLoggingEnabled = enabled;
9288
- }
9289
- /**
9290
- * DEBUG level - verbose-only console, always file
9291
- */
9292
- debug(message) {
9293
- this.writeToFile("DEBUG", message).catch(() => {
9294
- });
9295
- if (this.consoleVerbose) {
9296
- console.log(message);
9297
- }
9298
- }
9299
- /**
9300
- * INFO level - always shown to user
9301
- */
9302
- info(message) {
9303
- this.writeToFile("INFO", message).catch(() => {
9304
- });
9305
- console.log(message);
9306
- }
9307
- /**
9308
- * WARN level - always shown to user
9309
- */
9310
- warn(message) {
9311
- this.writeToFile("WARN", message).catch(() => {
9312
- });
9313
- console.warn(message);
9386
+ this.trustedTools.add(toolName);
9387
+ return true;
9314
9388
  }
9315
9389
  /**
9316
- * ERROR level - always shown to user
9390
+ * Remove a tool from the trusted list
9317
9391
  */
9318
- error(message, err) {
9319
- this.writeToFile("ERROR", message).catch(() => {
9320
- });
9321
- if (err) {
9322
- this.logErrorDetailsToFile(err).catch(() => {
9323
- });
9324
- }
9325
- console.error(message);
9392
+ untrustTool(toolName) {
9393
+ this.trustedTools.delete(toolName);
9326
9394
  }
9327
9395
  /**
9328
- * Write log entry to file
9396
+ * Get list of all trusted tools
9329
9397
  */
9330
- async writeToFile(level, message) {
9331
- if (!this.fileLoggingEnabled || !this.logFilePath) {
9332
- return;
9333
- }
9334
- try {
9335
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19);
9336
- const logEntry = `[${timestamp}] [${level}] ${message}
9337
- `;
9338
- await fs10.appendFile(this.logFilePath, logEntry, "utf-8");
9339
- } catch (error) {
9340
- console.error("File logging failed:", error);
9341
- }
9398
+ getTrustedTools() {
9399
+ return Array.from(this.trustedTools).sort();
9342
9400
  }
9343
9401
  /**
9344
- * Log error details to file
9402
+ * Check if a tool is currently trusted
9345
9403
  */
9346
- async logErrorDetailsToFile(err) {
9347
- if (!this.fileLoggingEnabled || !this.logFilePath) {
9348
- return;
9349
- }
9350
- try {
9351
- if (err && typeof err === "object" && "response" in err && err.response) {
9352
- const response = err.response;
9353
- const config = err && typeof err === "object" && "config" in err ? err.config : void 0;
9354
- await this.writeToFile("ERROR", ` Status: ${response.status} ${response.statusText || ""}`);
9355
- await this.writeToFile("ERROR", ` URL: ${config?.url || "unknown"}`);
9356
- await this.writeToFile("ERROR", ` Headers: ${this.safeStringify(response.headers)}`);
9357
- if (response.data) {
9358
- const errorText = this.extractErrorMessage(response.data);
9359
- if (errorText.trim().startsWith("<!DOCTYPE") || errorText.trim().startsWith("<html")) {
9360
- await this.writeToFile("ERROR", ` Response Type: HTML Error Page`);
9361
- const parsedError = this.parseHtmlError(errorText);
9362
- if (parsedError) {
9363
- await this.writeToFile("ERROR", ` Error Message: ${parsedError}`);
9364
- }
9365
- await this.writeToFile("ERROR", ` Raw HTML: ${this.truncate(errorText, 1e3)}`);
9366
- } else {
9367
- const preview = this.truncate(errorText, 500);
9368
- await this.writeToFile("ERROR", ` Response: ${preview}`);
9369
- }
9370
- } else {
9371
- await this.writeToFile("ERROR", ` Response: (no data)`);
9372
- }
9373
- }
9374
- if (err && typeof err === "object" && "stack" in err && typeof err.stack === "string") {
9375
- const stackLines = err.stack.split("\n").slice(0, 5).join("\n ");
9376
- await this.writeToFile("ERROR", ` Stack: ${stackLines}`);
9377
- } else if (err && typeof err === "object" && "message" in err && typeof err.message === "string") {
9378
- await this.writeToFile("ERROR", ` Message: ${err.message}`);
9379
- }
9380
- } catch (error) {
9381
- }
9404
+ isTrusted(toolName) {
9405
+ return this.trustedTools.has(toolName);
9382
9406
  }
9383
9407
  /**
9384
- * Safely stringify object, handling circular references
9408
+ * Get the category for a tool
9385
9409
  */
9386
- safeStringify(obj) {
9387
- try {
9388
- return JSON.stringify(obj);
9389
- } catch (error) {
9390
- if (error instanceof Error && error.message.includes("circular")) {
9391
- try {
9392
- const seen = /* @__PURE__ */ new WeakSet();
9393
- return JSON.stringify(obj, (key, value) => {
9394
- if (typeof value === "object" && value !== null) {
9395
- if (seen.has(value)) {
9396
- return "[Circular]";
9397
- }
9398
- seen.add(value);
9399
- }
9400
- return value;
9401
- });
9402
- } catch {
9403
- return "[Unable to stringify]";
9404
- }
9405
- }
9406
- return "[Stringify error]";
9407
- }
9410
+ getCategory(toolName) {
9411
+ const categoryMap = Object.fromEntries(this.customCategories);
9412
+ return getToolCategory(toolName, categoryMap);
9408
9413
  }
9409
9414
  /**
9410
- * Extract readable text from error response data
9415
+ * Check if a tool can be trusted (not in prompt_always category or denied by project)
9411
9416
  */
9412
- extractErrorMessage(data) {
9413
- if (Buffer.isBuffer(data)) {
9414
- return data.toString("utf-8");
9415
- }
9416
- if (typeof data === "string") {
9417
- return data;
9418
- }
9419
- 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) {
9420
- const chunks = [];
9421
- for (const chunk of data._readableState.buffer) {
9422
- if (chunk && typeof chunk === "object" && "type" in chunk && chunk.type === "Buffer" && "data" in chunk && Array.isArray(chunk.data)) {
9423
- chunks.push(Buffer.from(chunk.data));
9424
- } else if (Buffer.isBuffer(chunk)) {
9425
- chunks.push(chunk);
9426
- } else if (chunk && typeof chunk === "object" && "data" in chunk) {
9427
- if (Buffer.isBuffer(chunk.data)) {
9428
- chunks.push(chunk.data);
9429
- } else if (Array.isArray(chunk.data)) {
9430
- chunks.push(Buffer.from(chunk.data));
9431
- }
9432
- }
9433
- }
9434
- if (chunks.length > 0) {
9435
- return Buffer.concat(chunks).toString("utf-8");
9436
- }
9417
+ canBeTrusted(toolName) {
9418
+ const categoryMap = Object.fromEntries(this.customCategories);
9419
+ if (this.deniedTools.has(toolName)) {
9420
+ return false;
9437
9421
  }
9438
- return this.safeStringify(data);
9422
+ return canTrustTool(toolName, categoryMap);
9439
9423
  }
9440
9424
  /**
9441
- * Parse HTML error page to extract error message
9425
+ * Check if a tool is denied by project config
9442
9426
  */
9443
- parseHtmlError(html) {
9444
- const titleMatch = html.match(/<title>(.*?)<\/title>/i);
9445
- const h1Match = html.match(/<h1>(.*?)<\/h1>/i);
9446
- const bodyMatch = html.match(/<body[^>]*>(.*?)<\/body>/is);
9447
- if (titleMatch && titleMatch[1] !== "Error") {
9448
- return titleMatch[1].trim();
9449
- }
9450
- if (h1Match) {
9451
- return h1Match[1].trim();
9452
- }
9453
- if (bodyMatch) {
9454
- const text = bodyMatch[1].replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
9455
- return text.substring(0, 200);
9456
- }
9457
- return null;
9427
+ isDenied(toolName) {
9428
+ return this.deniedTools.has(toolName);
9458
9429
  }
9459
9430
  /**
9460
- * Truncate string to max length with ellipsis
9431
+ * Get list of denied tools
9461
9432
  */
9462
- truncate(str, maxLength) {
9463
- if (str.length <= maxLength) {
9464
- return str;
9465
- }
9466
- return str.substring(0, maxLength) + "... [truncated]";
9433
+ getDeniedTools() {
9434
+ return Array.from(this.deniedTools).sort();
9467
9435
  }
9468
9436
  /**
9469
- * Format bytes to human-readable size
9437
+ * Clear all trusted tools
9470
9438
  */
9471
- formatBytes(bytes) {
9472
- if (bytes === 0) return "0 B";
9473
- const k = 1024;
9474
- const sizes = ["B", "KB", "MB", "GB"];
9475
- const i = Math.floor(Math.log(bytes) / Math.log(k));
9476
- return Math.round(bytes / Math.pow(k, i) * 100) / 100 + " " + sizes[i];
9439
+ clearTrustedTools() {
9440
+ this.trustedTools.clear();
9477
9441
  }
9478
9442
  /**
9479
- * Clean up old debug logs (older than 30 days)
9443
+ * Get statistics about permissions
9480
9444
  */
9481
- async cleanupOldLogs() {
9482
- if (!this.fileLoggingEnabled) return;
9483
- try {
9484
- const debugDir = path11.join(os.homedir(), ".bike4mind", "debug");
9485
- const files = await fs10.readdir(debugDir);
9486
- const now = Date.now();
9487
- const thirtyDaysAgo = now - 30 * 24 * 60 * 60 * 1e3;
9488
- for (const file of files) {
9489
- const filePath = path11.join(debugDir, file);
9490
- const stats = await fs10.stat(filePath);
9491
- if (stats.mtime.getTime() < thirtyDaysAgo) {
9492
- await fs10.unlink(filePath);
9493
- }
9494
- }
9495
- } catch (error) {
9496
- console.error("Failed to cleanup old logs:", error);
9497
- }
9445
+ getStats() {
9446
+ return {
9447
+ trustedCount: this.trustedTools.size,
9448
+ autoApproveCount: 0,
9449
+ // TODO: Would need full tool list
9450
+ promptAlwaysCount: 0,
9451
+ promptDefaultCount: 0
9452
+ };
9498
9453
  }
9499
9454
  };
9500
- var logger = Logger2.getInstance();
9455
+
9456
+ // src/utils/apiUrl.ts
9457
+ var BIKE4MIND_URL = "https://app.bike4mind.com";
9458
+ function getApiUrl(configApiConfig) {
9459
+ if (configApiConfig?.customUrl) {
9460
+ return configApiConfig.customUrl;
9461
+ }
9462
+ return BIKE4MIND_URL;
9463
+ }
9464
+ function getEnvironmentName(configApiConfig) {
9465
+ if (configApiConfig?.customUrl) {
9466
+ return "Self-Hosted";
9467
+ }
9468
+ return "Bike4Mind";
9469
+ }
9501
9470
 
9502
9471
  // ../../b4m-core/packages/mcp/dist/src/client.js
9503
9472
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
@@ -10302,7 +10271,10 @@ var ApiClient = class {
10302
10271
  * Make a POST request
10303
10272
  */
10304
10273
  async post(url, data, config) {
10274
+ logger.debug(`[ApiClient] POST ${this.client.defaults.baseURL}${url}`);
10275
+ logger.debug(`[ApiClient] Request body: ${JSON.stringify(data)}`);
10305
10276
  const response = await this.client.post(url, data, config);
10277
+ logger.debug(`[ApiClient] Response status: ${response.status}`);
10306
10278
  return response.data;
10307
10279
  }
10308
10280
  /**
@@ -10353,7 +10325,7 @@ import { isAxiosError as isAxiosError2 } from "axios";
10353
10325
  // package.json
10354
10326
  var package_default = {
10355
10327
  name: "@bike4mind/cli",
10356
- version: "0.2.7-feat-cli-rewind-command.17128+8d4de110f",
10328
+ version: "0.2.7-feat-cli-tool-lambda-v2.17129+8e46f37a5",
10357
10329
  type: "module",
10358
10330
  description: "Interactive CLI tool for Bike4Mind with ReAct agents",
10359
10331
  license: "UNLICENSED",
@@ -10456,10 +10428,10 @@ var package_default = {
10456
10428
  },
10457
10429
  devDependencies: {
10458
10430
  "@bike4mind/agents": "0.1.0",
10459
- "@bike4mind/common": "2.38.1-feat-cli-rewind-command.17128+8d4de110f",
10460
- "@bike4mind/mcp": "1.20.1-feat-cli-rewind-command.17128+8d4de110f",
10461
- "@bike4mind/services": "2.33.1-feat-cli-rewind-command.17128+8d4de110f",
10462
- "@bike4mind/utils": "2.1.2-feat-cli-rewind-command.17128+8d4de110f",
10431
+ "@bike4mind/common": "2.38.1-feat-cli-tool-lambda-v2.17129+8e46f37a5",
10432
+ "@bike4mind/mcp": "1.20.1-feat-cli-tool-lambda-v2.17129+8e46f37a5",
10433
+ "@bike4mind/services": "2.33.1-feat-cli-tool-lambda-v2.17129+8e46f37a5",
10434
+ "@bike4mind/utils": "2.1.2-feat-cli-tool-lambda-v2.17129+8e46f37a5",
10463
10435
  "@types/better-sqlite3": "^7.6.12",
10464
10436
  "@types/diff": "^5.0.9",
10465
10437
  "@types/jsonwebtoken": "^9.0.4",
@@ -10472,7 +10444,7 @@ var package_default = {
10472
10444
  typescript: "^5.9.3",
10473
10445
  vitest: "^3.2.4"
10474
10446
  },
10475
- gitHead: "8d4de110f7d8f55bff6bd26cb38b8ca1d4dbd996"
10447
+ gitHead: "8e46f37a5b295f66cc4da97b2134509dc23cd237"
10476
10448
  };
10477
10449
 
10478
10450
  // src/config/constants.ts
@@ -10494,7 +10466,7 @@ var usageCache = null;
10494
10466
  function CliApp() {
10495
10467
  const { exit } = useApp();
10496
10468
  const imageRenderer = new ImageRenderer();
10497
- const [state, setState] = useState5({
10469
+ const [state, setState] = useState4({
10498
10470
  session: null,
10499
10471
  sessionStore: new SessionStore(),
10500
10472
  configStore: new ConfigStore(),
@@ -10507,12 +10479,11 @@ function CliApp() {
10507
10479
  mcpManager: null,
10508
10480
  permissionManager: null,
10509
10481
  permissionPrompt: null,
10510
- trustLocationSelector: null,
10511
- rewindSelector: null
10482
+ trustLocationSelector: null
10512
10483
  });
10513
- const [isInitialized, setIsInitialized] = useState5(false);
10514
- const [initError, setInitError] = useState5(null);
10515
- const [commandHistory, setCommandHistory] = useState5([]);
10484
+ const [isInitialized, setIsInitialized] = useState4(false);
10485
+ const [initError, setInitError] = useState4(null);
10486
+ const [commandHistory, setCommandHistory] = useState4([]);
10516
10487
  const imageStoreInitPromise = useRef(null);
10517
10488
  const setStoreSession = useCliStore((state2) => state2.setSession);
10518
10489
  const setPermissionPrompt = useCliStore((state2) => state2.setPermissionPrompt);
@@ -10553,7 +10524,7 @@ function CliApp() {
10553
10524
  process.exit(0);
10554
10525
  }, 100);
10555
10526
  }, [state.session, state.sessionStore, state.mcpManager, state.agent, state.imageStore]);
10556
- useInput4((input, key) => {
10527
+ useInput3((input, key) => {
10557
10528
  if (key.ctrl && input === "c") {
10558
10529
  const now = Date.now();
10559
10530
  if (exitTimestamp && now - exitTimestamp < EXIT_TIMEOUT_MS) {
@@ -10697,10 +10668,6 @@ function CliApp() {
10697
10668
  setPermissionPrompt(prompt);
10698
10669
  });
10699
10670
  };
10700
- console.log("\u{1F511} Tool API Keys:", {
10701
- openweather: config.toolApiKeys?.openweather ? "\u2713 configured" : "\u2717 missing",
10702
- serper: config.toolApiKeys?.serper ? "\u2713 configured" : "\u2717 missing"
10703
- });
10704
10671
  const agentContext = {
10705
10672
  currentAgent: null,
10706
10673
  observationQueue: []
@@ -10713,7 +10680,7 @@ function CliApp() {
10713
10680
  promptFn,
10714
10681
  agentContext,
10715
10682
  state.configStore,
10716
- config.toolApiKeys
10683
+ apiClient
10717
10684
  );
10718
10685
  console.log(`\u{1F6E0}\uFE0F Loaded ${b4mTools.length} B4M tool(s)`);
10719
10686
  const mcpManager = new McpManager(config);
@@ -10907,30 +10874,6 @@ Remember: Use context from previous messages to understand follow-up questions.`
10907
10874
  throw error;
10908
10875
  }
10909
10876
  };
10910
- const recalculateSessionMetadata = (messages) => {
10911
- let totalTokens = 0;
10912
- let totalCost = 0;
10913
- let toolCallCount = 0;
10914
- for (const msg of messages) {
10915
- if (msg.metadata) {
10916
- if (msg.metadata.tokenUsage) {
10917
- totalTokens += msg.metadata.tokenUsage.total || 0;
10918
- }
10919
- if (msg.metadata.cost) {
10920
- totalCost += msg.metadata.cost;
10921
- }
10922
- if (msg.metadata.steps) {
10923
- const observations = msg.metadata.steps.filter((s) => s.type === "observation");
10924
- toolCallCount += observations.length;
10925
- }
10926
- }
10927
- }
10928
- return {
10929
- totalTokens,
10930
- totalCost,
10931
- toolCallCount
10932
- };
10933
- };
10934
10877
  const handleCommand = async (command, args) => {
10935
10878
  switch (command) {
10936
10879
  case "help":
@@ -10939,7 +10882,6 @@ Available commands:
10939
10882
  /help - Show this help message
10940
10883
  /exit - Exit the CLI
10941
10884
  /clear - Start a new session
10942
- /rewind - Rewind conversation to a previous point
10943
10885
  /login - Authenticate with your B4M account
10944
10886
  /logout - Clear authentication and sign out
10945
10887
  /whoami - Show current authenticated user
@@ -11215,65 +11157,6 @@ Project Configuration:
11215
11157
  `);
11216
11158
  break;
11217
11159
  }
11218
- case "rewind":
11219
- case "undo": {
11220
- if (!state.session) {
11221
- console.log("No active session to rewind");
11222
- return;
11223
- }
11224
- if (state.session.messages.length === 0) {
11225
- console.log("\u26A0\uFE0F Conversation is empty. Nothing to rewind.");
11226
- return;
11227
- }
11228
- const userMessages = state.session.messages.filter((msg) => msg.role === "user");
11229
- if (userMessages.length === 0) {
11230
- console.log("\u26A0\uFE0F No user messages found. Nothing to rewind.");
11231
- return;
11232
- }
11233
- if (userMessages.length === 1) {
11234
- console.log("\u26A0\uFE0F Only one exchange in the conversation. Use /clear to start a new session instead.");
11235
- return;
11236
- }
11237
- const handleRewindSelect = async (messageIndex) => {
11238
- setState((prev) => ({ ...prev, rewindSelector: null }));
11239
- if (messageIndex === null) {
11240
- console.log("\u274C Rewind operation cancelled");
11241
- return;
11242
- }
11243
- if (!state.session) {
11244
- console.log("\u274C No active session");
11245
- return;
11246
- }
11247
- const selectedMessage = state.session.messages[messageIndex];
11248
- const prefillContent = selectedMessage?.content || "";
11249
- const rewindedMessages = state.session.messages.slice(0, messageIndex);
11250
- const newMetadata = recalculateSessionMetadata(rewindedMessages);
11251
- const rewindedSession = {
11252
- ...state.session,
11253
- messages: rewindedMessages,
11254
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
11255
- metadata: newMetadata
11256
- };
11257
- setState((prev) => ({
11258
- ...prev,
11259
- session: rewindedSession,
11260
- prefillInput: prefillContent
11261
- }));
11262
- setStoreSession(rewindedSession);
11263
- await state.sessionStore.save(rewindedSession);
11264
- console.log("\u2705 Conversation rewound successfully");
11265
- console.log(`\u{1F4CA} Current state: ${rewindedMessages.length} messages, ${newMetadata.totalTokens} tokens`);
11266
- console.log(`\u{1F4DD} Your message has been placed in the input. Edit and send when ready.
11267
- `);
11268
- };
11269
- setState((prev) => ({
11270
- ...prev,
11271
- rewindSelector: {
11272
- resolve: handleRewindSelect
11273
- }
11274
- }));
11275
- break;
11276
- }
11277
11160
  case "usage": {
11278
11161
  const usageAuthTokens = await state.configStore.getAuthTokens();
11279
11162
  if (!usageAuthTokens || new Date(usageAuthTokens.expiresAt) <= /* @__PURE__ */ new Date()) {
@@ -11427,12 +11310,12 @@ No usage data available for the last ${USAGE_DAYS} days.`);
11427
11310
  setState((prev) => ({ ...prev, config: updatedConfig }));
11428
11311
  };
11429
11312
  if (initError) {
11430
- return /* @__PURE__ */ React14.createElement(Box14, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React14.createElement(Text13, { color: "red", bold: true }, "\u274C Initialization Error"), /* @__PURE__ */ React14.createElement(Text13, null, initError), /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "\n", "Tip: Run /config to set up your API keys"));
11313
+ return /* @__PURE__ */ React13.createElement(Box13, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React13.createElement(Text12, { color: "red", bold: true }, "\u274C Initialization Error"), /* @__PURE__ */ React13.createElement(Text12, null, initError), /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, "\n", "Tip: Run /config to set up your API keys"));
11431
11314
  }
11432
11315
  if (state.trustLocationSelector) {
11433
11316
  const projectDir = state.configStore.getProjectConfigDir();
11434
11317
  const inProject = projectDir !== null;
11435
- return /* @__PURE__ */ React14.createElement(
11318
+ return /* @__PURE__ */ React13.createElement(
11436
11319
  TrustLocationSelector,
11437
11320
  {
11438
11321
  inProject,
@@ -11449,27 +11332,9 @@ No usage data available for the last ${USAGE_DAYS} days.`);
11449
11332
  }
11450
11333
  );
11451
11334
  }
11452
- if (state.rewindSelector && state.session) {
11453
- return /* @__PURE__ */ React14.createElement(
11454
- RewindSelector,
11455
- {
11456
- messages: state.session.messages,
11457
- onSelect: (messageIndex) => {
11458
- if (state.rewindSelector) {
11459
- state.rewindSelector.resolve(messageIndex);
11460
- }
11461
- },
11462
- onCancel: () => {
11463
- if (state.rewindSelector) {
11464
- state.rewindSelector.resolve(null);
11465
- }
11466
- }
11467
- }
11468
- );
11469
- }
11470
11335
  if (state.showLoginFlow) {
11471
11336
  const loginApiUrl = getApiUrl(state.config?.apiConfig);
11472
- return /* @__PURE__ */ React14.createElement(
11337
+ return /* @__PURE__ */ React13.createElement(
11473
11338
  LoginFlow,
11474
11339
  {
11475
11340
  apiUrl: loginApiUrl,
@@ -11492,9 +11357,9 @@ No usage data available for the last ${USAGE_DAYS} days.`);
11492
11357
  );
11493
11358
  }
11494
11359
  if (!isInitialized) {
11495
- return /* @__PURE__ */ React14.createElement(Box14, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React14.createElement(Text13, null, "\u{1F680} Initializing..."));
11360
+ return /* @__PURE__ */ React13.createElement(Box13, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React13.createElement(Text12, null, "\u{1F680} Initializing..."));
11496
11361
  }
11497
- return /* @__PURE__ */ React14.createElement(
11362
+ return /* @__PURE__ */ React13.createElement(
11498
11363
  App,
11499
11364
  {
11500
11365
  onMessage: handleMessage,
@@ -11504,10 +11369,6 @@ No usage data available for the last ${USAGE_DAYS} days.`);
11504
11369
  config: state.config,
11505
11370
  availableModels: state.availableModels,
11506
11371
  onSaveConfig: handleSaveConfig,
11507
- prefillInput: state.prefillInput,
11508
- onPrefillConsumed: () => {
11509
- setState((prev) => ({ ...prev, prefillInput: void 0 }));
11510
- },
11511
11372
  onPermissionResponse: (response) => {
11512
11373
  if (state.permissionPrompt) {
11513
11374
  state.permissionPrompt.resolve({ action: response });
@@ -11533,6 +11394,6 @@ var isDevMode = import.meta.url.includes("/src/") || process.env.NODE_ENV === "d
11533
11394
  if (isDevMode) {
11534
11395
  console.log("\u{1F527} Running in development mode (using TypeScript source)\n");
11535
11396
  }
11536
- render(/* @__PURE__ */ React14.createElement(CliApp, null), {
11397
+ render(/* @__PURE__ */ React13.createElement(CliApp, null), {
11537
11398
  exitOnCtrlC: false
11538
11399
  });