@bike4mind/cli 0.2.8-cli-bash-tools.17140 → 0.2.8-cli-bash-tools.17146
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 +365 -294
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -9183,6 +9183,17 @@ var MementoEvalResponseSchema = z142.object({
|
|
|
9183
9183
|
// ../../b4m-core/packages/services/dist/src/auth/AccessTokenGeneratorService.js
|
|
9184
9184
|
import jwt2 from "jsonwebtoken";
|
|
9185
9185
|
|
|
9186
|
+
// ../../b4m-core/packages/services/dist/src/cliTools/types.js
|
|
9187
|
+
var ToolErrorType;
|
|
9188
|
+
(function(ToolErrorType2) {
|
|
9189
|
+
ToolErrorType2["INVALID_INPUT"] = "INVALID_INPUT";
|
|
9190
|
+
ToolErrorType2["API_KEY_MISSING"] = "API_KEY_MISSING";
|
|
9191
|
+
ToolErrorType2["EXTERNAL_API_ERROR"] = "EXTERNAL_API_ERROR";
|
|
9192
|
+
ToolErrorType2["RATE_LIMIT"] = "RATE_LIMIT";
|
|
9193
|
+
ToolErrorType2["NETWORK_ERROR"] = "NETWORK_ERROR";
|
|
9194
|
+
ToolErrorType2["UNKNOWN"] = "UNKNOWN";
|
|
9195
|
+
})(ToolErrorType || (ToolErrorType = {}));
|
|
9196
|
+
|
|
9186
9197
|
// src/utils/diffPreview.ts
|
|
9187
9198
|
import * as Diff from "diff";
|
|
9188
9199
|
import { readFile } from "fs/promises";
|
|
@@ -9233,6 +9244,338 @@ Last modified: ${stats.mtime.toLocaleString()}`;
|
|
|
9233
9244
|
}
|
|
9234
9245
|
}
|
|
9235
9246
|
|
|
9247
|
+
// src/utils/Logger.ts
|
|
9248
|
+
import fs10 from "fs/promises";
|
|
9249
|
+
import path12 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 = path12.join(os.homedir(), ".bike4mind", "debug");
|
|
9273
|
+
await fs10.mkdir(debugDir, { recursive: true });
|
|
9274
|
+
this.logFilePath = path12.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);
|
|
9314
|
+
}
|
|
9315
|
+
/**
|
|
9316
|
+
* ERROR level - always shown to user
|
|
9317
|
+
*/
|
|
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);
|
|
9326
|
+
}
|
|
9327
|
+
/**
|
|
9328
|
+
* Write log entry to file
|
|
9329
|
+
*/
|
|
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
|
+
}
|
|
9342
|
+
}
|
|
9343
|
+
/**
|
|
9344
|
+
* Log error details to file
|
|
9345
|
+
*/
|
|
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
|
+
}
|
|
9382
|
+
}
|
|
9383
|
+
/**
|
|
9384
|
+
* Safely stringify object, handling circular references
|
|
9385
|
+
*/
|
|
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
|
+
}
|
|
9408
|
+
}
|
|
9409
|
+
/**
|
|
9410
|
+
* Extract readable text from error response data
|
|
9411
|
+
*/
|
|
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
|
+
}
|
|
9437
|
+
}
|
|
9438
|
+
return this.safeStringify(data);
|
|
9439
|
+
}
|
|
9440
|
+
/**
|
|
9441
|
+
* Parse HTML error page to extract error message
|
|
9442
|
+
*/
|
|
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;
|
|
9458
|
+
}
|
|
9459
|
+
/**
|
|
9460
|
+
* Truncate string to max length with ellipsis
|
|
9461
|
+
*/
|
|
9462
|
+
truncate(str, maxLength) {
|
|
9463
|
+
if (str.length <= maxLength) {
|
|
9464
|
+
return str;
|
|
9465
|
+
}
|
|
9466
|
+
return str.substring(0, maxLength) + "... [truncated]";
|
|
9467
|
+
}
|
|
9468
|
+
/**
|
|
9469
|
+
* Format bytes to human-readable size
|
|
9470
|
+
*/
|
|
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];
|
|
9477
|
+
}
|
|
9478
|
+
/**
|
|
9479
|
+
* Clean up old debug logs (older than 30 days)
|
|
9480
|
+
*/
|
|
9481
|
+
async cleanupOldLogs() {
|
|
9482
|
+
if (!this.fileLoggingEnabled) return;
|
|
9483
|
+
try {
|
|
9484
|
+
const debugDir = path12.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 = path12.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
|
+
}
|
|
9498
|
+
}
|
|
9499
|
+
};
|
|
9500
|
+
var logger = Logger2.getInstance();
|
|
9501
|
+
|
|
9502
|
+
// src/llm/ServerToolExecutor.ts
|
|
9503
|
+
var ServerToolExecutor = class {
|
|
9504
|
+
constructor(apiClient) {
|
|
9505
|
+
this.toolsEndpoint = "/api/ai/v1/tools";
|
|
9506
|
+
this.apiClient = apiClient;
|
|
9507
|
+
}
|
|
9508
|
+
/**
|
|
9509
|
+
* Execute a tool server-side
|
|
9510
|
+
*
|
|
9511
|
+
* @param toolName - Name of the tool to execute (e.g., 'weather_info', 'web_search')
|
|
9512
|
+
* @param input - Tool input parameters
|
|
9513
|
+
* @returns Tool execution result (content or error)
|
|
9514
|
+
*/
|
|
9515
|
+
async executeTool(toolName, input) {
|
|
9516
|
+
logger.debug(`[ServerToolExecutor] Executing tool: ${toolName}`);
|
|
9517
|
+
logger.debug(`[ServerToolExecutor] Endpoint: ${this.toolsEndpoint}`);
|
|
9518
|
+
logger.debug(`[ServerToolExecutor] Input: ${JSON.stringify(input)}`);
|
|
9519
|
+
try {
|
|
9520
|
+
const result = await this.apiClient.post(this.toolsEndpoint, {
|
|
9521
|
+
toolName,
|
|
9522
|
+
input
|
|
9523
|
+
});
|
|
9524
|
+
logger.debug(`[ServerToolExecutor] Response: ${JSON.stringify(result)}`);
|
|
9525
|
+
if (!result.success) {
|
|
9526
|
+
const errorMessage = result.error || "Tool execution failed";
|
|
9527
|
+
logger.error(`[ServerToolExecutor] Tool ${toolName} failed: ${errorMessage}`);
|
|
9528
|
+
return `Error executing ${toolName}: ${errorMessage}`;
|
|
9529
|
+
}
|
|
9530
|
+
logger.debug(`[ServerToolExecutor] Tool ${toolName} succeeded in ${result.executionTimeMs}ms`);
|
|
9531
|
+
return result.content || "";
|
|
9532
|
+
} catch (error) {
|
|
9533
|
+
const err = error;
|
|
9534
|
+
const errorMessage = err?.response?.data?.error || err?.message || "Unknown error";
|
|
9535
|
+
const status = err?.response?.status || "unknown";
|
|
9536
|
+
logger.error(`[ServerToolExecutor] Request failed for ${toolName}: ${errorMessage} (status: ${status})`);
|
|
9537
|
+
logger.error(`[ServerToolExecutor] Full error: ${JSON.stringify(err, null, 2)}`);
|
|
9538
|
+
return `Error executing ${toolName}: ${errorMessage}`;
|
|
9539
|
+
}
|
|
9540
|
+
}
|
|
9541
|
+
};
|
|
9542
|
+
|
|
9543
|
+
// src/llm/ToolRouter.ts
|
|
9544
|
+
var SERVER_TOOLS = ["weather_info", "web_search"];
|
|
9545
|
+
var LOCAL_TOOLS = [
|
|
9546
|
+
"file_read",
|
|
9547
|
+
"create_file",
|
|
9548
|
+
"glob_files",
|
|
9549
|
+
"grep_search",
|
|
9550
|
+
"delete_file",
|
|
9551
|
+
"edit_file",
|
|
9552
|
+
"dice_roll",
|
|
9553
|
+
"math_evaluate",
|
|
9554
|
+
"current_datetime"
|
|
9555
|
+
];
|
|
9556
|
+
function isServerTool(toolName) {
|
|
9557
|
+
return SERVER_TOOLS.includes(toolName);
|
|
9558
|
+
}
|
|
9559
|
+
function isLocalTool(toolName) {
|
|
9560
|
+
return LOCAL_TOOLS.includes(toolName);
|
|
9561
|
+
}
|
|
9562
|
+
async function executeTool(toolName, input, apiClient, localToolFn) {
|
|
9563
|
+
if (isServerTool(toolName)) {
|
|
9564
|
+
logger.debug(`[ToolRouter] Routing ${toolName} to server`);
|
|
9565
|
+
const executor = new ServerToolExecutor(apiClient);
|
|
9566
|
+
return await executor.executeTool(toolName, input);
|
|
9567
|
+
} else if (isLocalTool(toolName)) {
|
|
9568
|
+
logger.debug(`[ToolRouter] Executing ${toolName} locally`);
|
|
9569
|
+
if (!localToolFn) {
|
|
9570
|
+
throw new Error(`Local tool ${toolName} has no implementation`);
|
|
9571
|
+
}
|
|
9572
|
+
const result = await localToolFn(input);
|
|
9573
|
+
return typeof result === "string" ? result : JSON.stringify(result);
|
|
9574
|
+
} else {
|
|
9575
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
9576
|
+
}
|
|
9577
|
+
}
|
|
9578
|
+
|
|
9236
9579
|
// src/utils/toolsAdapter.ts
|
|
9237
9580
|
var NoOpStorage = class extends BaseStorage {
|
|
9238
9581
|
async upload(input, destination, options) {
|
|
@@ -9272,14 +9615,14 @@ var CliLogger = class extends Logger {
|
|
|
9272
9615
|
log(...args) {
|
|
9273
9616
|
}
|
|
9274
9617
|
};
|
|
9275
|
-
function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore) {
|
|
9618
|
+
function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient) {
|
|
9276
9619
|
const originalFn = tool.toolFn;
|
|
9277
9620
|
const toolName = tool.toolSchema.name;
|
|
9278
9621
|
return {
|
|
9279
9622
|
...tool,
|
|
9280
9623
|
toolFn: async (args) => {
|
|
9281
9624
|
if (!permissionManager.needsPermission(toolName)) {
|
|
9282
|
-
const result2 = await
|
|
9625
|
+
const result2 = await executeTool(toolName, args, apiClient, originalFn);
|
|
9283
9626
|
agentContext.observationQueue.push({ toolName, result: result2 });
|
|
9284
9627
|
return result2;
|
|
9285
9628
|
}
|
|
@@ -9340,13 +9683,13 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
|
|
|
9340
9683
|
}
|
|
9341
9684
|
}
|
|
9342
9685
|
}
|
|
9343
|
-
const result = await
|
|
9686
|
+
const result = await executeTool(toolName, args, apiClient, originalFn);
|
|
9344
9687
|
agentContext.observationQueue.push({ toolName, result });
|
|
9345
9688
|
return result;
|
|
9346
9689
|
}
|
|
9347
9690
|
};
|
|
9348
9691
|
}
|
|
9349
|
-
function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore,
|
|
9692
|
+
function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient) {
|
|
9350
9693
|
const logger2 = new CliLogger();
|
|
9351
9694
|
const storage = new NoOpStorage();
|
|
9352
9695
|
const user = {
|
|
@@ -9363,18 +9706,6 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
|
|
|
9363
9706
|
};
|
|
9364
9707
|
const mockAdminSettings = {
|
|
9365
9708
|
findBySettingName: async (settingName) => {
|
|
9366
|
-
if (settingName === "OpenWeatherKey") {
|
|
9367
|
-
const key = toolApiKeys?.openweather || process.env.OPENWEATHER_API_KEY;
|
|
9368
|
-
if (key) {
|
|
9369
|
-
return { settingValue: key };
|
|
9370
|
-
}
|
|
9371
|
-
}
|
|
9372
|
-
if (settingName === "SerperKey") {
|
|
9373
|
-
const key = toolApiKeys?.serper || process.env.SERPER_API_KEY;
|
|
9374
|
-
if (key) {
|
|
9375
|
-
return { settingValue: key };
|
|
9376
|
-
}
|
|
9377
|
-
}
|
|
9378
9709
|
return null;
|
|
9379
9710
|
}
|
|
9380
9711
|
};
|
|
@@ -9384,15 +9715,13 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
|
|
|
9384
9715
|
adminSettings: mockAdminSettings
|
|
9385
9716
|
}
|
|
9386
9717
|
};
|
|
9387
|
-
const hasSerperKey = !!(toolApiKeys?.serper || process.env.SERPER_API_KEY);
|
|
9388
|
-
const hasOpenWeatherKey = !!(toolApiKeys?.openweather || process.env.OPENWEATHER_API_KEY);
|
|
9389
9718
|
const toolConfig = {
|
|
9390
|
-
//
|
|
9719
|
+
// Local-only tools (no external API keys needed)
|
|
9391
9720
|
dice_roll: {},
|
|
9392
9721
|
math_evaluate: {},
|
|
9393
9722
|
current_datetime: {},
|
|
9394
9723
|
prompt_enhancement: {},
|
|
9395
|
-
// File operation tools (CLI-specific)
|
|
9724
|
+
// File operation tools (CLI-specific, local execution)
|
|
9396
9725
|
file_read: {},
|
|
9397
9726
|
create_file: {},
|
|
9398
9727
|
glob_files: {},
|
|
@@ -9400,13 +9729,10 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
|
|
|
9400
9729
|
delete_file: {},
|
|
9401
9730
|
// Shell execution (always requires permission)
|
|
9402
9731
|
bash_execute: {},
|
|
9403
|
-
//
|
|
9404
|
-
|
|
9405
|
-
|
|
9406
|
-
|
|
9407
|
-
// Requires OPENWEATHER_API_KEY
|
|
9408
|
-
...hasSerperKey && { deep_research: {} }
|
|
9409
|
-
// Requires SERPER_API_KEY
|
|
9732
|
+
// Server-side tools (executed via /api/ai/v1/tools Lambda)
|
|
9733
|
+
// Routed by ToolRouter to use B4M company API keys
|
|
9734
|
+
weather_info: {},
|
|
9735
|
+
web_search: {}
|
|
9410
9736
|
// Disable web-only tools
|
|
9411
9737
|
// blog_publish: undefined,
|
|
9412
9738
|
// blog_edit: undefined,
|
|
@@ -9416,6 +9742,7 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
|
|
|
9416
9742
|
// mermaid_chart: undefined,
|
|
9417
9743
|
// recharts: undefined,
|
|
9418
9744
|
// edit_file: undefined,
|
|
9745
|
+
// deep_research: undefined,
|
|
9419
9746
|
};
|
|
9420
9747
|
const toolsMap = generateTools(
|
|
9421
9748
|
userId,
|
|
@@ -9433,7 +9760,7 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
|
|
|
9433
9760
|
model
|
|
9434
9761
|
);
|
|
9435
9762
|
const tools2 = Object.entries(toolsMap).filter(([key]) => toolConfig[key] !== void 0).map(
|
|
9436
|
-
([_, tool]) => wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore)
|
|
9763
|
+
([_, tool]) => wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient)
|
|
9437
9764
|
);
|
|
9438
9765
|
return { tools: tools2, agentContext };
|
|
9439
9766
|
}
|
|
@@ -9629,261 +9956,6 @@ function getEnvironmentName(configApiConfig) {
|
|
|
9629
9956
|
return "Bike4Mind";
|
|
9630
9957
|
}
|
|
9631
9958
|
|
|
9632
|
-
// src/utils/Logger.ts
|
|
9633
|
-
import fs10 from "fs/promises";
|
|
9634
|
-
import path12 from "path";
|
|
9635
|
-
import os from "os";
|
|
9636
|
-
var Logger2 = class _Logger {
|
|
9637
|
-
constructor() {
|
|
9638
|
-
this.logFilePath = null;
|
|
9639
|
-
this.sessionId = null;
|
|
9640
|
-
this.fileLoggingEnabled = true;
|
|
9641
|
-
this.consoleVerbose = false;
|
|
9642
|
-
}
|
|
9643
|
-
static {
|
|
9644
|
-
this.instance = null;
|
|
9645
|
-
}
|
|
9646
|
-
static getInstance() {
|
|
9647
|
-
if (!_Logger.instance) {
|
|
9648
|
-
_Logger.instance = new _Logger();
|
|
9649
|
-
}
|
|
9650
|
-
return _Logger.instance;
|
|
9651
|
-
}
|
|
9652
|
-
/**
|
|
9653
|
-
* Initialize the logger with a session ID
|
|
9654
|
-
*/
|
|
9655
|
-
async initialize(sessionId) {
|
|
9656
|
-
this.sessionId = sessionId;
|
|
9657
|
-
const debugDir = path12.join(os.homedir(), ".bike4mind", "debug");
|
|
9658
|
-
await fs10.mkdir(debugDir, { recursive: true });
|
|
9659
|
-
this.logFilePath = path12.join(debugDir, `${sessionId}.txt`);
|
|
9660
|
-
await this.writeToFile("INFO", "=== CLI SESSION START ===");
|
|
9661
|
-
}
|
|
9662
|
-
/**
|
|
9663
|
-
* Set whether verbose console logging is enabled
|
|
9664
|
-
*/
|
|
9665
|
-
setVerbose(enabled) {
|
|
9666
|
-
this.consoleVerbose = enabled;
|
|
9667
|
-
}
|
|
9668
|
-
/**
|
|
9669
|
-
* Set whether file logging is enabled
|
|
9670
|
-
*/
|
|
9671
|
-
setFileLoggingEnabled(enabled) {
|
|
9672
|
-
this.fileLoggingEnabled = enabled;
|
|
9673
|
-
}
|
|
9674
|
-
/**
|
|
9675
|
-
* DEBUG level - verbose-only console, always file
|
|
9676
|
-
*/
|
|
9677
|
-
debug(message) {
|
|
9678
|
-
this.writeToFile("DEBUG", message).catch(() => {
|
|
9679
|
-
});
|
|
9680
|
-
if (this.consoleVerbose) {
|
|
9681
|
-
console.log(message);
|
|
9682
|
-
}
|
|
9683
|
-
}
|
|
9684
|
-
/**
|
|
9685
|
-
* INFO level - always shown to user
|
|
9686
|
-
*/
|
|
9687
|
-
info(message) {
|
|
9688
|
-
this.writeToFile("INFO", message).catch(() => {
|
|
9689
|
-
});
|
|
9690
|
-
console.log(message);
|
|
9691
|
-
}
|
|
9692
|
-
/**
|
|
9693
|
-
* WARN level - always shown to user
|
|
9694
|
-
*/
|
|
9695
|
-
warn(message) {
|
|
9696
|
-
this.writeToFile("WARN", message).catch(() => {
|
|
9697
|
-
});
|
|
9698
|
-
console.warn(message);
|
|
9699
|
-
}
|
|
9700
|
-
/**
|
|
9701
|
-
* ERROR level - always shown to user
|
|
9702
|
-
*/
|
|
9703
|
-
error(message, err) {
|
|
9704
|
-
this.writeToFile("ERROR", message).catch(() => {
|
|
9705
|
-
});
|
|
9706
|
-
if (err) {
|
|
9707
|
-
this.logErrorDetailsToFile(err).catch(() => {
|
|
9708
|
-
});
|
|
9709
|
-
}
|
|
9710
|
-
console.error(message);
|
|
9711
|
-
}
|
|
9712
|
-
/**
|
|
9713
|
-
* Write log entry to file
|
|
9714
|
-
*/
|
|
9715
|
-
async writeToFile(level, message) {
|
|
9716
|
-
if (!this.fileLoggingEnabled || !this.logFilePath) {
|
|
9717
|
-
return;
|
|
9718
|
-
}
|
|
9719
|
-
try {
|
|
9720
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19);
|
|
9721
|
-
const logEntry = `[${timestamp}] [${level}] ${message}
|
|
9722
|
-
`;
|
|
9723
|
-
await fs10.appendFile(this.logFilePath, logEntry, "utf-8");
|
|
9724
|
-
} catch (error) {
|
|
9725
|
-
console.error("File logging failed:", error);
|
|
9726
|
-
}
|
|
9727
|
-
}
|
|
9728
|
-
/**
|
|
9729
|
-
* Log error details to file
|
|
9730
|
-
*/
|
|
9731
|
-
async logErrorDetailsToFile(err) {
|
|
9732
|
-
if (!this.fileLoggingEnabled || !this.logFilePath) {
|
|
9733
|
-
return;
|
|
9734
|
-
}
|
|
9735
|
-
try {
|
|
9736
|
-
if (err && typeof err === "object" && "response" in err && err.response) {
|
|
9737
|
-
const response = err.response;
|
|
9738
|
-
const config = err && typeof err === "object" && "config" in err ? err.config : void 0;
|
|
9739
|
-
await this.writeToFile("ERROR", ` Status: ${response.status} ${response.statusText || ""}`);
|
|
9740
|
-
await this.writeToFile("ERROR", ` URL: ${config?.url || "unknown"}`);
|
|
9741
|
-
await this.writeToFile("ERROR", ` Headers: ${this.safeStringify(response.headers)}`);
|
|
9742
|
-
if (response.data) {
|
|
9743
|
-
const errorText = this.extractErrorMessage(response.data);
|
|
9744
|
-
if (errorText.trim().startsWith("<!DOCTYPE") || errorText.trim().startsWith("<html")) {
|
|
9745
|
-
await this.writeToFile("ERROR", ` Response Type: HTML Error Page`);
|
|
9746
|
-
const parsedError = this.parseHtmlError(errorText);
|
|
9747
|
-
if (parsedError) {
|
|
9748
|
-
await this.writeToFile("ERROR", ` Error Message: ${parsedError}`);
|
|
9749
|
-
}
|
|
9750
|
-
await this.writeToFile("ERROR", ` Raw HTML: ${this.truncate(errorText, 1e3)}`);
|
|
9751
|
-
} else {
|
|
9752
|
-
const preview = this.truncate(errorText, 500);
|
|
9753
|
-
await this.writeToFile("ERROR", ` Response: ${preview}`);
|
|
9754
|
-
}
|
|
9755
|
-
} else {
|
|
9756
|
-
await this.writeToFile("ERROR", ` Response: (no data)`);
|
|
9757
|
-
}
|
|
9758
|
-
}
|
|
9759
|
-
if (err && typeof err === "object" && "stack" in err && typeof err.stack === "string") {
|
|
9760
|
-
const stackLines = err.stack.split("\n").slice(0, 5).join("\n ");
|
|
9761
|
-
await this.writeToFile("ERROR", ` Stack: ${stackLines}`);
|
|
9762
|
-
} else if (err && typeof err === "object" && "message" in err && typeof err.message === "string") {
|
|
9763
|
-
await this.writeToFile("ERROR", ` Message: ${err.message}`);
|
|
9764
|
-
}
|
|
9765
|
-
} catch (error) {
|
|
9766
|
-
}
|
|
9767
|
-
}
|
|
9768
|
-
/**
|
|
9769
|
-
* Safely stringify object, handling circular references
|
|
9770
|
-
*/
|
|
9771
|
-
safeStringify(obj) {
|
|
9772
|
-
try {
|
|
9773
|
-
return JSON.stringify(obj);
|
|
9774
|
-
} catch (error) {
|
|
9775
|
-
if (error instanceof Error && error.message.includes("circular")) {
|
|
9776
|
-
try {
|
|
9777
|
-
const seen = /* @__PURE__ */ new WeakSet();
|
|
9778
|
-
return JSON.stringify(obj, (key, value) => {
|
|
9779
|
-
if (typeof value === "object" && value !== null) {
|
|
9780
|
-
if (seen.has(value)) {
|
|
9781
|
-
return "[Circular]";
|
|
9782
|
-
}
|
|
9783
|
-
seen.add(value);
|
|
9784
|
-
}
|
|
9785
|
-
return value;
|
|
9786
|
-
});
|
|
9787
|
-
} catch {
|
|
9788
|
-
return "[Unable to stringify]";
|
|
9789
|
-
}
|
|
9790
|
-
}
|
|
9791
|
-
return "[Stringify error]";
|
|
9792
|
-
}
|
|
9793
|
-
}
|
|
9794
|
-
/**
|
|
9795
|
-
* Extract readable text from error response data
|
|
9796
|
-
*/
|
|
9797
|
-
extractErrorMessage(data) {
|
|
9798
|
-
if (Buffer.isBuffer(data)) {
|
|
9799
|
-
return data.toString("utf-8");
|
|
9800
|
-
}
|
|
9801
|
-
if (typeof data === "string") {
|
|
9802
|
-
return data;
|
|
9803
|
-
}
|
|
9804
|
-
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) {
|
|
9805
|
-
const chunks = [];
|
|
9806
|
-
for (const chunk of data._readableState.buffer) {
|
|
9807
|
-
if (chunk && typeof chunk === "object" && "type" in chunk && chunk.type === "Buffer" && "data" in chunk && Array.isArray(chunk.data)) {
|
|
9808
|
-
chunks.push(Buffer.from(chunk.data));
|
|
9809
|
-
} else if (Buffer.isBuffer(chunk)) {
|
|
9810
|
-
chunks.push(chunk);
|
|
9811
|
-
} else if (chunk && typeof chunk === "object" && "data" in chunk) {
|
|
9812
|
-
if (Buffer.isBuffer(chunk.data)) {
|
|
9813
|
-
chunks.push(chunk.data);
|
|
9814
|
-
} else if (Array.isArray(chunk.data)) {
|
|
9815
|
-
chunks.push(Buffer.from(chunk.data));
|
|
9816
|
-
}
|
|
9817
|
-
}
|
|
9818
|
-
}
|
|
9819
|
-
if (chunks.length > 0) {
|
|
9820
|
-
return Buffer.concat(chunks).toString("utf-8");
|
|
9821
|
-
}
|
|
9822
|
-
}
|
|
9823
|
-
return this.safeStringify(data);
|
|
9824
|
-
}
|
|
9825
|
-
/**
|
|
9826
|
-
* Parse HTML error page to extract error message
|
|
9827
|
-
*/
|
|
9828
|
-
parseHtmlError(html) {
|
|
9829
|
-
const titleMatch = html.match(/<title>(.*?)<\/title>/i);
|
|
9830
|
-
const h1Match = html.match(/<h1>(.*?)<\/h1>/i);
|
|
9831
|
-
const bodyMatch = html.match(/<body[^>]*>(.*?)<\/body>/is);
|
|
9832
|
-
if (titleMatch && titleMatch[1] !== "Error") {
|
|
9833
|
-
return titleMatch[1].trim();
|
|
9834
|
-
}
|
|
9835
|
-
if (h1Match) {
|
|
9836
|
-
return h1Match[1].trim();
|
|
9837
|
-
}
|
|
9838
|
-
if (bodyMatch) {
|
|
9839
|
-
const text = bodyMatch[1].replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
9840
|
-
return text.substring(0, 200);
|
|
9841
|
-
}
|
|
9842
|
-
return null;
|
|
9843
|
-
}
|
|
9844
|
-
/**
|
|
9845
|
-
* Truncate string to max length with ellipsis
|
|
9846
|
-
*/
|
|
9847
|
-
truncate(str, maxLength) {
|
|
9848
|
-
if (str.length <= maxLength) {
|
|
9849
|
-
return str;
|
|
9850
|
-
}
|
|
9851
|
-
return str.substring(0, maxLength) + "... [truncated]";
|
|
9852
|
-
}
|
|
9853
|
-
/**
|
|
9854
|
-
* Format bytes to human-readable size
|
|
9855
|
-
*/
|
|
9856
|
-
formatBytes(bytes) {
|
|
9857
|
-
if (bytes === 0) return "0 B";
|
|
9858
|
-
const k = 1024;
|
|
9859
|
-
const sizes = ["B", "KB", "MB", "GB"];
|
|
9860
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
9861
|
-
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + " " + sizes[i];
|
|
9862
|
-
}
|
|
9863
|
-
/**
|
|
9864
|
-
* Clean up old debug logs (older than 30 days)
|
|
9865
|
-
*/
|
|
9866
|
-
async cleanupOldLogs() {
|
|
9867
|
-
if (!this.fileLoggingEnabled) return;
|
|
9868
|
-
try {
|
|
9869
|
-
const debugDir = path12.join(os.homedir(), ".bike4mind", "debug");
|
|
9870
|
-
const files = await fs10.readdir(debugDir);
|
|
9871
|
-
const now = Date.now();
|
|
9872
|
-
const thirtyDaysAgo = now - 30 * 24 * 60 * 60 * 1e3;
|
|
9873
|
-
for (const file of files) {
|
|
9874
|
-
const filePath = path12.join(debugDir, file);
|
|
9875
|
-
const stats = await fs10.stat(filePath);
|
|
9876
|
-
if (stats.mtime.getTime() < thirtyDaysAgo) {
|
|
9877
|
-
await fs10.unlink(filePath);
|
|
9878
|
-
}
|
|
9879
|
-
}
|
|
9880
|
-
} catch (error) {
|
|
9881
|
-
console.error("Failed to cleanup old logs:", error);
|
|
9882
|
-
}
|
|
9883
|
-
}
|
|
9884
|
-
};
|
|
9885
|
-
var logger = Logger2.getInstance();
|
|
9886
|
-
|
|
9887
9959
|
// ../../b4m-core/packages/mcp/dist/src/client.js
|
|
9888
9960
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
9889
9961
|
import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
|
|
@@ -10687,7 +10759,10 @@ var ApiClient = class {
|
|
|
10687
10759
|
* Make a POST request
|
|
10688
10760
|
*/
|
|
10689
10761
|
async post(url, data, config) {
|
|
10762
|
+
logger.debug(`[ApiClient] POST ${this.client.defaults.baseURL}${url}`);
|
|
10763
|
+
logger.debug(`[ApiClient] Request body: ${JSON.stringify(data)}`);
|
|
10690
10764
|
const response = await this.client.post(url, data, config);
|
|
10765
|
+
logger.debug(`[ApiClient] Response status: ${response.status}`);
|
|
10691
10766
|
return response.data;
|
|
10692
10767
|
}
|
|
10693
10768
|
/**
|
|
@@ -10738,7 +10813,7 @@ import { isAxiosError as isAxiosError2 } from "axios";
|
|
|
10738
10813
|
// package.json
|
|
10739
10814
|
var package_default = {
|
|
10740
10815
|
name: "@bike4mind/cli",
|
|
10741
|
-
version: "0.2.8-cli-bash-tools.
|
|
10816
|
+
version: "0.2.8-cli-bash-tools.17146+9129ce1ea",
|
|
10742
10817
|
type: "module",
|
|
10743
10818
|
description: "Interactive CLI tool for Bike4Mind with ReAct agents",
|
|
10744
10819
|
license: "UNLICENSED",
|
|
@@ -10841,10 +10916,10 @@ var package_default = {
|
|
|
10841
10916
|
},
|
|
10842
10917
|
devDependencies: {
|
|
10843
10918
|
"@bike4mind/agents": "0.1.0",
|
|
10844
|
-
"@bike4mind/common": "2.38.2-cli-bash-tools.
|
|
10845
|
-
"@bike4mind/mcp": "1.20.2-cli-bash-tools.
|
|
10846
|
-
"@bike4mind/services": "2.33.2-cli-bash-tools.
|
|
10847
|
-
"@bike4mind/utils": "2.1.3-cli-bash-tools.
|
|
10919
|
+
"@bike4mind/common": "2.38.2-cli-bash-tools.17146+9129ce1ea",
|
|
10920
|
+
"@bike4mind/mcp": "1.20.2-cli-bash-tools.17146+9129ce1ea",
|
|
10921
|
+
"@bike4mind/services": "2.33.2-cli-bash-tools.17146+9129ce1ea",
|
|
10922
|
+
"@bike4mind/utils": "2.1.3-cli-bash-tools.17146+9129ce1ea",
|
|
10848
10923
|
"@types/better-sqlite3": "^7.6.12",
|
|
10849
10924
|
"@types/diff": "^5.0.9",
|
|
10850
10925
|
"@types/jsonwebtoken": "^9.0.4",
|
|
@@ -10857,7 +10932,7 @@ var package_default = {
|
|
|
10857
10932
|
typescript: "^5.9.3",
|
|
10858
10933
|
vitest: "^3.2.4"
|
|
10859
10934
|
},
|
|
10860
|
-
gitHead: "
|
|
10935
|
+
gitHead: "9129ce1ea5d8589dec80ac92bf32ca954c6844fc"
|
|
10861
10936
|
};
|
|
10862
10937
|
|
|
10863
10938
|
// src/config/constants.ts
|
|
@@ -11082,10 +11157,6 @@ function CliApp() {
|
|
|
11082
11157
|
setPermissionPrompt(prompt);
|
|
11083
11158
|
});
|
|
11084
11159
|
};
|
|
11085
|
-
console.log("\u{1F511} Tool API Keys:", {
|
|
11086
|
-
openweather: config.toolApiKeys?.openweather ? "\u2713 configured" : "\u2717 missing",
|
|
11087
|
-
serper: config.toolApiKeys?.serper ? "\u2713 configured" : "\u2717 missing"
|
|
11088
|
-
});
|
|
11089
11160
|
const agentContext = {
|
|
11090
11161
|
currentAgent: null,
|
|
11091
11162
|
observationQueue: []
|
|
@@ -11098,7 +11169,7 @@ function CliApp() {
|
|
|
11098
11169
|
promptFn,
|
|
11099
11170
|
agentContext,
|
|
11100
11171
|
state.configStore,
|
|
11101
|
-
|
|
11172
|
+
apiClient
|
|
11102
11173
|
);
|
|
11103
11174
|
console.log(`\u{1F6E0}\uFE0F Loaded ${b4mTools.length} B4M tool(s)`);
|
|
11104
11175
|
const mcpManager = new McpManager(config);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bike4mind/cli",
|
|
3
|
-
"version": "0.2.8-cli-bash-tools.
|
|
3
|
+
"version": "0.2.8-cli-bash-tools.17146+9129ce1ea",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Interactive CLI tool for Bike4Mind with ReAct agents",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -103,10 +103,10 @@
|
|
|
103
103
|
},
|
|
104
104
|
"devDependencies": {
|
|
105
105
|
"@bike4mind/agents": "0.1.0",
|
|
106
|
-
"@bike4mind/common": "2.38.2-cli-bash-tools.
|
|
107
|
-
"@bike4mind/mcp": "1.20.2-cli-bash-tools.
|
|
108
|
-
"@bike4mind/services": "2.33.2-cli-bash-tools.
|
|
109
|
-
"@bike4mind/utils": "2.1.3-cli-bash-tools.
|
|
106
|
+
"@bike4mind/common": "2.38.2-cli-bash-tools.17146+9129ce1ea",
|
|
107
|
+
"@bike4mind/mcp": "1.20.2-cli-bash-tools.17146+9129ce1ea",
|
|
108
|
+
"@bike4mind/services": "2.33.2-cli-bash-tools.17146+9129ce1ea",
|
|
109
|
+
"@bike4mind/utils": "2.1.3-cli-bash-tools.17146+9129ce1ea",
|
|
110
110
|
"@types/better-sqlite3": "^7.6.12",
|
|
111
111
|
"@types/diff": "^5.0.9",
|
|
112
112
|
"@types/jsonwebtoken": "^9.0.4",
|
|
@@ -119,5 +119,5 @@
|
|
|
119
119
|
"typescript": "^5.9.3",
|
|
120
120
|
"vitest": "^3.2.4"
|
|
121
121
|
},
|
|
122
|
-
"gitHead": "
|
|
122
|
+
"gitHead": "9129ce1ea5d8589dec80ac92bf32ca954c6844fc"
|
|
123
123
|
}
|