@bike4mind/cli 0.2.7-feat-cli-tool-lambda-v2.17131 → 0.2.7-feat-organization-overview-enhancements.17130
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 +295 -366
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -8713,17 +8713,6 @@ var MementoEvalResponseSchema = z142.object({
|
|
|
8713
8713
|
// ../../b4m-core/packages/services/dist/src/auth/AccessTokenGeneratorService.js
|
|
8714
8714
|
import jwt2 from "jsonwebtoken";
|
|
8715
8715
|
|
|
8716
|
-
// ../../b4m-core/packages/services/dist/src/cliTools/types.js
|
|
8717
|
-
var ToolErrorType;
|
|
8718
|
-
(function(ToolErrorType2) {
|
|
8719
|
-
ToolErrorType2["INVALID_INPUT"] = "INVALID_INPUT";
|
|
8720
|
-
ToolErrorType2["API_KEY_MISSING"] = "API_KEY_MISSING";
|
|
8721
|
-
ToolErrorType2["EXTERNAL_API_ERROR"] = "EXTERNAL_API_ERROR";
|
|
8722
|
-
ToolErrorType2["RATE_LIMIT"] = "RATE_LIMIT";
|
|
8723
|
-
ToolErrorType2["NETWORK_ERROR"] = "NETWORK_ERROR";
|
|
8724
|
-
ToolErrorType2["UNKNOWN"] = "UNKNOWN";
|
|
8725
|
-
})(ToolErrorType || (ToolErrorType = {}));
|
|
8726
|
-
|
|
8727
8716
|
// src/utils/diffPreview.ts
|
|
8728
8717
|
import * as Diff from "diff";
|
|
8729
8718
|
import { readFile } from "fs/promises";
|
|
@@ -8774,338 +8763,6 @@ Last modified: ${stats.mtime.toLocaleString()}`;
|
|
|
8774
8763
|
}
|
|
8775
8764
|
}
|
|
8776
8765
|
|
|
8777
|
-
// src/utils/Logger.ts
|
|
8778
|
-
import fs10 from "fs/promises";
|
|
8779
|
-
import path11 from "path";
|
|
8780
|
-
import os from "os";
|
|
8781
|
-
var Logger2 = class _Logger {
|
|
8782
|
-
constructor() {
|
|
8783
|
-
this.logFilePath = null;
|
|
8784
|
-
this.sessionId = null;
|
|
8785
|
-
this.fileLoggingEnabled = true;
|
|
8786
|
-
this.consoleVerbose = false;
|
|
8787
|
-
}
|
|
8788
|
-
static {
|
|
8789
|
-
this.instance = null;
|
|
8790
|
-
}
|
|
8791
|
-
static getInstance() {
|
|
8792
|
-
if (!_Logger.instance) {
|
|
8793
|
-
_Logger.instance = new _Logger();
|
|
8794
|
-
}
|
|
8795
|
-
return _Logger.instance;
|
|
8796
|
-
}
|
|
8797
|
-
/**
|
|
8798
|
-
* Initialize the logger with a session ID
|
|
8799
|
-
*/
|
|
8800
|
-
async initialize(sessionId) {
|
|
8801
|
-
this.sessionId = sessionId;
|
|
8802
|
-
const debugDir = path11.join(os.homedir(), ".bike4mind", "debug");
|
|
8803
|
-
await fs10.mkdir(debugDir, { recursive: true });
|
|
8804
|
-
this.logFilePath = path11.join(debugDir, `${sessionId}.txt`);
|
|
8805
|
-
await this.writeToFile("INFO", "=== CLI SESSION START ===");
|
|
8806
|
-
}
|
|
8807
|
-
/**
|
|
8808
|
-
* Set whether verbose console logging is enabled
|
|
8809
|
-
*/
|
|
8810
|
-
setVerbose(enabled) {
|
|
8811
|
-
this.consoleVerbose = enabled;
|
|
8812
|
-
}
|
|
8813
|
-
/**
|
|
8814
|
-
* Set whether file logging is enabled
|
|
8815
|
-
*/
|
|
8816
|
-
setFileLoggingEnabled(enabled) {
|
|
8817
|
-
this.fileLoggingEnabled = enabled;
|
|
8818
|
-
}
|
|
8819
|
-
/**
|
|
8820
|
-
* DEBUG level - verbose-only console, always file
|
|
8821
|
-
*/
|
|
8822
|
-
debug(message) {
|
|
8823
|
-
this.writeToFile("DEBUG", message).catch(() => {
|
|
8824
|
-
});
|
|
8825
|
-
if (this.consoleVerbose) {
|
|
8826
|
-
console.log(message);
|
|
8827
|
-
}
|
|
8828
|
-
}
|
|
8829
|
-
/**
|
|
8830
|
-
* INFO level - always shown to user
|
|
8831
|
-
*/
|
|
8832
|
-
info(message) {
|
|
8833
|
-
this.writeToFile("INFO", message).catch(() => {
|
|
8834
|
-
});
|
|
8835
|
-
console.log(message);
|
|
8836
|
-
}
|
|
8837
|
-
/**
|
|
8838
|
-
* WARN level - always shown to user
|
|
8839
|
-
*/
|
|
8840
|
-
warn(message) {
|
|
8841
|
-
this.writeToFile("WARN", message).catch(() => {
|
|
8842
|
-
});
|
|
8843
|
-
console.warn(message);
|
|
8844
|
-
}
|
|
8845
|
-
/**
|
|
8846
|
-
* ERROR level - always shown to user
|
|
8847
|
-
*/
|
|
8848
|
-
error(message, err) {
|
|
8849
|
-
this.writeToFile("ERROR", message).catch(() => {
|
|
8850
|
-
});
|
|
8851
|
-
if (err) {
|
|
8852
|
-
this.logErrorDetailsToFile(err).catch(() => {
|
|
8853
|
-
});
|
|
8854
|
-
}
|
|
8855
|
-
console.error(message);
|
|
8856
|
-
}
|
|
8857
|
-
/**
|
|
8858
|
-
* Write log entry to file
|
|
8859
|
-
*/
|
|
8860
|
-
async writeToFile(level, message) {
|
|
8861
|
-
if (!this.fileLoggingEnabled || !this.logFilePath) {
|
|
8862
|
-
return;
|
|
8863
|
-
}
|
|
8864
|
-
try {
|
|
8865
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19);
|
|
8866
|
-
const logEntry = `[${timestamp}] [${level}] ${message}
|
|
8867
|
-
`;
|
|
8868
|
-
await fs10.appendFile(this.logFilePath, logEntry, "utf-8");
|
|
8869
|
-
} catch (error) {
|
|
8870
|
-
console.error("File logging failed:", error);
|
|
8871
|
-
}
|
|
8872
|
-
}
|
|
8873
|
-
/**
|
|
8874
|
-
* Log error details to file
|
|
8875
|
-
*/
|
|
8876
|
-
async logErrorDetailsToFile(err) {
|
|
8877
|
-
if (!this.fileLoggingEnabled || !this.logFilePath) {
|
|
8878
|
-
return;
|
|
8879
|
-
}
|
|
8880
|
-
try {
|
|
8881
|
-
if (err && typeof err === "object" && "response" in err && err.response) {
|
|
8882
|
-
const response = err.response;
|
|
8883
|
-
const config = err && typeof err === "object" && "config" in err ? err.config : void 0;
|
|
8884
|
-
await this.writeToFile("ERROR", ` Status: ${response.status} ${response.statusText || ""}`);
|
|
8885
|
-
await this.writeToFile("ERROR", ` URL: ${config?.url || "unknown"}`);
|
|
8886
|
-
await this.writeToFile("ERROR", ` Headers: ${this.safeStringify(response.headers)}`);
|
|
8887
|
-
if (response.data) {
|
|
8888
|
-
const errorText = this.extractErrorMessage(response.data);
|
|
8889
|
-
if (errorText.trim().startsWith("<!DOCTYPE") || errorText.trim().startsWith("<html")) {
|
|
8890
|
-
await this.writeToFile("ERROR", ` Response Type: HTML Error Page`);
|
|
8891
|
-
const parsedError = this.parseHtmlError(errorText);
|
|
8892
|
-
if (parsedError) {
|
|
8893
|
-
await this.writeToFile("ERROR", ` Error Message: ${parsedError}`);
|
|
8894
|
-
}
|
|
8895
|
-
await this.writeToFile("ERROR", ` Raw HTML: ${this.truncate(errorText, 1e3)}`);
|
|
8896
|
-
} else {
|
|
8897
|
-
const preview = this.truncate(errorText, 500);
|
|
8898
|
-
await this.writeToFile("ERROR", ` Response: ${preview}`);
|
|
8899
|
-
}
|
|
8900
|
-
} else {
|
|
8901
|
-
await this.writeToFile("ERROR", ` Response: (no data)`);
|
|
8902
|
-
}
|
|
8903
|
-
}
|
|
8904
|
-
if (err && typeof err === "object" && "stack" in err && typeof err.stack === "string") {
|
|
8905
|
-
const stackLines = err.stack.split("\n").slice(0, 5).join("\n ");
|
|
8906
|
-
await this.writeToFile("ERROR", ` Stack: ${stackLines}`);
|
|
8907
|
-
} else if (err && typeof err === "object" && "message" in err && typeof err.message === "string") {
|
|
8908
|
-
await this.writeToFile("ERROR", ` Message: ${err.message}`);
|
|
8909
|
-
}
|
|
8910
|
-
} catch (error) {
|
|
8911
|
-
}
|
|
8912
|
-
}
|
|
8913
|
-
/**
|
|
8914
|
-
* Safely stringify object, handling circular references
|
|
8915
|
-
*/
|
|
8916
|
-
safeStringify(obj) {
|
|
8917
|
-
try {
|
|
8918
|
-
return JSON.stringify(obj);
|
|
8919
|
-
} catch (error) {
|
|
8920
|
-
if (error instanceof Error && error.message.includes("circular")) {
|
|
8921
|
-
try {
|
|
8922
|
-
const seen = /* @__PURE__ */ new WeakSet();
|
|
8923
|
-
return JSON.stringify(obj, (key, value) => {
|
|
8924
|
-
if (typeof value === "object" && value !== null) {
|
|
8925
|
-
if (seen.has(value)) {
|
|
8926
|
-
return "[Circular]";
|
|
8927
|
-
}
|
|
8928
|
-
seen.add(value);
|
|
8929
|
-
}
|
|
8930
|
-
return value;
|
|
8931
|
-
});
|
|
8932
|
-
} catch {
|
|
8933
|
-
return "[Unable to stringify]";
|
|
8934
|
-
}
|
|
8935
|
-
}
|
|
8936
|
-
return "[Stringify error]";
|
|
8937
|
-
}
|
|
8938
|
-
}
|
|
8939
|
-
/**
|
|
8940
|
-
* Extract readable text from error response data
|
|
8941
|
-
*/
|
|
8942
|
-
extractErrorMessage(data) {
|
|
8943
|
-
if (Buffer.isBuffer(data)) {
|
|
8944
|
-
return data.toString("utf-8");
|
|
8945
|
-
}
|
|
8946
|
-
if (typeof data === "string") {
|
|
8947
|
-
return data;
|
|
8948
|
-
}
|
|
8949
|
-
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) {
|
|
8950
|
-
const chunks = [];
|
|
8951
|
-
for (const chunk of data._readableState.buffer) {
|
|
8952
|
-
if (chunk && typeof chunk === "object" && "type" in chunk && chunk.type === "Buffer" && "data" in chunk && Array.isArray(chunk.data)) {
|
|
8953
|
-
chunks.push(Buffer.from(chunk.data));
|
|
8954
|
-
} else if (Buffer.isBuffer(chunk)) {
|
|
8955
|
-
chunks.push(chunk);
|
|
8956
|
-
} else if (chunk && typeof chunk === "object" && "data" in chunk) {
|
|
8957
|
-
if (Buffer.isBuffer(chunk.data)) {
|
|
8958
|
-
chunks.push(chunk.data);
|
|
8959
|
-
} else if (Array.isArray(chunk.data)) {
|
|
8960
|
-
chunks.push(Buffer.from(chunk.data));
|
|
8961
|
-
}
|
|
8962
|
-
}
|
|
8963
|
-
}
|
|
8964
|
-
if (chunks.length > 0) {
|
|
8965
|
-
return Buffer.concat(chunks).toString("utf-8");
|
|
8966
|
-
}
|
|
8967
|
-
}
|
|
8968
|
-
return this.safeStringify(data);
|
|
8969
|
-
}
|
|
8970
|
-
/**
|
|
8971
|
-
* Parse HTML error page to extract error message
|
|
8972
|
-
*/
|
|
8973
|
-
parseHtmlError(html) {
|
|
8974
|
-
const titleMatch = html.match(/<title>(.*?)<\/title>/i);
|
|
8975
|
-
const h1Match = html.match(/<h1>(.*?)<\/h1>/i);
|
|
8976
|
-
const bodyMatch = html.match(/<body[^>]*>(.*?)<\/body>/is);
|
|
8977
|
-
if (titleMatch && titleMatch[1] !== "Error") {
|
|
8978
|
-
return titleMatch[1].trim();
|
|
8979
|
-
}
|
|
8980
|
-
if (h1Match) {
|
|
8981
|
-
return h1Match[1].trim();
|
|
8982
|
-
}
|
|
8983
|
-
if (bodyMatch) {
|
|
8984
|
-
const text = bodyMatch[1].replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
8985
|
-
return text.substring(0, 200);
|
|
8986
|
-
}
|
|
8987
|
-
return null;
|
|
8988
|
-
}
|
|
8989
|
-
/**
|
|
8990
|
-
* Truncate string to max length with ellipsis
|
|
8991
|
-
*/
|
|
8992
|
-
truncate(str, maxLength) {
|
|
8993
|
-
if (str.length <= maxLength) {
|
|
8994
|
-
return str;
|
|
8995
|
-
}
|
|
8996
|
-
return str.substring(0, maxLength) + "... [truncated]";
|
|
8997
|
-
}
|
|
8998
|
-
/**
|
|
8999
|
-
* Format bytes to human-readable size
|
|
9000
|
-
*/
|
|
9001
|
-
formatBytes(bytes) {
|
|
9002
|
-
if (bytes === 0) return "0 B";
|
|
9003
|
-
const k = 1024;
|
|
9004
|
-
const sizes = ["B", "KB", "MB", "GB"];
|
|
9005
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
9006
|
-
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + " " + sizes[i];
|
|
9007
|
-
}
|
|
9008
|
-
/**
|
|
9009
|
-
* Clean up old debug logs (older than 30 days)
|
|
9010
|
-
*/
|
|
9011
|
-
async cleanupOldLogs() {
|
|
9012
|
-
if (!this.fileLoggingEnabled) return;
|
|
9013
|
-
try {
|
|
9014
|
-
const debugDir = path11.join(os.homedir(), ".bike4mind", "debug");
|
|
9015
|
-
const files = await fs10.readdir(debugDir);
|
|
9016
|
-
const now = Date.now();
|
|
9017
|
-
const thirtyDaysAgo = now - 30 * 24 * 60 * 60 * 1e3;
|
|
9018
|
-
for (const file of files) {
|
|
9019
|
-
const filePath = path11.join(debugDir, file);
|
|
9020
|
-
const stats = await fs10.stat(filePath);
|
|
9021
|
-
if (stats.mtime.getTime() < thirtyDaysAgo) {
|
|
9022
|
-
await fs10.unlink(filePath);
|
|
9023
|
-
}
|
|
9024
|
-
}
|
|
9025
|
-
} catch (error) {
|
|
9026
|
-
console.error("Failed to cleanup old logs:", error);
|
|
9027
|
-
}
|
|
9028
|
-
}
|
|
9029
|
-
};
|
|
9030
|
-
var logger = Logger2.getInstance();
|
|
9031
|
-
|
|
9032
|
-
// src/llm/ServerToolExecutor.ts
|
|
9033
|
-
var ServerToolExecutor = class {
|
|
9034
|
-
constructor(apiClient) {
|
|
9035
|
-
this.toolsEndpoint = "/api/ai/v1/tools";
|
|
9036
|
-
this.apiClient = apiClient;
|
|
9037
|
-
}
|
|
9038
|
-
/**
|
|
9039
|
-
* Execute a tool server-side
|
|
9040
|
-
*
|
|
9041
|
-
* @param toolName - Name of the tool to execute (e.g., 'weather_info', 'web_search')
|
|
9042
|
-
* @param input - Tool input parameters
|
|
9043
|
-
* @returns Tool execution result (content or error)
|
|
9044
|
-
*/
|
|
9045
|
-
async executeTool(toolName, input) {
|
|
9046
|
-
logger.debug(`[ServerToolExecutor] Executing tool: ${toolName}`);
|
|
9047
|
-
logger.debug(`[ServerToolExecutor] Endpoint: ${this.toolsEndpoint}`);
|
|
9048
|
-
logger.debug(`[ServerToolExecutor] Input: ${JSON.stringify(input)}`);
|
|
9049
|
-
try {
|
|
9050
|
-
const result = await this.apiClient.post(this.toolsEndpoint, {
|
|
9051
|
-
toolName,
|
|
9052
|
-
input
|
|
9053
|
-
});
|
|
9054
|
-
logger.debug(`[ServerToolExecutor] Response: ${JSON.stringify(result)}`);
|
|
9055
|
-
if (!result.success) {
|
|
9056
|
-
const errorMessage = result.error || "Tool execution failed";
|
|
9057
|
-
logger.error(`[ServerToolExecutor] Tool ${toolName} failed: ${errorMessage}`);
|
|
9058
|
-
return `Error executing ${toolName}: ${errorMessage}`;
|
|
9059
|
-
}
|
|
9060
|
-
logger.debug(`[ServerToolExecutor] Tool ${toolName} succeeded in ${result.executionTimeMs}ms`);
|
|
9061
|
-
return result.content || "";
|
|
9062
|
-
} catch (error) {
|
|
9063
|
-
const err = error;
|
|
9064
|
-
const errorMessage = err?.response?.data?.error || err?.message || "Unknown error";
|
|
9065
|
-
const status = err?.response?.status || "unknown";
|
|
9066
|
-
logger.error(`[ServerToolExecutor] Request failed for ${toolName}: ${errorMessage} (status: ${status})`);
|
|
9067
|
-
logger.error(`[ServerToolExecutor] Full error: ${JSON.stringify(err, null, 2)}`);
|
|
9068
|
-
return `Error executing ${toolName}: ${errorMessage}`;
|
|
9069
|
-
}
|
|
9070
|
-
}
|
|
9071
|
-
};
|
|
9072
|
-
|
|
9073
|
-
// src/llm/ToolRouter.ts
|
|
9074
|
-
var SERVER_TOOLS = ["weather_info", "web_search"];
|
|
9075
|
-
var LOCAL_TOOLS = [
|
|
9076
|
-
"file_read",
|
|
9077
|
-
"create_file",
|
|
9078
|
-
"glob_files",
|
|
9079
|
-
"grep_search",
|
|
9080
|
-
"delete_file",
|
|
9081
|
-
"edit_file",
|
|
9082
|
-
"dice_roll",
|
|
9083
|
-
"math_evaluate",
|
|
9084
|
-
"current_datetime"
|
|
9085
|
-
];
|
|
9086
|
-
function isServerTool(toolName) {
|
|
9087
|
-
return SERVER_TOOLS.includes(toolName);
|
|
9088
|
-
}
|
|
9089
|
-
function isLocalTool(toolName) {
|
|
9090
|
-
return LOCAL_TOOLS.includes(toolName);
|
|
9091
|
-
}
|
|
9092
|
-
async function executeTool(toolName, input, apiClient, localToolFn) {
|
|
9093
|
-
if (isServerTool(toolName)) {
|
|
9094
|
-
logger.debug(`[ToolRouter] Routing ${toolName} to server`);
|
|
9095
|
-
const executor = new ServerToolExecutor(apiClient);
|
|
9096
|
-
return await executor.executeTool(toolName, input);
|
|
9097
|
-
} else if (isLocalTool(toolName)) {
|
|
9098
|
-
logger.debug(`[ToolRouter] Executing ${toolName} locally`);
|
|
9099
|
-
if (!localToolFn) {
|
|
9100
|
-
throw new Error(`Local tool ${toolName} has no implementation`);
|
|
9101
|
-
}
|
|
9102
|
-
const result = await localToolFn(input);
|
|
9103
|
-
return typeof result === "string" ? result : JSON.stringify(result);
|
|
9104
|
-
} else {
|
|
9105
|
-
throw new Error(`Unknown tool: ${toolName}`);
|
|
9106
|
-
}
|
|
9107
|
-
}
|
|
9108
|
-
|
|
9109
8766
|
// src/utils/toolsAdapter.ts
|
|
9110
8767
|
var NoOpStorage = class extends BaseStorage {
|
|
9111
8768
|
async upload(input, destination, options) {
|
|
@@ -9145,14 +8802,14 @@ var CliLogger = class extends Logger {
|
|
|
9145
8802
|
log(...args) {
|
|
9146
8803
|
}
|
|
9147
8804
|
};
|
|
9148
|
-
function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore
|
|
8805
|
+
function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore) {
|
|
9149
8806
|
const originalFn = tool.toolFn;
|
|
9150
8807
|
const toolName = tool.toolSchema.name;
|
|
9151
8808
|
return {
|
|
9152
8809
|
...tool,
|
|
9153
8810
|
toolFn: async (args) => {
|
|
9154
8811
|
if (!permissionManager.needsPermission(toolName)) {
|
|
9155
|
-
const result2 = await
|
|
8812
|
+
const result2 = await originalFn(args);
|
|
9156
8813
|
agentContext.observationQueue.push({ toolName, result: result2 });
|
|
9157
8814
|
return result2;
|
|
9158
8815
|
}
|
|
@@ -9209,13 +8866,13 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
|
|
|
9209
8866
|
}
|
|
9210
8867
|
}
|
|
9211
8868
|
}
|
|
9212
|
-
const result = await
|
|
8869
|
+
const result = await originalFn(args);
|
|
9213
8870
|
agentContext.observationQueue.push({ toolName, result });
|
|
9214
8871
|
return result;
|
|
9215
8872
|
}
|
|
9216
8873
|
};
|
|
9217
8874
|
}
|
|
9218
|
-
function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore,
|
|
8875
|
+
function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, toolApiKeys) {
|
|
9219
8876
|
const logger2 = new CliLogger();
|
|
9220
8877
|
const storage = new NoOpStorage();
|
|
9221
8878
|
const user = {
|
|
@@ -9232,6 +8889,18 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
|
|
|
9232
8889
|
};
|
|
9233
8890
|
const mockAdminSettings = {
|
|
9234
8891
|
findBySettingName: async (settingName) => {
|
|
8892
|
+
if (settingName === "OpenWeatherKey") {
|
|
8893
|
+
const key = toolApiKeys?.openweather || process.env.OPENWEATHER_API_KEY;
|
|
8894
|
+
if (key) {
|
|
8895
|
+
return { settingValue: key };
|
|
8896
|
+
}
|
|
8897
|
+
}
|
|
8898
|
+
if (settingName === "SerperKey") {
|
|
8899
|
+
const key = toolApiKeys?.serper || process.env.SERPER_API_KEY;
|
|
8900
|
+
if (key) {
|
|
8901
|
+
return { settingValue: key };
|
|
8902
|
+
}
|
|
8903
|
+
}
|
|
9235
8904
|
return null;
|
|
9236
8905
|
}
|
|
9237
8906
|
};
|
|
@@ -9241,22 +8910,27 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
|
|
|
9241
8910
|
adminSettings: mockAdminSettings
|
|
9242
8911
|
}
|
|
9243
8912
|
};
|
|
8913
|
+
const hasSerperKey = !!(toolApiKeys?.serper || process.env.SERPER_API_KEY);
|
|
8914
|
+
const hasOpenWeatherKey = !!(toolApiKeys?.openweather || process.env.OPENWEATHER_API_KEY);
|
|
9244
8915
|
const toolConfig = {
|
|
9245
|
-
//
|
|
8916
|
+
// Enable these tools for CLI (no external API keys needed)
|
|
9246
8917
|
dice_roll: {},
|
|
9247
8918
|
math_evaluate: {},
|
|
9248
8919
|
current_datetime: {},
|
|
9249
8920
|
prompt_enhancement: {},
|
|
9250
|
-
// File operation tools (CLI-specific
|
|
8921
|
+
// File operation tools (CLI-specific)
|
|
9251
8922
|
file_read: {},
|
|
9252
8923
|
create_file: {},
|
|
9253
8924
|
glob_files: {},
|
|
9254
8925
|
grep_search: {},
|
|
9255
8926
|
delete_file: {},
|
|
9256
|
-
//
|
|
9257
|
-
|
|
9258
|
-
|
|
9259
|
-
|
|
8927
|
+
// Tools requiring API keys (only include if API key is available)
|
|
8928
|
+
...hasSerperKey && { web_search: {} },
|
|
8929
|
+
// Requires SERPER_API_KEY
|
|
8930
|
+
...hasOpenWeatherKey && { weather_info: {} },
|
|
8931
|
+
// Requires OPENWEATHER_API_KEY
|
|
8932
|
+
...hasSerperKey && { deep_research: {} }
|
|
8933
|
+
// Requires SERPER_API_KEY
|
|
9260
8934
|
// Disable web-only tools
|
|
9261
8935
|
// blog_publish: undefined,
|
|
9262
8936
|
// blog_edit: undefined,
|
|
@@ -9266,7 +8940,6 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
|
|
|
9266
8940
|
// mermaid_chart: undefined,
|
|
9267
8941
|
// recharts: undefined,
|
|
9268
8942
|
// edit_file: undefined,
|
|
9269
|
-
// deep_research: undefined,
|
|
9270
8943
|
};
|
|
9271
8944
|
const toolsMap = generateTools(
|
|
9272
8945
|
userId,
|
|
@@ -9284,7 +8957,7 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
|
|
|
9284
8957
|
model
|
|
9285
8958
|
);
|
|
9286
8959
|
const tools2 = Object.entries(toolsMap).filter(([key]) => toolConfig[key] !== void 0).map(
|
|
9287
|
-
([_, tool]) => wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore
|
|
8960
|
+
([_, tool]) => wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore)
|
|
9288
8961
|
);
|
|
9289
8962
|
return { tools: tools2, agentContext };
|
|
9290
8963
|
}
|
|
@@ -9479,6 +9152,261 @@ function getEnvironmentName(configApiConfig) {
|
|
|
9479
9152
|
return "Bike4Mind";
|
|
9480
9153
|
}
|
|
9481
9154
|
|
|
9155
|
+
// src/utils/Logger.ts
|
|
9156
|
+
import fs10 from "fs/promises";
|
|
9157
|
+
import path11 from "path";
|
|
9158
|
+
import os from "os";
|
|
9159
|
+
var Logger2 = class _Logger {
|
|
9160
|
+
constructor() {
|
|
9161
|
+
this.logFilePath = null;
|
|
9162
|
+
this.sessionId = null;
|
|
9163
|
+
this.fileLoggingEnabled = true;
|
|
9164
|
+
this.consoleVerbose = false;
|
|
9165
|
+
}
|
|
9166
|
+
static {
|
|
9167
|
+
this.instance = null;
|
|
9168
|
+
}
|
|
9169
|
+
static getInstance() {
|
|
9170
|
+
if (!_Logger.instance) {
|
|
9171
|
+
_Logger.instance = new _Logger();
|
|
9172
|
+
}
|
|
9173
|
+
return _Logger.instance;
|
|
9174
|
+
}
|
|
9175
|
+
/**
|
|
9176
|
+
* Initialize the logger with a session ID
|
|
9177
|
+
*/
|
|
9178
|
+
async initialize(sessionId) {
|
|
9179
|
+
this.sessionId = sessionId;
|
|
9180
|
+
const debugDir = path11.join(os.homedir(), ".bike4mind", "debug");
|
|
9181
|
+
await fs10.mkdir(debugDir, { recursive: true });
|
|
9182
|
+
this.logFilePath = path11.join(debugDir, `${sessionId}.txt`);
|
|
9183
|
+
await this.writeToFile("INFO", "=== CLI SESSION START ===");
|
|
9184
|
+
}
|
|
9185
|
+
/**
|
|
9186
|
+
* Set whether verbose console logging is enabled
|
|
9187
|
+
*/
|
|
9188
|
+
setVerbose(enabled) {
|
|
9189
|
+
this.consoleVerbose = enabled;
|
|
9190
|
+
}
|
|
9191
|
+
/**
|
|
9192
|
+
* Set whether file logging is enabled
|
|
9193
|
+
*/
|
|
9194
|
+
setFileLoggingEnabled(enabled) {
|
|
9195
|
+
this.fileLoggingEnabled = enabled;
|
|
9196
|
+
}
|
|
9197
|
+
/**
|
|
9198
|
+
* DEBUG level - verbose-only console, always file
|
|
9199
|
+
*/
|
|
9200
|
+
debug(message) {
|
|
9201
|
+
this.writeToFile("DEBUG", message).catch(() => {
|
|
9202
|
+
});
|
|
9203
|
+
if (this.consoleVerbose) {
|
|
9204
|
+
console.log(message);
|
|
9205
|
+
}
|
|
9206
|
+
}
|
|
9207
|
+
/**
|
|
9208
|
+
* INFO level - always shown to user
|
|
9209
|
+
*/
|
|
9210
|
+
info(message) {
|
|
9211
|
+
this.writeToFile("INFO", message).catch(() => {
|
|
9212
|
+
});
|
|
9213
|
+
console.log(message);
|
|
9214
|
+
}
|
|
9215
|
+
/**
|
|
9216
|
+
* WARN level - always shown to user
|
|
9217
|
+
*/
|
|
9218
|
+
warn(message) {
|
|
9219
|
+
this.writeToFile("WARN", message).catch(() => {
|
|
9220
|
+
});
|
|
9221
|
+
console.warn(message);
|
|
9222
|
+
}
|
|
9223
|
+
/**
|
|
9224
|
+
* ERROR level - always shown to user
|
|
9225
|
+
*/
|
|
9226
|
+
error(message, err) {
|
|
9227
|
+
this.writeToFile("ERROR", message).catch(() => {
|
|
9228
|
+
});
|
|
9229
|
+
if (err) {
|
|
9230
|
+
this.logErrorDetailsToFile(err).catch(() => {
|
|
9231
|
+
});
|
|
9232
|
+
}
|
|
9233
|
+
console.error(message);
|
|
9234
|
+
}
|
|
9235
|
+
/**
|
|
9236
|
+
* Write log entry to file
|
|
9237
|
+
*/
|
|
9238
|
+
async writeToFile(level, message) {
|
|
9239
|
+
if (!this.fileLoggingEnabled || !this.logFilePath) {
|
|
9240
|
+
return;
|
|
9241
|
+
}
|
|
9242
|
+
try {
|
|
9243
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19);
|
|
9244
|
+
const logEntry = `[${timestamp}] [${level}] ${message}
|
|
9245
|
+
`;
|
|
9246
|
+
await fs10.appendFile(this.logFilePath, logEntry, "utf-8");
|
|
9247
|
+
} catch (error) {
|
|
9248
|
+
console.error("File logging failed:", error);
|
|
9249
|
+
}
|
|
9250
|
+
}
|
|
9251
|
+
/**
|
|
9252
|
+
* Log error details to file
|
|
9253
|
+
*/
|
|
9254
|
+
async logErrorDetailsToFile(err) {
|
|
9255
|
+
if (!this.fileLoggingEnabled || !this.logFilePath) {
|
|
9256
|
+
return;
|
|
9257
|
+
}
|
|
9258
|
+
try {
|
|
9259
|
+
if (err && typeof err === "object" && "response" in err && err.response) {
|
|
9260
|
+
const response = err.response;
|
|
9261
|
+
const config = err && typeof err === "object" && "config" in err ? err.config : void 0;
|
|
9262
|
+
await this.writeToFile("ERROR", ` Status: ${response.status} ${response.statusText || ""}`);
|
|
9263
|
+
await this.writeToFile("ERROR", ` URL: ${config?.url || "unknown"}`);
|
|
9264
|
+
await this.writeToFile("ERROR", ` Headers: ${this.safeStringify(response.headers)}`);
|
|
9265
|
+
if (response.data) {
|
|
9266
|
+
const errorText = this.extractErrorMessage(response.data);
|
|
9267
|
+
if (errorText.trim().startsWith("<!DOCTYPE") || errorText.trim().startsWith("<html")) {
|
|
9268
|
+
await this.writeToFile("ERROR", ` Response Type: HTML Error Page`);
|
|
9269
|
+
const parsedError = this.parseHtmlError(errorText);
|
|
9270
|
+
if (parsedError) {
|
|
9271
|
+
await this.writeToFile("ERROR", ` Error Message: ${parsedError}`);
|
|
9272
|
+
}
|
|
9273
|
+
await this.writeToFile("ERROR", ` Raw HTML: ${this.truncate(errorText, 1e3)}`);
|
|
9274
|
+
} else {
|
|
9275
|
+
const preview = this.truncate(errorText, 500);
|
|
9276
|
+
await this.writeToFile("ERROR", ` Response: ${preview}`);
|
|
9277
|
+
}
|
|
9278
|
+
} else {
|
|
9279
|
+
await this.writeToFile("ERROR", ` Response: (no data)`);
|
|
9280
|
+
}
|
|
9281
|
+
}
|
|
9282
|
+
if (err && typeof err === "object" && "stack" in err && typeof err.stack === "string") {
|
|
9283
|
+
const stackLines = err.stack.split("\n").slice(0, 5).join("\n ");
|
|
9284
|
+
await this.writeToFile("ERROR", ` Stack: ${stackLines}`);
|
|
9285
|
+
} else if (err && typeof err === "object" && "message" in err && typeof err.message === "string") {
|
|
9286
|
+
await this.writeToFile("ERROR", ` Message: ${err.message}`);
|
|
9287
|
+
}
|
|
9288
|
+
} catch (error) {
|
|
9289
|
+
}
|
|
9290
|
+
}
|
|
9291
|
+
/**
|
|
9292
|
+
* Safely stringify object, handling circular references
|
|
9293
|
+
*/
|
|
9294
|
+
safeStringify(obj) {
|
|
9295
|
+
try {
|
|
9296
|
+
return JSON.stringify(obj);
|
|
9297
|
+
} catch (error) {
|
|
9298
|
+
if (error instanceof Error && error.message.includes("circular")) {
|
|
9299
|
+
try {
|
|
9300
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
9301
|
+
return JSON.stringify(obj, (key, value) => {
|
|
9302
|
+
if (typeof value === "object" && value !== null) {
|
|
9303
|
+
if (seen.has(value)) {
|
|
9304
|
+
return "[Circular]";
|
|
9305
|
+
}
|
|
9306
|
+
seen.add(value);
|
|
9307
|
+
}
|
|
9308
|
+
return value;
|
|
9309
|
+
});
|
|
9310
|
+
} catch {
|
|
9311
|
+
return "[Unable to stringify]";
|
|
9312
|
+
}
|
|
9313
|
+
}
|
|
9314
|
+
return "[Stringify error]";
|
|
9315
|
+
}
|
|
9316
|
+
}
|
|
9317
|
+
/**
|
|
9318
|
+
* Extract readable text from error response data
|
|
9319
|
+
*/
|
|
9320
|
+
extractErrorMessage(data) {
|
|
9321
|
+
if (Buffer.isBuffer(data)) {
|
|
9322
|
+
return data.toString("utf-8");
|
|
9323
|
+
}
|
|
9324
|
+
if (typeof data === "string") {
|
|
9325
|
+
return data;
|
|
9326
|
+
}
|
|
9327
|
+
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) {
|
|
9328
|
+
const chunks = [];
|
|
9329
|
+
for (const chunk of data._readableState.buffer) {
|
|
9330
|
+
if (chunk && typeof chunk === "object" && "type" in chunk && chunk.type === "Buffer" && "data" in chunk && Array.isArray(chunk.data)) {
|
|
9331
|
+
chunks.push(Buffer.from(chunk.data));
|
|
9332
|
+
} else if (Buffer.isBuffer(chunk)) {
|
|
9333
|
+
chunks.push(chunk);
|
|
9334
|
+
} else if (chunk && typeof chunk === "object" && "data" in chunk) {
|
|
9335
|
+
if (Buffer.isBuffer(chunk.data)) {
|
|
9336
|
+
chunks.push(chunk.data);
|
|
9337
|
+
} else if (Array.isArray(chunk.data)) {
|
|
9338
|
+
chunks.push(Buffer.from(chunk.data));
|
|
9339
|
+
}
|
|
9340
|
+
}
|
|
9341
|
+
}
|
|
9342
|
+
if (chunks.length > 0) {
|
|
9343
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
9344
|
+
}
|
|
9345
|
+
}
|
|
9346
|
+
return this.safeStringify(data);
|
|
9347
|
+
}
|
|
9348
|
+
/**
|
|
9349
|
+
* Parse HTML error page to extract error message
|
|
9350
|
+
*/
|
|
9351
|
+
parseHtmlError(html) {
|
|
9352
|
+
const titleMatch = html.match(/<title>(.*?)<\/title>/i);
|
|
9353
|
+
const h1Match = html.match(/<h1>(.*?)<\/h1>/i);
|
|
9354
|
+
const bodyMatch = html.match(/<body[^>]*>(.*?)<\/body>/is);
|
|
9355
|
+
if (titleMatch && titleMatch[1] !== "Error") {
|
|
9356
|
+
return titleMatch[1].trim();
|
|
9357
|
+
}
|
|
9358
|
+
if (h1Match) {
|
|
9359
|
+
return h1Match[1].trim();
|
|
9360
|
+
}
|
|
9361
|
+
if (bodyMatch) {
|
|
9362
|
+
const text = bodyMatch[1].replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
9363
|
+
return text.substring(0, 200);
|
|
9364
|
+
}
|
|
9365
|
+
return null;
|
|
9366
|
+
}
|
|
9367
|
+
/**
|
|
9368
|
+
* Truncate string to max length with ellipsis
|
|
9369
|
+
*/
|
|
9370
|
+
truncate(str, maxLength) {
|
|
9371
|
+
if (str.length <= maxLength) {
|
|
9372
|
+
return str;
|
|
9373
|
+
}
|
|
9374
|
+
return str.substring(0, maxLength) + "... [truncated]";
|
|
9375
|
+
}
|
|
9376
|
+
/**
|
|
9377
|
+
* Format bytes to human-readable size
|
|
9378
|
+
*/
|
|
9379
|
+
formatBytes(bytes) {
|
|
9380
|
+
if (bytes === 0) return "0 B";
|
|
9381
|
+
const k = 1024;
|
|
9382
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
9383
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
9384
|
+
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + " " + sizes[i];
|
|
9385
|
+
}
|
|
9386
|
+
/**
|
|
9387
|
+
* Clean up old debug logs (older than 30 days)
|
|
9388
|
+
*/
|
|
9389
|
+
async cleanupOldLogs() {
|
|
9390
|
+
if (!this.fileLoggingEnabled) return;
|
|
9391
|
+
try {
|
|
9392
|
+
const debugDir = path11.join(os.homedir(), ".bike4mind", "debug");
|
|
9393
|
+
const files = await fs10.readdir(debugDir);
|
|
9394
|
+
const now = Date.now();
|
|
9395
|
+
const thirtyDaysAgo = now - 30 * 24 * 60 * 60 * 1e3;
|
|
9396
|
+
for (const file of files) {
|
|
9397
|
+
const filePath = path11.join(debugDir, file);
|
|
9398
|
+
const stats = await fs10.stat(filePath);
|
|
9399
|
+
if (stats.mtime.getTime() < thirtyDaysAgo) {
|
|
9400
|
+
await fs10.unlink(filePath);
|
|
9401
|
+
}
|
|
9402
|
+
}
|
|
9403
|
+
} catch (error) {
|
|
9404
|
+
console.error("Failed to cleanup old logs:", error);
|
|
9405
|
+
}
|
|
9406
|
+
}
|
|
9407
|
+
};
|
|
9408
|
+
var logger = Logger2.getInstance();
|
|
9409
|
+
|
|
9482
9410
|
// ../../b4m-core/packages/mcp/dist/src/client.js
|
|
9483
9411
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
9484
9412
|
import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
|
|
@@ -10282,10 +10210,7 @@ var ApiClient = class {
|
|
|
10282
10210
|
* Make a POST request
|
|
10283
10211
|
*/
|
|
10284
10212
|
async post(url, data, config) {
|
|
10285
|
-
logger.debug(`[ApiClient] POST ${this.client.defaults.baseURL}${url}`);
|
|
10286
|
-
logger.debug(`[ApiClient] Request body: ${JSON.stringify(data)}`);
|
|
10287
10213
|
const response = await this.client.post(url, data, config);
|
|
10288
|
-
logger.debug(`[ApiClient] Response status: ${response.status}`);
|
|
10289
10214
|
return response.data;
|
|
10290
10215
|
}
|
|
10291
10216
|
/**
|
|
@@ -10336,7 +10261,7 @@ import { isAxiosError as isAxiosError2 } from "axios";
|
|
|
10336
10261
|
// package.json
|
|
10337
10262
|
var package_default = {
|
|
10338
10263
|
name: "@bike4mind/cli",
|
|
10339
|
-
version: "0.2.7-feat-
|
|
10264
|
+
version: "0.2.7-feat-organization-overview-enhancements.17130+2e676f9be",
|
|
10340
10265
|
type: "module",
|
|
10341
10266
|
description: "Interactive CLI tool for Bike4Mind with ReAct agents",
|
|
10342
10267
|
license: "UNLICENSED",
|
|
@@ -10439,11 +10364,11 @@ var package_default = {
|
|
|
10439
10364
|
},
|
|
10440
10365
|
devDependencies: {
|
|
10441
10366
|
"@bike4mind/agents": "0.1.0",
|
|
10442
|
-
"@bike4mind/common": "2.38.1-feat-
|
|
10443
|
-
"@bike4mind/mcp": "1.20.1-feat-
|
|
10444
|
-
"@bike4mind/services": "2.33.1-feat-
|
|
10445
|
-
"@bike4mind/utils": "2.1.2-feat-
|
|
10446
|
-
"@types/better-sqlite3": "^7.6.
|
|
10367
|
+
"@bike4mind/common": "2.38.1-feat-organization-overview-enhancements.17130+2e676f9be",
|
|
10368
|
+
"@bike4mind/mcp": "1.20.1-feat-organization-overview-enhancements.17130+2e676f9be",
|
|
10369
|
+
"@bike4mind/services": "2.33.1-feat-organization-overview-enhancements.17130+2e676f9be",
|
|
10370
|
+
"@bike4mind/utils": "2.1.2-feat-organization-overview-enhancements.17130+2e676f9be",
|
|
10371
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
10447
10372
|
"@types/diff": "^5.0.9",
|
|
10448
10373
|
"@types/jsonwebtoken": "^9.0.4",
|
|
10449
10374
|
"@types/node": "^22.9.0",
|
|
@@ -10455,7 +10380,7 @@ var package_default = {
|
|
|
10455
10380
|
typescript: "^5.9.3",
|
|
10456
10381
|
vitest: "^3.2.4"
|
|
10457
10382
|
},
|
|
10458
|
-
gitHead: "
|
|
10383
|
+
gitHead: "2e676f9bedcd3876486314a5bcade6acc1491e3f"
|
|
10459
10384
|
};
|
|
10460
10385
|
|
|
10461
10386
|
// src/config/constants.ts
|
|
@@ -10679,6 +10604,10 @@ function CliApp() {
|
|
|
10679
10604
|
setPermissionPrompt(prompt);
|
|
10680
10605
|
});
|
|
10681
10606
|
};
|
|
10607
|
+
console.log("\u{1F511} Tool API Keys:", {
|
|
10608
|
+
openweather: config.toolApiKeys?.openweather ? "\u2713 configured" : "\u2717 missing",
|
|
10609
|
+
serper: config.toolApiKeys?.serper ? "\u2713 configured" : "\u2717 missing"
|
|
10610
|
+
});
|
|
10682
10611
|
const agentContext = {
|
|
10683
10612
|
currentAgent: null,
|
|
10684
10613
|
observationQueue: []
|
|
@@ -10691,7 +10620,7 @@ function CliApp() {
|
|
|
10691
10620
|
promptFn,
|
|
10692
10621
|
agentContext,
|
|
10693
10622
|
state.configStore,
|
|
10694
|
-
|
|
10623
|
+
config.toolApiKeys
|
|
10695
10624
|
);
|
|
10696
10625
|
console.log(`\u{1F6E0}\uFE0F Loaded ${b4mTools.length} B4M tool(s)`);
|
|
10697
10626
|
const mcpManager = new McpManager(config);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bike4mind/cli",
|
|
3
|
-
"version": "0.2.7-feat-
|
|
3
|
+
"version": "0.2.7-feat-organization-overview-enhancements.17130+2e676f9be",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Interactive CLI tool for Bike4Mind with ReAct agents",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -103,11 +103,11 @@
|
|
|
103
103
|
},
|
|
104
104
|
"devDependencies": {
|
|
105
105
|
"@bike4mind/agents": "0.1.0",
|
|
106
|
-
"@bike4mind/common": "2.38.1-feat-
|
|
107
|
-
"@bike4mind/mcp": "1.20.1-feat-
|
|
108
|
-
"@bike4mind/services": "2.33.1-feat-
|
|
109
|
-
"@bike4mind/utils": "2.1.2-feat-
|
|
110
|
-
"@types/better-sqlite3": "^7.6.
|
|
106
|
+
"@bike4mind/common": "2.38.1-feat-organization-overview-enhancements.17130+2e676f9be",
|
|
107
|
+
"@bike4mind/mcp": "1.20.1-feat-organization-overview-enhancements.17130+2e676f9be",
|
|
108
|
+
"@bike4mind/services": "2.33.1-feat-organization-overview-enhancements.17130+2e676f9be",
|
|
109
|
+
"@bike4mind/utils": "2.1.2-feat-organization-overview-enhancements.17130+2e676f9be",
|
|
110
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
111
111
|
"@types/diff": "^5.0.9",
|
|
112
112
|
"@types/jsonwebtoken": "^9.0.4",
|
|
113
113
|
"@types/node": "^22.9.0",
|
|
@@ -119,5 +119,5 @@
|
|
|
119
119
|
"typescript": "^5.9.3",
|
|
120
120
|
"vitest": "^3.2.4"
|
|
121
121
|
},
|
|
122
|
-
"gitHead": "
|
|
122
|
+
"gitHead": "2e676f9bedcd3876486314a5bcade6acc1491e3f"
|
|
123
123
|
}
|