@bike4mind/cli 0.2.1-quest-file-uploads.17007 → 0.2.3-basic-cli-shortcuts.17058
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/{chunk-F3ETXO5N.js → chunk-5N4PG5EH.js} +1 -1
- package/dist/{chunk-QXGU6KYI.js → chunk-GZTILMGE.js} +1 -1
- package/dist/{chunk-U3GPYBTD.js → chunk-NZADYDFG.js} +12 -7
- package/dist/{chunk-LZMRYJ3B.js → chunk-Y4HMTEDH.js} +13 -5
- package/dist/{create-YUYXTEIS.js → create-ZTK4723N.js} +2 -2
- package/dist/index.js +864 -239
- package/dist/{mementoService-2BDIT6PH.js → mementoService-5XQYECNM.js} +2 -2
- package/dist/{src-VLP2EHKE.js → src-DOO3QAO2.js} +1 -1
- package/dist/{subtractCredits-NNYIS5XZ.js → subtractCredits-3QOJA6XF.js} +2 -2
- package/package.json +13 -7
package/dist/index.js
CHANGED
|
@@ -4,9 +4,9 @@ import {
|
|
|
4
4
|
getEffectiveApiKey,
|
|
5
5
|
getOpenWeatherKey,
|
|
6
6
|
getSerperKey
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-GZTILMGE.js";
|
|
8
|
+
import "./chunk-Y4HMTEDH.js";
|
|
9
|
+
import "./chunk-5N4PG5EH.js";
|
|
10
10
|
import {
|
|
11
11
|
BFLImageService,
|
|
12
12
|
BaseStorage,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
OpenAIBackend,
|
|
16
16
|
OpenAIImageService,
|
|
17
17
|
XAIImageService
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-NZADYDFG.js";
|
|
19
19
|
import {
|
|
20
20
|
Logger
|
|
21
21
|
} from "./chunk-AMDXHL6S.js";
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
AuthEvents,
|
|
30
30
|
BFL_IMAGE_MODELS,
|
|
31
31
|
BFL_SAFETY_TOLERANCE,
|
|
32
|
+
CREDIT_DEDUCT_TRANSACTION_TYPES,
|
|
32
33
|
ChatCompletionCreateInputSchema,
|
|
33
34
|
ChatModels,
|
|
34
35
|
CollectionType,
|
|
@@ -78,13 +79,13 @@ import {
|
|
|
78
79
|
} from "./chunk-PDX44BCA.js";
|
|
79
80
|
|
|
80
81
|
// src/index.tsx
|
|
81
|
-
import
|
|
82
|
-
import { render, Box as
|
|
82
|
+
import React12, { useState as useState4, useEffect as useEffect3, useCallback } from "react";
|
|
83
|
+
import { render, Box as Box11, Text as Text11, useApp, useInput as useInput3 } from "ink";
|
|
83
84
|
import { v4 as uuidv49 } from "uuid";
|
|
84
85
|
|
|
85
86
|
// src/components/App.tsx
|
|
86
|
-
import
|
|
87
|
-
import { Box as
|
|
87
|
+
import React9 from "react";
|
|
88
|
+
import { Box as Box8, Text as Text8, Static } from "ink";
|
|
88
89
|
|
|
89
90
|
// src/components/StatusBar.tsx
|
|
90
91
|
import React from "react";
|
|
@@ -94,26 +95,327 @@ var StatusBar = React.memo(function StatusBar2({ sessionName, model, tokenUsage
|
|
|
94
95
|
});
|
|
95
96
|
|
|
96
97
|
// src/components/InputPrompt.tsx
|
|
97
|
-
import
|
|
98
|
-
import { Box as
|
|
99
|
-
|
|
98
|
+
import React5, { useState as useState2, useMemo, useEffect } from "react";
|
|
99
|
+
import { Box as Box4, Text as Text5, useInput as useInput2 } from "ink";
|
|
100
|
+
|
|
101
|
+
// src/components/CustomTextInput.tsx
|
|
102
|
+
import React2, { useState } from "react";
|
|
103
|
+
import { Text as Text2, useInput } from "ink";
|
|
104
|
+
function CustomTextInput({
|
|
105
|
+
value,
|
|
106
|
+
onChange,
|
|
107
|
+
onSubmit,
|
|
108
|
+
placeholder = "",
|
|
109
|
+
showCursor = true
|
|
110
|
+
}) {
|
|
111
|
+
const [cursorOffset, setCursorOffset] = useState(value.length);
|
|
112
|
+
useInput(
|
|
113
|
+
(input, key) => {
|
|
114
|
+
if (key.return) {
|
|
115
|
+
onSubmit(value);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (key.ctrl) {
|
|
119
|
+
switch (input) {
|
|
120
|
+
case "u":
|
|
121
|
+
{
|
|
122
|
+
const lines = value.split("\n");
|
|
123
|
+
let currentLineStart = 0;
|
|
124
|
+
let currentLineIndex = 0;
|
|
125
|
+
let charCount = 0;
|
|
126
|
+
for (let i = 0; i < lines.length; i++) {
|
|
127
|
+
const lineLength = lines[i].length;
|
|
128
|
+
if (charCount + lineLength >= cursorOffset) {
|
|
129
|
+
currentLineIndex = i;
|
|
130
|
+
currentLineStart = charCount;
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
charCount += lineLength + 1;
|
|
134
|
+
}
|
|
135
|
+
lines.splice(currentLineIndex, 1);
|
|
136
|
+
const newValue = lines.join("\n");
|
|
137
|
+
onChange(newValue);
|
|
138
|
+
setCursorOffset(Math.min(currentLineStart, newValue.length));
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
case "k":
|
|
142
|
+
onChange(value.slice(0, cursorOffset));
|
|
143
|
+
return;
|
|
144
|
+
case "w":
|
|
145
|
+
{
|
|
146
|
+
const beforeCursor2 = value.slice(0, cursorOffset);
|
|
147
|
+
const afterCursor2 = value.slice(cursorOffset);
|
|
148
|
+
const trimmed = beforeCursor2.trimEnd();
|
|
149
|
+
const lastSpace = trimmed.lastIndexOf(" ");
|
|
150
|
+
const newBefore = lastSpace === -1 ? "" : trimmed.slice(0, lastSpace + 1);
|
|
151
|
+
const newValue = newBefore + afterCursor2;
|
|
152
|
+
onChange(newValue);
|
|
153
|
+
setCursorOffset(newBefore.length);
|
|
154
|
+
}
|
|
155
|
+
return;
|
|
156
|
+
case "a":
|
|
157
|
+
setCursorOffset(0);
|
|
158
|
+
return;
|
|
159
|
+
case "e":
|
|
160
|
+
setCursorOffset(value.length);
|
|
161
|
+
return;
|
|
162
|
+
case "l":
|
|
163
|
+
onChange("");
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (key.backspace || key.delete) {
|
|
168
|
+
if (cursorOffset > 0) {
|
|
169
|
+
const newValue = value.slice(0, cursorOffset - 1) + value.slice(cursorOffset);
|
|
170
|
+
onChange(newValue);
|
|
171
|
+
setCursorOffset(cursorOffset - 1);
|
|
172
|
+
}
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (key.leftArrow) {
|
|
176
|
+
setCursorOffset(Math.max(0, cursorOffset - 1));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (key.rightArrow) {
|
|
180
|
+
setCursorOffset(Math.min(value.length, cursorOffset + 1));
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (!key.ctrl && !key.meta && input.length === 1) {
|
|
184
|
+
const newValue = value.slice(0, cursorOffset) + input + value.slice(cursorOffset);
|
|
185
|
+
onChange(newValue);
|
|
186
|
+
setCursorOffset(cursorOffset + 1);
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
{ isActive: true }
|
|
190
|
+
);
|
|
191
|
+
const hasValue = value.length > 0;
|
|
192
|
+
if (!hasValue) {
|
|
193
|
+
return /* @__PURE__ */ React2.createElement(Text2, null, showCursor ? /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(Text2, { inverse: true }, " "), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, placeholder)) : /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, placeholder));
|
|
194
|
+
}
|
|
195
|
+
const beforeCursor = value.slice(0, cursorOffset);
|
|
196
|
+
const cursorChar = value[cursorOffset] || " ";
|
|
197
|
+
const afterCursor = value.slice(cursorOffset + 1);
|
|
198
|
+
return /* @__PURE__ */ React2.createElement(Text2, null, beforeCursor, showCursor && /* @__PURE__ */ React2.createElement(Text2, { inverse: true }, cursorChar), !showCursor && cursorChar, afterCursor);
|
|
199
|
+
}
|
|
100
200
|
|
|
101
201
|
// src/components/CommandAutocomplete.tsx
|
|
102
|
-
import
|
|
103
|
-
import { Box as Box2, Text as
|
|
202
|
+
import React3 from "react";
|
|
203
|
+
import { Box as Box2, Text as Text3 } from "ink";
|
|
104
204
|
function CommandAutocomplete({ commands, selectedIndex }) {
|
|
105
205
|
if (commands.length === 0) {
|
|
106
|
-
return /* @__PURE__ */
|
|
206
|
+
return /* @__PURE__ */ React3.createElement(Box2, { marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "No matching commands"));
|
|
107
207
|
}
|
|
108
|
-
return /* @__PURE__ */
|
|
208
|
+
return /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, commands.length === 1 ? "1 command" : `${commands.length} commands`, " \u2022 Use \u2191\u2193 to navigate, Enter to select")), commands.map((cmd, index) => {
|
|
109
209
|
const args = cmd.args ? ` ${cmd.args}` : "";
|
|
110
210
|
const isSelected = index === selectedIndex;
|
|
111
|
-
return /* @__PURE__ */
|
|
211
|
+
return /* @__PURE__ */ React3.createElement(Box2, { key: cmd.name, marginLeft: 1 }, /* @__PURE__ */ React3.createElement(Text3, { color: isSelected ? "cyan" : void 0, bold: isSelected }, isSelected ? "\u25B8 " : " ", "/", cmd.name, args, " - ", cmd.description));
|
|
112
212
|
}));
|
|
113
213
|
}
|
|
114
214
|
|
|
115
|
-
// src/
|
|
215
|
+
// src/components/FileAutocomplete.tsx
|
|
216
|
+
import React4 from "react";
|
|
217
|
+
import { Box as Box3, Text as Text4 } from "ink";
|
|
218
|
+
|
|
219
|
+
// src/utils/fileSearch.ts
|
|
220
|
+
import * as fs from "fs";
|
|
221
|
+
import * as path from "path";
|
|
116
222
|
import Fuse from "fuse.js";
|
|
223
|
+
var DEFAULT_IGNORE_PATTERNS = [
|
|
224
|
+
"node_modules",
|
|
225
|
+
".git",
|
|
226
|
+
".next",
|
|
227
|
+
".turbo",
|
|
228
|
+
"dist",
|
|
229
|
+
"build",
|
|
230
|
+
".cache",
|
|
231
|
+
"coverage",
|
|
232
|
+
".DS_Store",
|
|
233
|
+
".env",
|
|
234
|
+
".env.local",
|
|
235
|
+
".env.secrets"
|
|
236
|
+
];
|
|
237
|
+
function walkDirectory(basePath = process.cwd(), maxDepth = 5, ignorePatterns = DEFAULT_IGNORE_PATTERNS) {
|
|
238
|
+
const results = [];
|
|
239
|
+
const normalizedBase = path.resolve(basePath);
|
|
240
|
+
function walk(currentPath, depth) {
|
|
241
|
+
if (depth > maxDepth) return;
|
|
242
|
+
try {
|
|
243
|
+
const entries = fs.readdirSync(currentPath, { withFileTypes: true });
|
|
244
|
+
for (const entry of entries) {
|
|
245
|
+
if (ignorePatterns.some((pattern) => entry.name === pattern || entry.name.startsWith(pattern))) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
const fullPath = path.join(currentPath, entry.name);
|
|
249
|
+
const relativePath = path.relative(normalizedBase, fullPath);
|
|
250
|
+
if (entry.isDirectory()) {
|
|
251
|
+
results.push({
|
|
252
|
+
path: relativePath,
|
|
253
|
+
isDirectory: true
|
|
254
|
+
});
|
|
255
|
+
walk(fullPath, depth + 1);
|
|
256
|
+
} else if (entry.isFile()) {
|
|
257
|
+
try {
|
|
258
|
+
const stats = fs.statSync(fullPath);
|
|
259
|
+
results.push({
|
|
260
|
+
path: relativePath,
|
|
261
|
+
isDirectory: false,
|
|
262
|
+
size: stats.size
|
|
263
|
+
});
|
|
264
|
+
} catch {
|
|
265
|
+
results.push({
|
|
266
|
+
path: relativePath,
|
|
267
|
+
isDirectory: false
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
} catch {
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
walk(normalizedBase, 0);
|
|
276
|
+
return results;
|
|
277
|
+
}
|
|
278
|
+
function formatFileSize(bytes) {
|
|
279
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
280
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
281
|
+
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
282
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
|
283
|
+
}
|
|
284
|
+
var cachedFiles = null;
|
|
285
|
+
var cacheTimestamp = 0;
|
|
286
|
+
var CACHE_TTL_MS = 3e4;
|
|
287
|
+
function getCachedFiles() {
|
|
288
|
+
const now = Date.now();
|
|
289
|
+
if (!cachedFiles || now - cacheTimestamp > CACHE_TTL_MS) {
|
|
290
|
+
cachedFiles = walkDirectory();
|
|
291
|
+
cacheTimestamp = now;
|
|
292
|
+
}
|
|
293
|
+
return cachedFiles;
|
|
294
|
+
}
|
|
295
|
+
function searchFiles(query) {
|
|
296
|
+
const files = getCachedFiles();
|
|
297
|
+
if (!query || query.trim() === "") {
|
|
298
|
+
return files.filter((f) => !f.path.includes(path.sep)).slice(0, 20);
|
|
299
|
+
}
|
|
300
|
+
const queryDir = path.dirname(query);
|
|
301
|
+
let searchPool = files;
|
|
302
|
+
if (queryDir && queryDir !== ".") {
|
|
303
|
+
const normalizedDir = queryDir.replace(/\\/g, "/");
|
|
304
|
+
searchPool = files.filter((f) => {
|
|
305
|
+
const normalizedPath = f.path.replace(/\\/g, "/");
|
|
306
|
+
return normalizedPath.startsWith(normalizedDir + "/") || normalizedPath === normalizedDir;
|
|
307
|
+
});
|
|
308
|
+
if (searchPool.length === 0) {
|
|
309
|
+
searchPool = files;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
const fuse2 = new Fuse(searchPool, {
|
|
313
|
+
keys: [{ name: "path", weight: 1 }],
|
|
314
|
+
threshold: 0.4,
|
|
315
|
+
// Allow some fuzzy matching
|
|
316
|
+
includeScore: true,
|
|
317
|
+
minMatchCharLength: 1,
|
|
318
|
+
// Use extended search for better path matching
|
|
319
|
+
useExtendedSearch: false
|
|
320
|
+
});
|
|
321
|
+
const results = fuse2.search(query);
|
|
322
|
+
return results.slice(0, 20).map((result) => result.item);
|
|
323
|
+
}
|
|
324
|
+
function isPathWithinCwd(filePath) {
|
|
325
|
+
const cwd = path.resolve(process.cwd());
|
|
326
|
+
const absolutePath = path.resolve(cwd, filePath);
|
|
327
|
+
let resolvedCwd = cwd;
|
|
328
|
+
let resolvedPath = absolutePath;
|
|
329
|
+
try {
|
|
330
|
+
const realCwd = fs.realpathSync.native(cwd);
|
|
331
|
+
if (realCwd) {
|
|
332
|
+
resolvedCwd = realCwd;
|
|
333
|
+
}
|
|
334
|
+
} catch {
|
|
335
|
+
}
|
|
336
|
+
try {
|
|
337
|
+
const realPath = fs.realpathSync.native(absolutePath);
|
|
338
|
+
if (realPath) {
|
|
339
|
+
resolvedPath = realPath;
|
|
340
|
+
}
|
|
341
|
+
} catch {
|
|
342
|
+
const relativePath2 = path.relative(cwd, absolutePath);
|
|
343
|
+
resolvedPath = path.resolve(resolvedCwd, relativePath2);
|
|
344
|
+
}
|
|
345
|
+
const normalizedCwd = resolvedCwd.toLowerCase();
|
|
346
|
+
const normalizedPath = resolvedPath.toLowerCase();
|
|
347
|
+
const isWithin = normalizedPath === normalizedCwd || normalizedPath.startsWith(normalizedCwd + path.sep);
|
|
348
|
+
if (!isWithin) {
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
const relativePath = path.relative(resolvedCwd, resolvedPath);
|
|
352
|
+
return !relativePath.startsWith("..") && !path.isAbsolute(relativePath);
|
|
353
|
+
}
|
|
354
|
+
function isBinaryFile(filePath) {
|
|
355
|
+
const binaryExtensions = [
|
|
356
|
+
".png",
|
|
357
|
+
".jpg",
|
|
358
|
+
".jpeg",
|
|
359
|
+
".gif",
|
|
360
|
+
".bmp",
|
|
361
|
+
".ico",
|
|
362
|
+
".webp",
|
|
363
|
+
// Note: .svg is NOT included because SVG is XML text format that can be safely read
|
|
364
|
+
".pdf",
|
|
365
|
+
".doc",
|
|
366
|
+
".docx",
|
|
367
|
+
".xls",
|
|
368
|
+
".xlsx",
|
|
369
|
+
".ppt",
|
|
370
|
+
".pptx",
|
|
371
|
+
".zip",
|
|
372
|
+
".tar",
|
|
373
|
+
".gz",
|
|
374
|
+
".rar",
|
|
375
|
+
".7z",
|
|
376
|
+
".exe",
|
|
377
|
+
".dll",
|
|
378
|
+
".so",
|
|
379
|
+
".dylib",
|
|
380
|
+
".bin",
|
|
381
|
+
".dat",
|
|
382
|
+
".mp3",
|
|
383
|
+
".mp4",
|
|
384
|
+
".wav",
|
|
385
|
+
".avi",
|
|
386
|
+
".mov",
|
|
387
|
+
".mkv",
|
|
388
|
+
".ttf",
|
|
389
|
+
".otf",
|
|
390
|
+
".woff",
|
|
391
|
+
".woff2",
|
|
392
|
+
".eot",
|
|
393
|
+
".class",
|
|
394
|
+
".pyc",
|
|
395
|
+
".o",
|
|
396
|
+
".obj"
|
|
397
|
+
];
|
|
398
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
399
|
+
return binaryExtensions.includes(ext);
|
|
400
|
+
}
|
|
401
|
+
var MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
402
|
+
|
|
403
|
+
// src/components/FileAutocomplete.tsx
|
|
404
|
+
function FileAutocomplete({ files, selectedIndex, query }) {
|
|
405
|
+
if (files.length === 0) {
|
|
406
|
+
return /* @__PURE__ */ React4.createElement(Box3, { marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "No matching files", query ? ` for "${query}"` : ""));
|
|
407
|
+
}
|
|
408
|
+
return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column", marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React4.createElement(Box3, { marginBottom: 1 }, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, files.length === 1 ? "1 match" : `${files.length} matches`, " - Use up/down to navigate, Tab to select")), files.map((file, index) => {
|
|
409
|
+
const isSelected = index === selectedIndex;
|
|
410
|
+
const icon = file.isDirectory ? "[folder]" : "[file] ";
|
|
411
|
+
const sizeDisplay = file.isDirectory ? "" : file.size !== void 0 ? ` (${formatFileSize(file.size)})` : "";
|
|
412
|
+
const pathDisplay = file.isDirectory ? `${file.path}/` : file.path;
|
|
413
|
+
return /* @__PURE__ */ React4.createElement(Box3, { key: file.path, marginLeft: 1 }, /* @__PURE__ */ React4.createElement(Text4, { color: isSelected ? "cyan" : void 0, bold: isSelected }, isSelected ? "> " : " "), /* @__PURE__ */ React4.createElement(Text4, { color: file.isDirectory ? "yellow" : isSelected ? "cyan" : void 0, bold: isSelected }, icon), /* @__PURE__ */ React4.createElement(Text4, { color: isSelected ? "cyan" : void 0, bold: isSelected }, " ", pathDisplay), /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, sizeDisplay));
|
|
414
|
+
}));
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// src/utils/fuzzySearch.ts
|
|
418
|
+
import Fuse2 from "fuse.js";
|
|
117
419
|
|
|
118
420
|
// src/config/commands.ts
|
|
119
421
|
var COMMANDS = [
|
|
@@ -177,11 +479,15 @@ var COMMANDS = [
|
|
|
177
479
|
{
|
|
178
480
|
name: "trusted",
|
|
179
481
|
description: "List all trusted tools"
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
name: "usage",
|
|
485
|
+
description: "Show credit usage and balance"
|
|
180
486
|
}
|
|
181
487
|
];
|
|
182
488
|
|
|
183
489
|
// src/utils/fuzzySearch.ts
|
|
184
|
-
var fuse = new
|
|
490
|
+
var fuse = new Fuse2(COMMANDS, {
|
|
185
491
|
keys: [
|
|
186
492
|
{ name: "name", weight: 0.7 },
|
|
187
493
|
// Prioritize command name
|
|
@@ -202,23 +508,64 @@ function searchCommands(query) {
|
|
|
202
508
|
}
|
|
203
509
|
|
|
204
510
|
// src/components/InputPrompt.tsx
|
|
511
|
+
function findAtTrigger(value) {
|
|
512
|
+
for (let i = value.length - 1; i >= 0; i--) {
|
|
513
|
+
if (value[i] === "@") {
|
|
514
|
+
const isValid = i === 0 || value[i - 1] === " ";
|
|
515
|
+
if (isValid) {
|
|
516
|
+
const query = value.slice(i + 1);
|
|
517
|
+
if (query.includes(" ")) {
|
|
518
|
+
return null;
|
|
519
|
+
}
|
|
520
|
+
return { startIndex: i, query };
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
205
526
|
function InputPrompt({ onSubmit, disabled = false, history = [] }) {
|
|
206
|
-
const [value, setValue] =
|
|
207
|
-
const [selectedIndex, setSelectedIndex] =
|
|
208
|
-
const [historyIndex, setHistoryIndex] =
|
|
209
|
-
const [tempInput, setTempInput] =
|
|
210
|
-
const [
|
|
211
|
-
const
|
|
212
|
-
const
|
|
527
|
+
const [value, setValue] = useState2("");
|
|
528
|
+
const [selectedIndex, setSelectedIndex] = useState2(0);
|
|
529
|
+
const [historyIndex, setHistoryIndex] = useState2(-1);
|
|
530
|
+
const [tempInput, setTempInput] = useState2("");
|
|
531
|
+
const [fileAutocomplete, setFileAutocomplete] = useState2(null);
|
|
532
|
+
const [fileSelectedIndex, setFileSelectedIndex] = useState2(0);
|
|
533
|
+
const shouldShowCommandAutocomplete = value.startsWith("/") && !disabled && !fileAutocomplete?.active;
|
|
534
|
+
const commandQuery = shouldShowCommandAutocomplete ? value.slice(1) : "";
|
|
213
535
|
const filteredCommands = useMemo(() => {
|
|
214
|
-
if (!
|
|
215
|
-
return searchCommands(
|
|
216
|
-
}, [
|
|
536
|
+
if (!shouldShowCommandAutocomplete) return [];
|
|
537
|
+
return searchCommands(commandQuery);
|
|
538
|
+
}, [shouldShowCommandAutocomplete, commandQuery]);
|
|
539
|
+
const filteredFiles = useMemo(() => {
|
|
540
|
+
if (!fileAutocomplete?.active) return [];
|
|
541
|
+
return searchFiles(fileAutocomplete.query);
|
|
542
|
+
}, [fileAutocomplete?.active, fileAutocomplete?.query]);
|
|
217
543
|
useEffect(() => {
|
|
218
544
|
setSelectedIndex(0);
|
|
219
545
|
}, [filteredCommands]);
|
|
220
|
-
|
|
221
|
-
|
|
546
|
+
useEffect(() => {
|
|
547
|
+
setFileSelectedIndex(0);
|
|
548
|
+
}, [filteredFiles]);
|
|
549
|
+
useInput2((_input, key) => {
|
|
550
|
+
if (fileAutocomplete?.active && filteredFiles.length > 0) {
|
|
551
|
+
if (key.upArrow) {
|
|
552
|
+
setFileSelectedIndex((prev) => prev > 0 ? prev - 1 : filteredFiles.length - 1);
|
|
553
|
+
return;
|
|
554
|
+
} else if (key.downArrow) {
|
|
555
|
+
setFileSelectedIndex((prev) => prev < filteredFiles.length - 1 ? prev + 1 : 0);
|
|
556
|
+
return;
|
|
557
|
+
} else if (key.tab) {
|
|
558
|
+
const selectedFile = filteredFiles[fileSelectedIndex];
|
|
559
|
+
if (selectedFile) {
|
|
560
|
+
insertSelectedFile(selectedFile);
|
|
561
|
+
}
|
|
562
|
+
return;
|
|
563
|
+
} else if (key.escape) {
|
|
564
|
+
setFileAutocomplete(null);
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if (shouldShowCommandAutocomplete && filteredCommands.length > 0) {
|
|
222
569
|
if (key.upArrow) {
|
|
223
570
|
setSelectedIndex((prev) => prev > 0 ? prev - 1 : filteredCommands.length - 1);
|
|
224
571
|
} else if (key.downArrow) {
|
|
@@ -226,7 +573,7 @@ function InputPrompt({ onSubmit, disabled = false, history = [] }) {
|
|
|
226
573
|
}
|
|
227
574
|
return;
|
|
228
575
|
}
|
|
229
|
-
if (!
|
|
576
|
+
if (!shouldShowCommandAutocomplete && !fileAutocomplete?.active && history.length > 0) {
|
|
230
577
|
if (key.upArrow) {
|
|
231
578
|
if (historyIndex === -1) {
|
|
232
579
|
setTempInput(value);
|
|
@@ -249,15 +596,39 @@ function InputPrompt({ onSubmit, disabled = false, history = [] }) {
|
|
|
249
596
|
}
|
|
250
597
|
}
|
|
251
598
|
});
|
|
599
|
+
const insertSelectedFile = (file) => {
|
|
600
|
+
if (!fileAutocomplete) return;
|
|
601
|
+
const beforeAt = value.slice(0, fileAutocomplete.startIndex);
|
|
602
|
+
const afterQuery = value.slice(fileAutocomplete.startIndex + 1 + fileAutocomplete.query.length);
|
|
603
|
+
const pathToInsert = file.isDirectory ? `@${file.path}/` : `@${file.path}`;
|
|
604
|
+
const newValue = beforeAt + pathToInsert + afterQuery;
|
|
605
|
+
setValue(newValue);
|
|
606
|
+
if (file.isDirectory) {
|
|
607
|
+
setFileAutocomplete({
|
|
608
|
+
active: true,
|
|
609
|
+
startIndex: fileAutocomplete.startIndex,
|
|
610
|
+
query: file.path + "/"
|
|
611
|
+
});
|
|
612
|
+
setFileSelectedIndex(0);
|
|
613
|
+
} else {
|
|
614
|
+
setFileAutocomplete(null);
|
|
615
|
+
}
|
|
616
|
+
};
|
|
252
617
|
const handleSubmit = (input) => {
|
|
253
618
|
if (disabled || !input.trim()) return;
|
|
254
|
-
if (
|
|
619
|
+
if (fileAutocomplete?.active && filteredFiles.length > 0) {
|
|
620
|
+
const selectedFile = filteredFiles[fileSelectedIndex];
|
|
621
|
+
if (selectedFile) {
|
|
622
|
+
insertSelectedFile(selectedFile);
|
|
623
|
+
}
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (shouldShowCommandAutocomplete && filteredCommands.length > 0) {
|
|
255
627
|
const selectedCommand = filteredCommands[selectedIndex];
|
|
256
628
|
const commandText = `/${selectedCommand.name}`;
|
|
257
629
|
if (selectedCommand.args) {
|
|
258
630
|
setValue(commandText + " ");
|
|
259
631
|
setHistoryIndex(-1);
|
|
260
|
-
setInputKey((prev) => prev + 1);
|
|
261
632
|
return;
|
|
262
633
|
}
|
|
263
634
|
setValue("");
|
|
@@ -268,6 +639,7 @@ function InputPrompt({ onSubmit, disabled = false, history = [] }) {
|
|
|
268
639
|
onSubmit(input);
|
|
269
640
|
setValue("");
|
|
270
641
|
setHistoryIndex(-1);
|
|
642
|
+
setFileAutocomplete(null);
|
|
271
643
|
};
|
|
272
644
|
const handleChange = (newValue) => {
|
|
273
645
|
setValue(newValue);
|
|
@@ -275,30 +647,39 @@ function InputPrompt({ onSubmit, disabled = false, history = [] }) {
|
|
|
275
647
|
setHistoryIndex(-1);
|
|
276
648
|
setTempInput("");
|
|
277
649
|
}
|
|
650
|
+
const atTrigger = findAtTrigger(newValue);
|
|
651
|
+
if (atTrigger) {
|
|
652
|
+
setFileAutocomplete({
|
|
653
|
+
active: true,
|
|
654
|
+
startIndex: atTrigger.startIndex,
|
|
655
|
+
query: atTrigger.query
|
|
656
|
+
});
|
|
657
|
+
} else {
|
|
658
|
+
setFileAutocomplete(null);
|
|
659
|
+
}
|
|
278
660
|
};
|
|
279
|
-
return /* @__PURE__ */
|
|
280
|
-
|
|
661
|
+
return /* @__PURE__ */ React5.createElement(Box4, { flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Box4, null, /* @__PURE__ */ React5.createElement(Text5, { bold: true, color: "cyan" }, "> "), /* @__PURE__ */ React5.createElement(
|
|
662
|
+
CustomTextInput,
|
|
281
663
|
{
|
|
282
|
-
key: inputKey,
|
|
283
664
|
value,
|
|
284
665
|
onChange: handleChange,
|
|
285
666
|
onSubmit: handleSubmit,
|
|
286
|
-
placeholder: disabled ? "Thinking..." : "Type your message
|
|
667
|
+
placeholder: disabled ? "Thinking..." : "Type your message, /help for commands, or @file to reference",
|
|
287
668
|
showCursor: !disabled
|
|
288
669
|
}
|
|
289
|
-
)),
|
|
670
|
+
)), shouldShowCommandAutocomplete && /* @__PURE__ */ React5.createElement(CommandAutocomplete, { commands: filteredCommands, selectedIndex }), fileAutocomplete?.active && /* @__PURE__ */ React5.createElement(FileAutocomplete, { files: filteredFiles, selectedIndex: fileSelectedIndex, query: fileAutocomplete.query }));
|
|
290
671
|
}
|
|
291
672
|
|
|
292
673
|
// src/components/AgentThinking.tsx
|
|
293
|
-
import
|
|
294
|
-
import { Box as
|
|
674
|
+
import React7 from "react";
|
|
675
|
+
import { Box as Box6 } from "ink";
|
|
295
676
|
|
|
296
677
|
// src/components/ThoughtStream.tsx
|
|
297
|
-
import
|
|
298
|
-
import { Box as
|
|
678
|
+
import React6 from "react";
|
|
679
|
+
import { Box as Box5, Text as Text6 } from "ink";
|
|
299
680
|
import Spinner from "ink-spinner";
|
|
300
|
-
var ThoughtStream =
|
|
301
|
-
return /* @__PURE__ */
|
|
681
|
+
var ThoughtStream = React6.memo(function ThoughtStream2({ steps }) {
|
|
682
|
+
return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column", gap: 1 }, steps.map((step, index) => /* @__PURE__ */ React6.createElement(Box5, { key: index, flexDirection: "column" }, step.type === "thought" && /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Text6, { color: "magenta" }, "\u{1F914} Thought: "), /* @__PURE__ */ React6.createElement(Text6, null, step.content)), step.type === "action" && /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, "\u26A1 Action: "), /* @__PURE__ */ React6.createElement(Text6, { bold: true }, step.metadata?.toolName || "unknown")), step.metadata?.toolInput && /* @__PURE__ */ React6.createElement(Box5, { paddingLeft: 2 }, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, JSON.stringify(step.metadata.toolInput, null, 2)))), step.type === "observation" && /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Text6, { color: "blue" }, "\u{1F4CA} Observation: ")), /* @__PURE__ */ React6.createElement(Box5, { paddingLeft: 2 }, /* @__PURE__ */ React6.createElement(Text6, null, step.content))), step.type === "final_answer" && /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Text6, { color: "green", bold: true }, "\u2705 Final Answer:", " ")), /* @__PURE__ */ React6.createElement(Box5, { paddingLeft: 2 }, /* @__PURE__ */ React6.createElement(Text6, null, step.content)), step.metadata?.tokenUsage && /* @__PURE__ */ React6.createElement(Box5, { paddingLeft: 2 }, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "(", step.metadata.tokenUsage.total, " tokens)"))))), !steps.some((s) => s.type === "final_answer") && /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, /* @__PURE__ */ React6.createElement(Spinner, { type: "dots" })), /* @__PURE__ */ React6.createElement(Text6, null, " ", steps.length === 0 ? "Thinking..." : "Processing...")));
|
|
302
683
|
});
|
|
303
684
|
|
|
304
685
|
// src/store/index.ts
|
|
@@ -307,6 +688,12 @@ var useCliStore = create((set) => ({
|
|
|
307
688
|
// Session state
|
|
308
689
|
session: null,
|
|
309
690
|
setSession: (session) => set({ session }),
|
|
691
|
+
clearMessages: () => set((state) => ({
|
|
692
|
+
session: state.session ? {
|
|
693
|
+
...state.session,
|
|
694
|
+
messages: []
|
|
695
|
+
} : null
|
|
696
|
+
})),
|
|
310
697
|
// UI state
|
|
311
698
|
isThinking: false,
|
|
312
699
|
setIsThinking: (thinking) => set({ isThinking: thinking }),
|
|
@@ -324,32 +711,32 @@ var useCliStore = create((set) => ({
|
|
|
324
711
|
}));
|
|
325
712
|
|
|
326
713
|
// src/components/AgentThinking.tsx
|
|
327
|
-
var AgentThinking =
|
|
714
|
+
var AgentThinking = React7.memo(function AgentThinking2() {
|
|
328
715
|
const isThinking = useCliStore((state) => state.isThinking);
|
|
329
716
|
const agentSteps = useCliStore((state) => state.agentSteps);
|
|
330
717
|
if (!isThinking) {
|
|
331
718
|
return null;
|
|
332
719
|
}
|
|
333
|
-
return /* @__PURE__ */
|
|
720
|
+
return /* @__PURE__ */ React7.createElement(Box6, { borderStyle: "single", borderColor: "yellow", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React7.createElement(ThoughtStream, { steps: agentSteps }));
|
|
334
721
|
});
|
|
335
722
|
|
|
336
723
|
// src/components/PermissionPrompt.tsx
|
|
337
|
-
import
|
|
338
|
-
import { Box as
|
|
724
|
+
import React8 from "react";
|
|
725
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
339
726
|
import SelectInput from "ink-select-input";
|
|
340
727
|
function renderDiffPreview(preview) {
|
|
341
728
|
const lines = preview.split("\n");
|
|
342
729
|
return lines.map((line, index) => {
|
|
343
730
|
if (line.startsWith("+")) {
|
|
344
|
-
return /* @__PURE__ */
|
|
731
|
+
return /* @__PURE__ */ React8.createElement(Text7, { key: index, color: "green" }, line);
|
|
345
732
|
}
|
|
346
733
|
if (line.startsWith("-")) {
|
|
347
|
-
return /* @__PURE__ */
|
|
734
|
+
return /* @__PURE__ */ React8.createElement(Text7, { key: index, color: "red" }, line);
|
|
348
735
|
}
|
|
349
736
|
if (line.startsWith("@@")) {
|
|
350
|
-
return /* @__PURE__ */
|
|
737
|
+
return /* @__PURE__ */ React8.createElement(Text7, { key: index, color: "cyan" }, line);
|
|
351
738
|
}
|
|
352
|
-
return /* @__PURE__ */
|
|
739
|
+
return /* @__PURE__ */ React8.createElement(Text7, { key: index, dimColor: true }, line);
|
|
353
740
|
});
|
|
354
741
|
}
|
|
355
742
|
function PermissionPrompt({
|
|
@@ -369,7 +756,121 @@ function PermissionPrompt({
|
|
|
369
756
|
{ label: "\u2717 Deny", value: "deny" }
|
|
370
757
|
];
|
|
371
758
|
const argsString = typeof args === "string" ? args : JSON.stringify(args, null, 2);
|
|
372
|
-
return /* @__PURE__ */
|
|
759
|
+
return /* @__PURE__ */ React8.createElement(Box7, { flexDirection: "column", borderStyle: "bold", borderColor: "yellow", padding: 1, marginY: 1 }, /* @__PURE__ */ React8.createElement(Box7, null, /* @__PURE__ */ React8.createElement(Text7, { bold: true, color: "yellow" }, "\u26A0\uFE0F Permission Required")), /* @__PURE__ */ React8.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, "Tool: "), /* @__PURE__ */ React8.createElement(Text7, { bold: true, color: "cyan" }, toolName)), toolDescription && /* @__PURE__ */ React8.createElement(Box7, null, /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, "Action: "), /* @__PURE__ */ React8.createElement(Text7, null, toolDescription)), /* @__PURE__ */ React8.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React8.createElement(Text7, { bold: true }, "Arguments:"), /* @__PURE__ */ React8.createElement(Box7, { paddingLeft: 2, flexDirection: "column" }, /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, argsString))), preview && /* @__PURE__ */ React8.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React8.createElement(Text7, { bold: true }, "Preview:"), /* @__PURE__ */ React8.createElement(Box7, { borderStyle: "single", borderColor: "gray", paddingX: 1, flexDirection: "column" }, renderDiffPreview(preview))), !canBeTrusted && /* @__PURE__ */ React8.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(Text7, { color: "red", dimColor: true }, "Note: This tool cannot be trusted due to its dangerous nature.")), /* @__PURE__ */ React8.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(SelectInput, { items, onSelect: (item) => onResponse(item.value) })));
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// src/utils/processFileReferences.ts
|
|
763
|
+
import * as fs2 from "fs";
|
|
764
|
+
import * as path2 from "path";
|
|
765
|
+
|
|
766
|
+
// src/utils/constants.ts
|
|
767
|
+
var NAME_SUFFIXES = ["jr", "sr", "ii", "iii", "iv", "v", "phd", "md", "esq"];
|
|
768
|
+
function isNameSuffix(value) {
|
|
769
|
+
return NAME_SUFFIXES.includes(value);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// src/utils/processFileReferences.ts
|
|
773
|
+
var FILE_REFERENCE_REGEX = /(?:^|\s)@([^\s@]+)/g;
|
|
774
|
+
function looksLikeFilePath(ref) {
|
|
775
|
+
if (ref.includes("/") || ref.includes(path2.sep)) {
|
|
776
|
+
return true;
|
|
777
|
+
}
|
|
778
|
+
const extensionMatch = /\.(\w+)$/.exec(ref);
|
|
779
|
+
if (extensionMatch) {
|
|
780
|
+
const ext = extensionMatch[1].toLowerCase();
|
|
781
|
+
if (isNameSuffix(ext)) {
|
|
782
|
+
return false;
|
|
783
|
+
}
|
|
784
|
+
if (ext.length > 10) {
|
|
785
|
+
return false;
|
|
786
|
+
}
|
|
787
|
+
return true;
|
|
788
|
+
}
|
|
789
|
+
return false;
|
|
790
|
+
}
|
|
791
|
+
function extractFileReferences(message) {
|
|
792
|
+
const references = [];
|
|
793
|
+
const regex = /(?:^|\s)@([^\s@]+)/g;
|
|
794
|
+
let match;
|
|
795
|
+
while ((match = regex.exec(message)) !== null) {
|
|
796
|
+
const ref = match[1];
|
|
797
|
+
if (looksLikeFilePath(ref)) {
|
|
798
|
+
references.push(ref);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
return references;
|
|
802
|
+
}
|
|
803
|
+
function readFileContents(filePath) {
|
|
804
|
+
const cwd = process.cwd();
|
|
805
|
+
const absolutePath = path2.resolve(cwd, filePath);
|
|
806
|
+
if (!isPathWithinCwd(filePath)) {
|
|
807
|
+
return { error: `Security: Path "${filePath}" is outside the current working directory` };
|
|
808
|
+
}
|
|
809
|
+
if (!fs2.existsSync(absolutePath)) {
|
|
810
|
+
return { error: `File not found: "${filePath}"` };
|
|
811
|
+
}
|
|
812
|
+
const stats = fs2.statSync(absolutePath);
|
|
813
|
+
if (stats.isDirectory()) {
|
|
814
|
+
try {
|
|
815
|
+
const entries = fs2.readdirSync(absolutePath);
|
|
816
|
+
const fileCount = entries.length;
|
|
817
|
+
return {
|
|
818
|
+
content: `(Directory with ${fileCount} items. Use file tools to explore if needed.)`,
|
|
819
|
+
size: 0
|
|
820
|
+
};
|
|
821
|
+
} catch (err) {
|
|
822
|
+
return { error: `Cannot read directory "${filePath}": ${err instanceof Error ? err.message : "Unknown error"}` };
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
if (stats.size > MAX_FILE_SIZE) {
|
|
826
|
+
return {
|
|
827
|
+
error: `File too large: "${filePath}" is ${formatFileSize(stats.size)} (max ${formatFileSize(MAX_FILE_SIZE)})`
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
if (isBinaryFile(filePath)) {
|
|
831
|
+
return { error: `Binary file: "${filePath}" cannot be included as text content` };
|
|
832
|
+
}
|
|
833
|
+
try {
|
|
834
|
+
const content = fs2.readFileSync(absolutePath, "utf-8");
|
|
835
|
+
return { content, size: stats.size };
|
|
836
|
+
} catch (err) {
|
|
837
|
+
return { error: `Cannot read file "${filePath}": ${err instanceof Error ? err.message : "Unknown error"}` };
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
function formatFileBlock(filePath, content, size, isDirectory) {
|
|
841
|
+
if (isDirectory) {
|
|
842
|
+
return `
|
|
843
|
+
--- Directory Reference: ${filePath} ---
|
|
844
|
+
${content}
|
|
845
|
+
--- End of ${filePath} ---`;
|
|
846
|
+
}
|
|
847
|
+
return `
|
|
848
|
+
--- Referenced File: ${filePath} (${formatFileSize(size)}) ---
|
|
849
|
+
${content}
|
|
850
|
+
--- End of ${filePath} ---`;
|
|
851
|
+
}
|
|
852
|
+
async function processFileReferences(message) {
|
|
853
|
+
const references = extractFileReferences(message);
|
|
854
|
+
const errors = [];
|
|
855
|
+
const fileBlocks = [];
|
|
856
|
+
for (const ref of references) {
|
|
857
|
+
const result = readFileContents(ref);
|
|
858
|
+
if ("error" in result) {
|
|
859
|
+
errors.push(result.error);
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
const isDirectory = result.size === 0 && result.content.startsWith("(Directory");
|
|
863
|
+
fileBlocks.push(formatFileBlock(ref, result.content, result.size, isDirectory));
|
|
864
|
+
}
|
|
865
|
+
if (fileBlocks.length === 0) {
|
|
866
|
+
return { content: message, errors };
|
|
867
|
+
}
|
|
868
|
+
const processedContent = message + "\n" + fileBlocks.join("\n");
|
|
869
|
+
return { content: processedContent, errors };
|
|
870
|
+
}
|
|
871
|
+
function hasFileReferences(message) {
|
|
872
|
+
FILE_REFERENCE_REGEX.lastIndex = 0;
|
|
873
|
+
return FILE_REFERENCE_REGEX.test(message);
|
|
373
874
|
}
|
|
374
875
|
|
|
375
876
|
// src/components/App.tsx
|
|
@@ -382,7 +883,7 @@ function App({ onMessage, onCommand, onPermissionResponse, commandHistory = [] }
|
|
|
382
883
|
const permissionPrompt = useCliStore((state) => state.permissionPrompt);
|
|
383
884
|
const exitRequested = useCliStore((state) => state.exitRequested);
|
|
384
885
|
const setIsThinking = useCliStore((state) => state.setIsThinking);
|
|
385
|
-
const handleSubmit =
|
|
886
|
+
const handleSubmit = React9.useCallback(
|
|
386
887
|
async (input) => {
|
|
387
888
|
const trimmed = input.trim();
|
|
388
889
|
if (!trimmed) return;
|
|
@@ -393,14 +894,25 @@ function App({ onMessage, onCommand, onPermissionResponse, commandHistory = [] }
|
|
|
393
894
|
}
|
|
394
895
|
setIsThinking(true);
|
|
395
896
|
try {
|
|
396
|
-
|
|
897
|
+
let messageToSend = trimmed;
|
|
898
|
+
if (hasFileReferences(trimmed)) {
|
|
899
|
+
const processed = await processFileReferences(trimmed);
|
|
900
|
+
messageToSend = processed.content;
|
|
901
|
+
if (processed.errors.length > 0) {
|
|
902
|
+
const errorBlock = processed.errors.map((e) => `[Warning: ${e}]`).join("\n");
|
|
903
|
+
messageToSend = `${messageToSend}
|
|
904
|
+
|
|
905
|
+
${errorBlock}`;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
await onMessage(messageToSend);
|
|
397
909
|
} finally {
|
|
398
910
|
setIsThinking(false);
|
|
399
911
|
}
|
|
400
912
|
},
|
|
401
913
|
[onMessage, onCommand, setIsThinking]
|
|
402
914
|
);
|
|
403
|
-
return /* @__PURE__ */
|
|
915
|
+
return /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column" }, /* @__PURE__ */ React9.createElement(StatusBar, { sessionName, model: currentModel, tokenUsage: totalTokens }), permissionPrompt ? /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React9.createElement(
|
|
404
916
|
PermissionPrompt,
|
|
405
917
|
{
|
|
406
918
|
toolName: permissionPrompt.toolName,
|
|
@@ -409,39 +921,39 @@ function App({ onMessage, onCommand, onPermissionResponse, commandHistory = [] }
|
|
|
409
921
|
canBeTrusted: permissionPrompt.canBeTrusted,
|
|
410
922
|
onResponse: onPermissionResponse
|
|
411
923
|
}
|
|
412
|
-
)) : /* @__PURE__ */
|
|
924
|
+
)) : /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(Static, { items: messages }, (message, index) => /* @__PURE__ */ React9.createElement(Box8, { key: index, flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React9.createElement(MessageItem, { message }))), /* @__PURE__ */ React9.createElement(AgentThinking, null), exitRequested && /* @__PURE__ */ React9.createElement(Box8, { paddingX: 1, marginBottom: 1 }, /* @__PURE__ */ React9.createElement(Text8, { color: "yellow", bold: true }, "\u26A0\uFE0F Press Ctrl+C again to exit")), /* @__PURE__ */ React9.createElement(Box8, { borderStyle: "single", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React9.createElement(InputPrompt, { onSubmit: handleSubmit, disabled: isThinking, history: commandHistory }))));
|
|
413
925
|
}
|
|
414
926
|
function MessageItem({ message }) {
|
|
415
|
-
return /* @__PURE__ */
|
|
927
|
+
return /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React9.createElement(Box8, null, /* @__PURE__ */ React9.createElement(Text8, { bold: true, color: message.role === "user" ? "cyan" : "green" }, message.role === "user" ? "\u{1F464} You" : "\u{1F916} Assistant"), /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, " \u2022 ", new Date(message.timestamp).toLocaleTimeString())), /* @__PURE__ */ React9.createElement(Box8, { paddingLeft: 2 }, message.metadata?.permissionDenied ? /* @__PURE__ */ React9.createElement(Text8, { color: "yellow" }, "\u26A0\uFE0F ", message.content) : /* @__PURE__ */ React9.createElement(Text8, null, message.content)), message.metadata?.steps && message.metadata.steps.length > 0 && /* @__PURE__ */ React9.createElement(Box8, { paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true, bold: true }, "\u{1F527} Agent Reasoning Trace (", message.metadata.steps.filter((s) => s.type === "action").length, " ", "tools used, ", message.metadata.steps.length, " total steps)", message.metadata.tokenUsage && ` \u2022 ${message.metadata.tokenUsage.total} tokens`), /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "Step types: ", message.metadata.steps.map((s) => s.type).join(", ")), message.metadata.steps.map((step, idx) => {
|
|
416
928
|
if (step.type === "thought") {
|
|
417
|
-
return /* @__PURE__ */
|
|
929
|
+
return /* @__PURE__ */ React9.createElement(Box8, { key: idx, paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React9.createElement(Text8, { color: "blue" }, "\u{1F4AD} Thought:"), /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, ` ${step.content.slice(0, 200)}${step.content.length > 200 ? "..." : ""}`));
|
|
418
930
|
}
|
|
419
931
|
if (step.type === "action") {
|
|
420
932
|
const toolName = step.metadata?.toolName || "unknown";
|
|
421
933
|
const toolInput = step.metadata?.toolInput;
|
|
422
934
|
const observationStep = message.metadata.steps[idx + 1];
|
|
423
935
|
const result = observationStep?.type === "observation" ? observationStep.content : null;
|
|
424
|
-
return /* @__PURE__ */
|
|
936
|
+
return /* @__PURE__ */ React9.createElement(Box8, { key: idx, paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React9.createElement(Text8, { color: "yellow" }, "\u{1F527} Action: ", toolName), toolInput && /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, ` Input: ${typeof toolInput === "string" ? toolInput : JSON.stringify(toolInput).slice(0, 100)}`), result && /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, ` Result: ${typeof result === "string" ? result.slice(0, 200) : JSON.stringify(result).slice(0, 200)}${(typeof result === "string" ? result.length : JSON.stringify(result).length) > 200 ? "..." : ""}`));
|
|
425
937
|
}
|
|
426
938
|
return null;
|
|
427
|
-
}).filter(Boolean)), message.metadata?.tokenUsage && (!message.metadata.steps || message.metadata.steps.length === 0) && /* @__PURE__ */
|
|
939
|
+
}).filter(Boolean)), message.metadata?.tokenUsage && (!message.metadata.steps || message.metadata.steps.length === 0) && /* @__PURE__ */ React9.createElement(Box8, { paddingLeft: 2 }, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, `${message.metadata.tokenUsage.total} tokens`)));
|
|
428
940
|
}
|
|
429
941
|
|
|
430
942
|
// src/components/MessageList.tsx
|
|
431
|
-
import
|
|
432
|
-
import { Box as
|
|
943
|
+
import React10 from "react";
|
|
944
|
+
import { Box as Box9, Text as Text9 } from "ink";
|
|
433
945
|
function stripThinkingTags(content) {
|
|
434
946
|
return content.replace(/<think>[\s\S]*?<\/think>/g, "").trim();
|
|
435
947
|
}
|
|
436
|
-
var MessageList =
|
|
948
|
+
var MessageList = React10.memo(
|
|
437
949
|
function MessageList2({ messages }) {
|
|
438
950
|
if (messages.length === 0) {
|
|
439
|
-
return /* @__PURE__ */
|
|
951
|
+
return /* @__PURE__ */ React10.createElement(Box9, { paddingY: 1 }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "No messages yet. Type a message to start!"));
|
|
440
952
|
}
|
|
441
|
-
return /* @__PURE__ */
|
|
953
|
+
return /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column", gap: 1, paddingY: 1 }, messages.map((message, index) => /* @__PURE__ */ React10.createElement(Box9, { key: index, flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Text9, { bold: true, color: message.role === "user" ? "cyan" : "green" }, message.role === "user" ? "\u{1F464} You" : "\u{1F916} Assistant"), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " \u2022 ", new Date(message.timestamp).toLocaleTimeString())), /* @__PURE__ */ React10.createElement(Box9, { paddingLeft: 2 }, message.metadata?.permissionDenied ? /* @__PURE__ */ React10.createElement(Text9, { color: "yellow" }, "\u26A0\uFE0F ", stripThinkingTags(message.content)) : /* @__PURE__ */ React10.createElement(Text9, null, stripThinkingTags(message.content))), message.metadata?.steps && message.metadata.steps.length > 0 && /* @__PURE__ */ React10.createElement(Box9, { paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true, bold: true }, "\u{1F527} Agent Reasoning Trace (", message.metadata.steps.filter((s) => s.type === "action").length, " tools used,", " ", message.metadata.steps.length, " total steps)", message.metadata.tokenUsage && ` \u2022 ${message.metadata.tokenUsage.total} tokens`), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "Step types: ", message.metadata.steps.map((s) => s.type).join(", ")), message.metadata.steps.map((step, idx) => {
|
|
442
954
|
if (step.type === "thought") {
|
|
443
|
-
return /* @__PURE__ */
|
|
444
|
-
|
|
955
|
+
return /* @__PURE__ */ React10.createElement(Box9, { key: idx, paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { color: "blue" }, "\u{1F4AD} Thought:"), /* @__PURE__ */ React10.createElement(
|
|
956
|
+
Text9,
|
|
445
957
|
{
|
|
446
958
|
dimColor: true
|
|
447
959
|
},
|
|
@@ -453,10 +965,10 @@ var MessageList = React8.memo(
|
|
|
453
965
|
const toolInput = step.metadata?.toolInput;
|
|
454
966
|
const observationStep = message.metadata.steps[idx + 1];
|
|
455
967
|
const result = observationStep?.type === "observation" ? observationStep.content : null;
|
|
456
|
-
return /* @__PURE__ */
|
|
968
|
+
return /* @__PURE__ */ React10.createElement(Box9, { key: idx, paddingLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text9, { color: "yellow" }, "\u{1F527} Action: ", toolName), toolInput && /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, ` Input: ${typeof toolInput === "string" ? toolInput : JSON.stringify(toolInput).slice(0, 100)}`), result && /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, ` Result: ${typeof result === "string" ? result.slice(0, 200) : JSON.stringify(result).slice(0, 200)}${(typeof result === "string" ? result.length : JSON.stringify(result).length) > 200 ? "..." : ""}`));
|
|
457
969
|
}
|
|
458
970
|
return null;
|
|
459
|
-
}).filter(Boolean)), message.metadata?.tokenUsage && (!message.metadata.steps || message.metadata.steps.length === 0) && /* @__PURE__ */
|
|
971
|
+
}).filter(Boolean)), message.metadata?.tokenUsage && (!message.metadata.steps || message.metadata.steps.length === 0) && /* @__PURE__ */ React10.createElement(Box9, { paddingLeft: 2 }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, `${message.metadata.tokenUsage.total} tokens`)))));
|
|
460
972
|
},
|
|
461
973
|
(prevProps, nextProps) => {
|
|
462
974
|
if (prevProps.messages.length !== nextProps.messages.length) {
|
|
@@ -467,8 +979,8 @@ var MessageList = React8.memo(
|
|
|
467
979
|
);
|
|
468
980
|
|
|
469
981
|
// src/components/LoginFlow.tsx
|
|
470
|
-
import
|
|
471
|
-
import { Box as
|
|
982
|
+
import React11, { useState as useState3, useEffect as useEffect2 } from "react";
|
|
983
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
472
984
|
import Spinner2 from "ink-spinner";
|
|
473
985
|
import jwt from "jsonwebtoken";
|
|
474
986
|
import open from "open";
|
|
@@ -578,16 +1090,16 @@ var OAuthClient = class {
|
|
|
578
1090
|
* Sleep for specified milliseconds
|
|
579
1091
|
*/
|
|
580
1092
|
sleep(ms) {
|
|
581
|
-
return new Promise((
|
|
1093
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
582
1094
|
}
|
|
583
1095
|
};
|
|
584
1096
|
|
|
585
1097
|
// src/components/LoginFlow.tsx
|
|
586
1098
|
function LoginFlow({ apiUrl = "http://localhost:3000", configStore, onSuccess, onError }) {
|
|
587
|
-
const [status, setStatus] =
|
|
588
|
-
const [deviceFlow, setDeviceFlow] =
|
|
589
|
-
const [statusMessage, setStatusMessage] =
|
|
590
|
-
const [error, setError] =
|
|
1099
|
+
const [status, setStatus] = useState3("initiating");
|
|
1100
|
+
const [deviceFlow, setDeviceFlow] = useState3(null);
|
|
1101
|
+
const [statusMessage, setStatusMessage] = useState3("Initiating device authorization...");
|
|
1102
|
+
const [error, setError] = useState3(null);
|
|
591
1103
|
useEffect2(() => {
|
|
592
1104
|
const runLoginFlow = async () => {
|
|
593
1105
|
const oauth = new OAuthClient(apiUrl);
|
|
@@ -631,31 +1143,31 @@ function LoginFlow({ apiUrl = "http://localhost:3000", configStore, onSuccess, o
|
|
|
631
1143
|
}
|
|
632
1144
|
}, [deviceFlow, status]);
|
|
633
1145
|
if (status === "initiating") {
|
|
634
|
-
return /* @__PURE__ */
|
|
1146
|
+
return /* @__PURE__ */ React11.createElement(Box10, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React11.createElement(Box10, null, /* @__PURE__ */ React11.createElement(Text10, { color: "cyan" }, /* @__PURE__ */ React11.createElement(Spinner2, { type: "dots" }), " Initiating device authorization...")));
|
|
635
1147
|
}
|
|
636
1148
|
if (status === "error") {
|
|
637
|
-
return /* @__PURE__ */
|
|
1149
|
+
return /* @__PURE__ */ React11.createElement(Box10, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React11.createElement(Box10, { marginBottom: 1 }, /* @__PURE__ */ React11.createElement(Text10, { color: "red", bold: true }, "\u2716 Authentication Failed")), /* @__PURE__ */ React11.createElement(Box10, null, /* @__PURE__ */ React11.createElement(Text10, { color: "red" }, error)));
|
|
638
1150
|
}
|
|
639
1151
|
if (status === "success") {
|
|
640
|
-
return /* @__PURE__ */
|
|
1152
|
+
return /* @__PURE__ */ React11.createElement(Box10, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React11.createElement(Box10, { marginBottom: 1 }, /* @__PURE__ */ React11.createElement(Text10, { color: "green", bold: true }, "\u2714 Successfully authenticated!")), /* @__PURE__ */ React11.createElement(Box10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "You can now use B4M CLI with your account.")));
|
|
641
1153
|
}
|
|
642
|
-
return /* @__PURE__ */
|
|
1154
|
+
return /* @__PURE__ */ React11.createElement(Box10, { flexDirection: "column", padding: 1, borderStyle: "round", borderColor: "cyan" }, /* @__PURE__ */ React11.createElement(Box10, { marginBottom: 1 }, /* @__PURE__ */ React11.createElement(Text10, { color: "cyan", bold: true }, "\u{1F510} Device Authorization")), /* @__PURE__ */ React11.createElement(Box10, { marginBottom: 1 }, /* @__PURE__ */ React11.createElement(Text10, null, "Opening browser automatically... If it doesn't open, please visit:")), /* @__PURE__ */ React11.createElement(Box10, { marginBottom: 1, paddingLeft: 2 }, /* @__PURE__ */ React11.createElement(Text10, { color: "blue", bold: true }, deviceFlow?.verification_uri)), /* @__PURE__ */ React11.createElement(Box10, { marginBottom: 1 }, /* @__PURE__ */ React11.createElement(Text10, null, "And enter this code when prompted:")), /* @__PURE__ */ React11.createElement(Box10, { marginBottom: 1, paddingLeft: 2 }, /* @__PURE__ */ React11.createElement(Text10, { color: "yellow", bold: true }, deviceFlow?.user_code)), /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React11.createElement(Text10, { color: "cyan" }, /* @__PURE__ */ React11.createElement(Spinner2, { type: "dots" }), " ", statusMessage)), /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "Expires in ", deviceFlow ? Math.floor(deviceFlow.expires_in / 60) : 0, " minutes")));
|
|
643
1155
|
}
|
|
644
1156
|
|
|
645
1157
|
// src/storage/SessionStore.ts
|
|
646
|
-
import { promises as
|
|
647
|
-
import
|
|
1158
|
+
import { promises as fs3 } from "fs";
|
|
1159
|
+
import path3 from "path";
|
|
648
1160
|
import { homedir } from "os";
|
|
649
1161
|
var SessionStore = class {
|
|
650
1162
|
constructor(basePath) {
|
|
651
|
-
this.basePath = basePath ||
|
|
1163
|
+
this.basePath = basePath || path3.join(homedir(), ".bike4mind", "sessions");
|
|
652
1164
|
}
|
|
653
1165
|
/**
|
|
654
1166
|
* Initialize storage directory
|
|
655
1167
|
*/
|
|
656
1168
|
async init() {
|
|
657
1169
|
try {
|
|
658
|
-
await
|
|
1170
|
+
await fs3.mkdir(this.basePath, { recursive: true });
|
|
659
1171
|
} catch (error) {
|
|
660
1172
|
console.error("Failed to initialize session storage:", error);
|
|
661
1173
|
throw error;
|
|
@@ -666,9 +1178,9 @@ var SessionStore = class {
|
|
|
666
1178
|
*/
|
|
667
1179
|
async save(session) {
|
|
668
1180
|
await this.init();
|
|
669
|
-
const filePath =
|
|
1181
|
+
const filePath = path3.join(this.basePath, `${session.id}.json`);
|
|
670
1182
|
try {
|
|
671
|
-
await
|
|
1183
|
+
await fs3.writeFile(filePath, JSON.stringify(session, null, 2), "utf-8");
|
|
672
1184
|
} catch (error) {
|
|
673
1185
|
console.error("Failed to save session:", error);
|
|
674
1186
|
throw error;
|
|
@@ -678,9 +1190,9 @@ var SessionStore = class {
|
|
|
678
1190
|
* Load a session from disk by ID
|
|
679
1191
|
*/
|
|
680
1192
|
async load(id) {
|
|
681
|
-
const filePath =
|
|
1193
|
+
const filePath = path3.join(this.basePath, `${id}.json`);
|
|
682
1194
|
try {
|
|
683
|
-
const data = await
|
|
1195
|
+
const data = await fs3.readFile(filePath, "utf-8");
|
|
684
1196
|
return JSON.parse(data);
|
|
685
1197
|
} catch (error) {
|
|
686
1198
|
if (error.code === "ENOENT") {
|
|
@@ -704,12 +1216,12 @@ var SessionStore = class {
|
|
|
704
1216
|
async list() {
|
|
705
1217
|
await this.init();
|
|
706
1218
|
try {
|
|
707
|
-
const files = await
|
|
1219
|
+
const files = await fs3.readdir(this.basePath);
|
|
708
1220
|
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
709
1221
|
const sessions = await Promise.all(
|
|
710
1222
|
jsonFiles.map(async (file) => {
|
|
711
|
-
const filePath =
|
|
712
|
-
const data = await
|
|
1223
|
+
const filePath = path3.join(this.basePath, file);
|
|
1224
|
+
const data = await fs3.readFile(filePath, "utf-8");
|
|
713
1225
|
return JSON.parse(data);
|
|
714
1226
|
})
|
|
715
1227
|
);
|
|
@@ -723,9 +1235,9 @@ var SessionStore = class {
|
|
|
723
1235
|
* Delete a session
|
|
724
1236
|
*/
|
|
725
1237
|
async delete(id) {
|
|
726
|
-
const filePath =
|
|
1238
|
+
const filePath = path3.join(this.basePath, `${id}.json`);
|
|
727
1239
|
try {
|
|
728
|
-
await
|
|
1240
|
+
await fs3.unlink(filePath);
|
|
729
1241
|
return true;
|
|
730
1242
|
} catch (error) {
|
|
731
1243
|
if (error.code === "ENOENT") {
|
|
@@ -761,8 +1273,8 @@ var SessionStore = class {
|
|
|
761
1273
|
};
|
|
762
1274
|
|
|
763
1275
|
// src/storage/ConfigStore.ts
|
|
764
|
-
import { promises as
|
|
765
|
-
import
|
|
1276
|
+
import { promises as fs4 } from "fs";
|
|
1277
|
+
import path4 from "path";
|
|
766
1278
|
import { homedir as homedir2 } from "os";
|
|
767
1279
|
import { v4 as uuidv4 } from "uuid";
|
|
768
1280
|
import { z } from "zod";
|
|
@@ -836,15 +1348,15 @@ var DEFAULT_CONFIG = {
|
|
|
836
1348
|
var ConfigStore = class {
|
|
837
1349
|
constructor(configPath) {
|
|
838
1350
|
this.config = null;
|
|
839
|
-
this.configPath = configPath ||
|
|
1351
|
+
this.configPath = configPath || path4.join(homedir2(), ".bike4mind", "config.json");
|
|
840
1352
|
}
|
|
841
1353
|
/**
|
|
842
1354
|
* Initialize config directory
|
|
843
1355
|
*/
|
|
844
1356
|
async init() {
|
|
845
|
-
const dir =
|
|
1357
|
+
const dir = path4.dirname(this.configPath);
|
|
846
1358
|
try {
|
|
847
|
-
await
|
|
1359
|
+
await fs4.mkdir(dir, { recursive: true });
|
|
848
1360
|
} catch (error) {
|
|
849
1361
|
console.error("Failed to initialize config directory:", error);
|
|
850
1362
|
throw error;
|
|
@@ -859,15 +1371,15 @@ var ConfigStore = class {
|
|
|
859
1371
|
}
|
|
860
1372
|
try {
|
|
861
1373
|
try {
|
|
862
|
-
const stats = await
|
|
1374
|
+
const stats = await fs4.stat(this.configPath);
|
|
863
1375
|
const mode = stats.mode & 511;
|
|
864
1376
|
if (mode !== 384) {
|
|
865
1377
|
console.warn(`\u26A0\uFE0F Config file has insecure permissions (${mode.toString(8)}). Setting to 0600...`);
|
|
866
|
-
await
|
|
1378
|
+
await fs4.chmod(this.configPath, 384);
|
|
867
1379
|
}
|
|
868
1380
|
} catch (statError) {
|
|
869
1381
|
}
|
|
870
|
-
const data = await
|
|
1382
|
+
const data = await fs4.readFile(this.configPath, "utf-8");
|
|
871
1383
|
const rawConfig = JSON.parse(data);
|
|
872
1384
|
if (rawConfig.apiConfig && "environment" in rawConfig.apiConfig) {
|
|
873
1385
|
const oldApiConfig = rawConfig.apiConfig;
|
|
@@ -940,8 +1452,8 @@ var ConfigStore = class {
|
|
|
940
1452
|
throw new Error("No configuration to save");
|
|
941
1453
|
}
|
|
942
1454
|
try {
|
|
943
|
-
await
|
|
944
|
-
await
|
|
1455
|
+
await fs4.writeFile(this.configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
1456
|
+
await fs4.chmod(this.configPath, 384);
|
|
945
1457
|
} catch (error) {
|
|
946
1458
|
console.error("Failed to save config:", error);
|
|
947
1459
|
throw error;
|
|
@@ -1088,22 +1600,22 @@ var ConfigStore = class {
|
|
|
1088
1600
|
};
|
|
1089
1601
|
|
|
1090
1602
|
// src/storage/CommandHistoryStore.ts
|
|
1091
|
-
import { promises as
|
|
1092
|
-
import
|
|
1603
|
+
import { promises as fs5 } from "fs";
|
|
1604
|
+
import path5 from "path";
|
|
1093
1605
|
import { homedir as homedir3 } from "os";
|
|
1094
1606
|
var MAX_HISTORY_ENTRIES = 1e3;
|
|
1095
1607
|
var CommandHistoryStore = class {
|
|
1096
1608
|
constructor(historyPath) {
|
|
1097
1609
|
this.history = null;
|
|
1098
|
-
this.historyPath = historyPath ||
|
|
1610
|
+
this.historyPath = historyPath || path5.join(homedir3(), ".bike4mind", "history.jsonl");
|
|
1099
1611
|
}
|
|
1100
1612
|
/**
|
|
1101
1613
|
* Initialize history directory
|
|
1102
1614
|
*/
|
|
1103
1615
|
async init() {
|
|
1104
|
-
const dir =
|
|
1616
|
+
const dir = path5.dirname(this.historyPath);
|
|
1105
1617
|
try {
|
|
1106
|
-
await
|
|
1618
|
+
await fs5.mkdir(dir, { recursive: true });
|
|
1107
1619
|
} catch (error) {
|
|
1108
1620
|
console.error("Failed to initialize history directory:", error);
|
|
1109
1621
|
throw error;
|
|
@@ -1118,7 +1630,7 @@ var CommandHistoryStore = class {
|
|
|
1118
1630
|
return this.history;
|
|
1119
1631
|
}
|
|
1120
1632
|
try {
|
|
1121
|
-
const data = await
|
|
1633
|
+
const data = await fs5.readFile(this.historyPath, "utf-8");
|
|
1122
1634
|
const lines = data.trim().split("\n").filter((line) => line.length > 0);
|
|
1123
1635
|
const entries = lines.map((line) => {
|
|
1124
1636
|
try {
|
|
@@ -1155,7 +1667,7 @@ var CommandHistoryStore = class {
|
|
|
1155
1667
|
};
|
|
1156
1668
|
await this.init();
|
|
1157
1669
|
const line = JSON.stringify(entry) + "\n";
|
|
1158
|
-
await
|
|
1670
|
+
await fs5.appendFile(this.historyPath, line, "utf-8");
|
|
1159
1671
|
this.history = [command, ...this.history || []];
|
|
1160
1672
|
if (this.history.length > MAX_HISTORY_ENTRIES) {
|
|
1161
1673
|
await this.trim();
|
|
@@ -1178,7 +1690,7 @@ var CommandHistoryStore = class {
|
|
|
1178
1690
|
};
|
|
1179
1691
|
return JSON.stringify(entry);
|
|
1180
1692
|
}).join("\n");
|
|
1181
|
-
await
|
|
1693
|
+
await fs5.writeFile(this.historyPath, lines + "\n", "utf-8");
|
|
1182
1694
|
this.history = trimmedCommands;
|
|
1183
1695
|
}
|
|
1184
1696
|
/**
|
|
@@ -1186,7 +1698,7 @@ var CommandHistoryStore = class {
|
|
|
1186
1698
|
*/
|
|
1187
1699
|
async clear() {
|
|
1188
1700
|
try {
|
|
1189
|
-
await
|
|
1701
|
+
await fs5.unlink(this.historyPath);
|
|
1190
1702
|
} catch (error) {
|
|
1191
1703
|
if (error.code !== "ENOENT") {
|
|
1192
1704
|
throw error;
|
|
@@ -1960,7 +2472,7 @@ import { GoogleGenerativeAI } from "@google/generative-ai";
|
|
|
1960
2472
|
import OpenAI2 from "openai";
|
|
1961
2473
|
|
|
1962
2474
|
// ../../b4m-core/packages/services/dist/src/importHistoryService/index.js
|
|
1963
|
-
import
|
|
2475
|
+
import fs6, { unlinkSync } from "fs";
|
|
1964
2476
|
import yauzl from "yauzl";
|
|
1965
2477
|
import axios3 from "axios";
|
|
1966
2478
|
|
|
@@ -3646,8 +4158,8 @@ async function processAndStoreImages(images, context) {
|
|
|
3646
4158
|
const buffer = await downloadImage(image);
|
|
3647
4159
|
const fileType = await fileTypeFromBuffer2(buffer);
|
|
3648
4160
|
const filename = `${uuidv45()}.${fileType?.ext}`;
|
|
3649
|
-
const
|
|
3650
|
-
return
|
|
4161
|
+
const path13 = await context.imageGenerateStorage.upload(buffer, filename, {});
|
|
4162
|
+
return path13;
|
|
3651
4163
|
}));
|
|
3652
4164
|
}
|
|
3653
4165
|
async function updateQuestAndReturnMarkdown(storedImageUrls, context) {
|
|
@@ -4831,8 +5343,8 @@ async function processAndStoreImage(imageUrl, context) {
|
|
|
4831
5343
|
const buffer = await downloadImage2(imageUrl);
|
|
4832
5344
|
const fileType = await fileTypeFromBuffer3(buffer);
|
|
4833
5345
|
const filename = `${uuidv46()}.${fileType?.ext}`;
|
|
4834
|
-
const
|
|
4835
|
-
return
|
|
5346
|
+
const path13 = await context.imageGenerateStorage.upload(buffer, filename, {});
|
|
5347
|
+
return path13;
|
|
4836
5348
|
}
|
|
4837
5349
|
async function updateQuestAndReturnMarkdown2(storedImagePath, context) {
|
|
4838
5350
|
await context.onFinish?.("edit_image", storedImagePath);
|
|
@@ -6669,33 +7181,33 @@ var planetVisibilityTool = {
|
|
|
6669
7181
|
};
|
|
6670
7182
|
|
|
6671
7183
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/fileRead/index.js
|
|
6672
|
-
import { promises as
|
|
6673
|
-
import { existsSync, statSync } from "fs";
|
|
6674
|
-
import
|
|
6675
|
-
var
|
|
7184
|
+
import { promises as fs7 } from "fs";
|
|
7185
|
+
import { existsSync as existsSync2, statSync as statSync3 } from "fs";
|
|
7186
|
+
import path6 from "path";
|
|
7187
|
+
var MAX_FILE_SIZE2 = 10 * 1024 * 1024;
|
|
6676
7188
|
async function readFileContent(params) {
|
|
6677
7189
|
const { path: filePath, encoding = "utf-8", maxLines } = params;
|
|
6678
|
-
const normalizedPath =
|
|
6679
|
-
const resolvedPath =
|
|
6680
|
-
const cwd =
|
|
7190
|
+
const normalizedPath = path6.normalize(filePath);
|
|
7191
|
+
const resolvedPath = path6.resolve(process.cwd(), normalizedPath);
|
|
7192
|
+
const cwd = path6.resolve(process.cwd());
|
|
6681
7193
|
if (!resolvedPath.startsWith(cwd)) {
|
|
6682
7194
|
throw new Error(`Access denied: Cannot read files outside of current working directory`);
|
|
6683
7195
|
}
|
|
6684
|
-
if (!
|
|
7196
|
+
if (!existsSync2(resolvedPath)) {
|
|
6685
7197
|
throw new Error(`File not found: ${filePath}`);
|
|
6686
7198
|
}
|
|
6687
|
-
const stats =
|
|
7199
|
+
const stats = statSync3(resolvedPath);
|
|
6688
7200
|
if (stats.isDirectory()) {
|
|
6689
7201
|
throw new Error(`Path is a directory, not a file: ${filePath}`);
|
|
6690
7202
|
}
|
|
6691
|
-
if (stats.size >
|
|
6692
|
-
throw new Error(`File too large: ${(stats.size / 1024 / 1024).toFixed(2)}MB (max ${
|
|
7203
|
+
if (stats.size > MAX_FILE_SIZE2) {
|
|
7204
|
+
throw new Error(`File too large: ${(stats.size / 1024 / 1024).toFixed(2)}MB (max ${MAX_FILE_SIZE2 / 1024 / 1024}MB)`);
|
|
6693
7205
|
}
|
|
6694
7206
|
const isBinary = await checkIfBinary(resolvedPath);
|
|
6695
7207
|
if (isBinary && encoding === "utf-8") {
|
|
6696
7208
|
throw new Error(`File appears to be binary. Use encoding 'base64' to read binary files, or specify a different encoding.`);
|
|
6697
7209
|
}
|
|
6698
|
-
const content = await
|
|
7210
|
+
const content = await fs7.readFile(resolvedPath, encoding);
|
|
6699
7211
|
if (maxLines && typeof content === "string") {
|
|
6700
7212
|
const lines = content.split("\n");
|
|
6701
7213
|
if (lines.length > maxLines) {
|
|
@@ -6710,7 +7222,7 @@ ${content}`;
|
|
|
6710
7222
|
}
|
|
6711
7223
|
async function checkIfBinary(filePath) {
|
|
6712
7224
|
const buffer = Buffer.alloc(8192);
|
|
6713
|
-
const fd = await
|
|
7225
|
+
const fd = await fs7.open(filePath, "r");
|
|
6714
7226
|
try {
|
|
6715
7227
|
const { bytesRead } = await fd.read(buffer, 0, 8192, 0);
|
|
6716
7228
|
const chunk = buffer.slice(0, bytesRead);
|
|
@@ -6727,7 +7239,7 @@ var fileReadTool = {
|
|
|
6727
7239
|
context.logger.info("\u{1F4C4} FileRead: Reading file", { path: params.path });
|
|
6728
7240
|
try {
|
|
6729
7241
|
const content = await readFileContent(params);
|
|
6730
|
-
const stats =
|
|
7242
|
+
const stats = statSync3(path6.resolve(process.cwd(), path6.normalize(params.path)));
|
|
6731
7243
|
context.logger.info("\u2705 FileRead: Success", {
|
|
6732
7244
|
path: params.path,
|
|
6733
7245
|
size: stats.size,
|
|
@@ -6766,25 +7278,25 @@ var fileReadTool = {
|
|
|
6766
7278
|
};
|
|
6767
7279
|
|
|
6768
7280
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/createFile/index.js
|
|
6769
|
-
import { promises as
|
|
6770
|
-
import { existsSync as
|
|
6771
|
-
import
|
|
7281
|
+
import { promises as fs8 } from "fs";
|
|
7282
|
+
import { existsSync as existsSync3 } from "fs";
|
|
7283
|
+
import path7 from "path";
|
|
6772
7284
|
async function createFile(params) {
|
|
6773
7285
|
const { path: filePath, content, createDirectories = true } = params;
|
|
6774
|
-
const normalizedPath =
|
|
6775
|
-
const resolvedPath =
|
|
6776
|
-
const cwd =
|
|
7286
|
+
const normalizedPath = path7.normalize(filePath);
|
|
7287
|
+
const resolvedPath = path7.resolve(process.cwd(), normalizedPath);
|
|
7288
|
+
const cwd = path7.resolve(process.cwd());
|
|
6777
7289
|
if (!resolvedPath.startsWith(cwd)) {
|
|
6778
7290
|
throw new Error(`Access denied: Cannot create files outside of current working directory`);
|
|
6779
7291
|
}
|
|
6780
|
-
const fileExists =
|
|
7292
|
+
const fileExists = existsSync3(resolvedPath);
|
|
6781
7293
|
const action = fileExists ? "overwritten" : "created";
|
|
6782
7294
|
if (createDirectories) {
|
|
6783
|
-
const dir =
|
|
6784
|
-
await
|
|
7295
|
+
const dir = path7.dirname(resolvedPath);
|
|
7296
|
+
await fs8.mkdir(dir, { recursive: true });
|
|
6785
7297
|
}
|
|
6786
|
-
await
|
|
6787
|
-
const stats = await
|
|
7298
|
+
await fs8.writeFile(resolvedPath, content, "utf-8");
|
|
7299
|
+
const stats = await fs8.stat(resolvedPath);
|
|
6788
7300
|
const lines = content.split("\n").length;
|
|
6789
7301
|
return `File ${action} successfully: ${filePath}
|
|
6790
7302
|
Size: ${stats.size} bytes
|
|
@@ -6795,7 +7307,7 @@ var createFileTool = {
|
|
|
6795
7307
|
implementation: (context) => ({
|
|
6796
7308
|
toolFn: async (value) => {
|
|
6797
7309
|
const params = value;
|
|
6798
|
-
const fileExists =
|
|
7310
|
+
const fileExists = existsSync3(path7.resolve(process.cwd(), path7.normalize(params.path)));
|
|
6799
7311
|
context.logger.info(`\u{1F4DD} CreateFile: ${fileExists ? "Overwriting" : "Creating"} file`, {
|
|
6800
7312
|
path: params.path,
|
|
6801
7313
|
size: params.content.length
|
|
@@ -6836,8 +7348,8 @@ var createFileTool = {
|
|
|
6836
7348
|
|
|
6837
7349
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/globFiles/index.js
|
|
6838
7350
|
import { readdir } from "fs/promises";
|
|
6839
|
-
import { statSync as
|
|
6840
|
-
import
|
|
7351
|
+
import { statSync as statSync4 } from "fs";
|
|
7352
|
+
import path8 from "path";
|
|
6841
7353
|
function matchGlob(filePath, pattern) {
|
|
6842
7354
|
let regexPattern = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, ".").replace(/\{([^}]+)\}/g, (_, options) => `(${options.split(",").join("|")})`).replace(/\[([^\]]+)\]/g, "[$1]");
|
|
6843
7355
|
regexPattern = regexPattern.replace(/\{\{GLOBSTAR\}\}\//g, "(.*/)?").replace(/\/\{\{GLOBSTAR\}\}/g, "(/.*)?").replace(/\{\{GLOBSTAR\}\}/g, ".*");
|
|
@@ -6855,8 +7367,8 @@ async function walkDir(dir, pattern, includeHidden, maxResults, basePath = "") {
|
|
|
6855
7367
|
if (!includeHidden && entry.name.startsWith(".")) {
|
|
6856
7368
|
continue;
|
|
6857
7369
|
}
|
|
6858
|
-
const relativePath = basePath ?
|
|
6859
|
-
const fullPath =
|
|
7370
|
+
const relativePath = basePath ? path8.join(basePath, entry.name) : entry.name;
|
|
7371
|
+
const fullPath = path8.join(dir, entry.name);
|
|
6860
7372
|
if (matchGlob(relativePath, pattern)) {
|
|
6861
7373
|
matches.push(relativePath);
|
|
6862
7374
|
}
|
|
@@ -6872,7 +7384,7 @@ async function walkDir(dir, pattern, includeHidden, maxResults, basePath = "") {
|
|
|
6872
7384
|
async function listFiles(params) {
|
|
6873
7385
|
const { pattern, cwd: relativeCwd, includeHidden = false, maxResults = 1e3 } = params;
|
|
6874
7386
|
const baseCwd = process.cwd();
|
|
6875
|
-
const targetCwd = relativeCwd ?
|
|
7387
|
+
const targetCwd = relativeCwd ? path8.resolve(baseCwd, path8.normalize(relativeCwd)) : baseCwd;
|
|
6876
7388
|
if (!targetCwd.startsWith(baseCwd)) {
|
|
6877
7389
|
throw new Error(`Access denied: Cannot list files outside of current working directory`);
|
|
6878
7390
|
}
|
|
@@ -6883,8 +7395,8 @@ async function listFiles(params) {
|
|
|
6883
7395
|
const filesInfo = [];
|
|
6884
7396
|
for (const match of matches) {
|
|
6885
7397
|
try {
|
|
6886
|
-
const fullPath =
|
|
6887
|
-
const stats =
|
|
7398
|
+
const fullPath = path8.join(targetCwd, match);
|
|
7399
|
+
const stats = statSync4(fullPath);
|
|
6888
7400
|
filesInfo.push({
|
|
6889
7401
|
path: match,
|
|
6890
7402
|
size: stats.size,
|
|
@@ -6965,9 +7477,9 @@ var globFilesTool = {
|
|
|
6965
7477
|
|
|
6966
7478
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/grepSearch/index.js
|
|
6967
7479
|
import { readdir as readdir2 } from "fs/promises";
|
|
6968
|
-
import { readFileSync, statSync as
|
|
6969
|
-
import
|
|
6970
|
-
var
|
|
7480
|
+
import { readFileSync as readFileSync2, statSync as statSync5 } from "fs";
|
|
7481
|
+
import path9 from "path";
|
|
7482
|
+
var MAX_FILE_SIZE3 = 10 * 1024 * 1024;
|
|
6971
7483
|
function matchGlob2(filePath, pattern) {
|
|
6972
7484
|
const regexPattern = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*").replace(/\?/g, ".").replace(/\{([^}]+)\}/g, (_, options) => `(${options.split(",").join("|")})`).replace(/\[([^\]]+)\]/g, "[$1]");
|
|
6973
7485
|
const regex = new RegExp(`^${regexPattern}$`);
|
|
@@ -6984,8 +7496,8 @@ async function walkDir2(dir, pattern, maxResults, basePath = "") {
|
|
|
6984
7496
|
if (entry.name.startsWith(".")) {
|
|
6985
7497
|
continue;
|
|
6986
7498
|
}
|
|
6987
|
-
const relativePath = basePath ?
|
|
6988
|
-
const fullPath =
|
|
7499
|
+
const relativePath = basePath ? path9.join(basePath, entry.name) : entry.name;
|
|
7500
|
+
const fullPath = path9.join(dir, entry.name);
|
|
6989
7501
|
if (matchGlob2(relativePath, pattern)) {
|
|
6990
7502
|
matches.push(relativePath);
|
|
6991
7503
|
}
|
|
@@ -6998,10 +7510,10 @@ async function walkDir2(dir, pattern, maxResults, basePath = "") {
|
|
|
6998
7510
|
}
|
|
6999
7511
|
return matches;
|
|
7000
7512
|
}
|
|
7001
|
-
async function
|
|
7513
|
+
async function searchFiles2(params) {
|
|
7002
7514
|
const { pattern, filePattern = "**/*", cwd: relativeCwd, caseSensitive = false, maxResults = 100, contextLines = 0 } = params;
|
|
7003
7515
|
const baseCwd = process.cwd();
|
|
7004
|
-
const targetCwd = relativeCwd ?
|
|
7516
|
+
const targetCwd = relativeCwd ? path9.resolve(baseCwd, path9.normalize(relativeCwd)) : baseCwd;
|
|
7005
7517
|
if (!targetCwd.startsWith(baseCwd)) {
|
|
7006
7518
|
throw new Error(`Access denied: Cannot search files outside of current working directory`);
|
|
7007
7519
|
}
|
|
@@ -7015,13 +7527,13 @@ async function searchFiles(params) {
|
|
|
7015
7527
|
const filesToSearch = await walkDir2(targetCwd, filePattern, 1e4);
|
|
7016
7528
|
const filteredFiles = [];
|
|
7017
7529
|
for (const match of filesToSearch) {
|
|
7018
|
-
const fullPath =
|
|
7530
|
+
const fullPath = path9.join(targetCwd, match);
|
|
7019
7531
|
try {
|
|
7020
|
-
const stats =
|
|
7021
|
-
if (stats.isDirectory() || stats.size >
|
|
7532
|
+
const stats = statSync5(fullPath);
|
|
7533
|
+
if (stats.isDirectory() || stats.size > MAX_FILE_SIZE3) {
|
|
7022
7534
|
continue;
|
|
7023
7535
|
}
|
|
7024
|
-
if (
|
|
7536
|
+
if (isBinaryFile2(fullPath)) {
|
|
7025
7537
|
continue;
|
|
7026
7538
|
}
|
|
7027
7539
|
filteredFiles.push(match);
|
|
@@ -7039,9 +7551,9 @@ async function searchFiles(params) {
|
|
|
7039
7551
|
break;
|
|
7040
7552
|
}
|
|
7041
7553
|
filesSearched++;
|
|
7042
|
-
const fullPath =
|
|
7554
|
+
const fullPath = path9.join(targetCwd, file);
|
|
7043
7555
|
try {
|
|
7044
|
-
const content =
|
|
7556
|
+
const content = readFileSync2(fullPath, "utf-8");
|
|
7045
7557
|
const lines = content.split("\n");
|
|
7046
7558
|
for (let i = 0; i < lines.length; i++) {
|
|
7047
7559
|
if (matches.length >= maxResults) {
|
|
@@ -7105,7 +7617,7 @@ ${match.context.after.map((l) => ` ${l}`).join("\n")}`;
|
|
|
7105
7617
|
}).join("\n");
|
|
7106
7618
|
return `${summary}${patternInfo}${cwdInfo}${matchesList}`;
|
|
7107
7619
|
}
|
|
7108
|
-
function
|
|
7620
|
+
function isBinaryFile2(filePath) {
|
|
7109
7621
|
try {
|
|
7110
7622
|
const buffer = Buffer.alloc(8192);
|
|
7111
7623
|
const fd = __require("fs").openSync(filePath, "r");
|
|
@@ -7128,7 +7640,7 @@ var grepSearchTool = {
|
|
|
7128
7640
|
cwd: params.cwd || "."
|
|
7129
7641
|
});
|
|
7130
7642
|
try {
|
|
7131
|
-
const result = await
|
|
7643
|
+
const result = await searchFiles2(params);
|
|
7132
7644
|
context.logger.info("\u2705 GrepSearch: Success");
|
|
7133
7645
|
return result;
|
|
7134
7646
|
} catch (error) {
|
|
@@ -7174,31 +7686,31 @@ var grepSearchTool = {
|
|
|
7174
7686
|
};
|
|
7175
7687
|
|
|
7176
7688
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/deleteFile/index.js
|
|
7177
|
-
import { promises as
|
|
7178
|
-
import { existsSync as
|
|
7179
|
-
import
|
|
7689
|
+
import { promises as fs9 } from "fs";
|
|
7690
|
+
import { existsSync as existsSync4, statSync as statSync6 } from "fs";
|
|
7691
|
+
import path10 from "path";
|
|
7180
7692
|
async function deleteFile(params) {
|
|
7181
7693
|
const { path: filePath, recursive = false } = params;
|
|
7182
|
-
const normalizedPath =
|
|
7183
|
-
const resolvedPath =
|
|
7184
|
-
const cwd =
|
|
7694
|
+
const normalizedPath = path10.normalize(filePath);
|
|
7695
|
+
const resolvedPath = path10.resolve(process.cwd(), normalizedPath);
|
|
7696
|
+
const cwd = path10.resolve(process.cwd());
|
|
7185
7697
|
if (!resolvedPath.startsWith(cwd)) {
|
|
7186
7698
|
throw new Error(`Access denied: Cannot delete files outside of current working directory`);
|
|
7187
7699
|
}
|
|
7188
|
-
if (!
|
|
7700
|
+
if (!existsSync4(resolvedPath)) {
|
|
7189
7701
|
throw new Error(`File or directory not found: ${filePath}`);
|
|
7190
7702
|
}
|
|
7191
|
-
const stats =
|
|
7703
|
+
const stats = statSync6(resolvedPath);
|
|
7192
7704
|
const isDirectory = stats.isDirectory();
|
|
7193
7705
|
const size = stats.size;
|
|
7194
7706
|
if (isDirectory && !recursive) {
|
|
7195
7707
|
throw new Error(`Path is a directory: ${filePath}. Use recursive=true to delete directories and their contents.`);
|
|
7196
7708
|
}
|
|
7197
7709
|
if (isDirectory) {
|
|
7198
|
-
await
|
|
7710
|
+
await fs9.rm(resolvedPath, { recursive: true, force: true });
|
|
7199
7711
|
return `Directory deleted successfully: ${filePath}`;
|
|
7200
7712
|
} else {
|
|
7201
|
-
await
|
|
7713
|
+
await fs9.unlink(resolvedPath);
|
|
7202
7714
|
return `File deleted successfully: ${filePath}
|
|
7203
7715
|
Size: ${size} bytes`;
|
|
7204
7716
|
}
|
|
@@ -7208,8 +7720,8 @@ var deleteFileTool = {
|
|
|
7208
7720
|
implementation: (context) => ({
|
|
7209
7721
|
toolFn: async (value) => {
|
|
7210
7722
|
const params = value;
|
|
7211
|
-
const resolvedPath =
|
|
7212
|
-
const isDirectory =
|
|
7723
|
+
const resolvedPath = path10.resolve(process.cwd(), path10.normalize(params.path));
|
|
7724
|
+
const isDirectory = existsSync4(resolvedPath) && statSync6(resolvedPath).isDirectory();
|
|
7213
7725
|
context.logger.info(`\u{1F5D1}\uFE0F DeleteFile: Deleting ${isDirectory ? "directory" : "file"}`, {
|
|
7214
7726
|
path: params.path,
|
|
7215
7727
|
recursive: params.recursive
|
|
@@ -7499,10 +8011,10 @@ import jwt2 from "jsonwebtoken";
|
|
|
7499
8011
|
// src/utils/diffPreview.ts
|
|
7500
8012
|
import * as Diff from "diff";
|
|
7501
8013
|
import { readFile } from "fs/promises";
|
|
7502
|
-
import { existsSync as
|
|
8014
|
+
import { existsSync as existsSync5 } from "fs";
|
|
7503
8015
|
async function generateFileDiffPreview(args) {
|
|
7504
8016
|
try {
|
|
7505
|
-
if (!
|
|
8017
|
+
if (!existsSync5(args.path)) {
|
|
7506
8018
|
const lines2 = args.content.split("\n");
|
|
7507
8019
|
const preview = lines2.slice(0, 20).join("\n");
|
|
7508
8020
|
const hasMore = lines2.length > 20;
|
|
@@ -7532,10 +8044,10 @@ ${preview}${hasMore ? `
|
|
|
7532
8044
|
}
|
|
7533
8045
|
async function generateFileDeletePreview(args) {
|
|
7534
8046
|
try {
|
|
7535
|
-
if (!
|
|
8047
|
+
if (!existsSync5(args.path)) {
|
|
7536
8048
|
return `[File does not exist: ${args.path}]`;
|
|
7537
8049
|
}
|
|
7538
|
-
const stats = await import("fs/promises").then((
|
|
8050
|
+
const stats = await import("fs/promises").then((fs11) => fs11.stat(args.path));
|
|
7539
8051
|
return `[File will be deleted]
|
|
7540
8052
|
|
|
7541
8053
|
Path: ${args.path}
|
|
@@ -7551,21 +8063,21 @@ var NoOpStorage = class extends BaseStorage {
|
|
|
7551
8063
|
async upload(input, destination, options) {
|
|
7552
8064
|
return `/tmp/${destination}`;
|
|
7553
8065
|
}
|
|
7554
|
-
async download(
|
|
8066
|
+
async download(path13) {
|
|
7555
8067
|
throw new Error("Download not supported in CLI");
|
|
7556
8068
|
}
|
|
7557
|
-
async delete(
|
|
8069
|
+
async delete(path13) {
|
|
7558
8070
|
}
|
|
7559
|
-
async getSignedUrl(
|
|
7560
|
-
return `/tmp/${
|
|
8071
|
+
async getSignedUrl(path13) {
|
|
8072
|
+
return `/tmp/${path13}`;
|
|
7561
8073
|
}
|
|
7562
|
-
getPublicUrl(
|
|
7563
|
-
return `/tmp/${
|
|
8074
|
+
getPublicUrl(path13) {
|
|
8075
|
+
return `/tmp/${path13}`;
|
|
7564
8076
|
}
|
|
7565
|
-
async getPreview(
|
|
7566
|
-
return `/tmp/${
|
|
8077
|
+
async getPreview(path13) {
|
|
8078
|
+
return `/tmp/${path13}`;
|
|
7567
8079
|
}
|
|
7568
|
-
async getMetadata(
|
|
8080
|
+
async getMetadata(path13) {
|
|
7569
8081
|
return { size: 0, contentType: "application/octet-stream" };
|
|
7570
8082
|
}
|
|
7571
8083
|
};
|
|
@@ -7891,8 +8403,8 @@ function getEnvironmentName(configApiConfig) {
|
|
|
7891
8403
|
}
|
|
7892
8404
|
|
|
7893
8405
|
// src/utils/Logger.ts
|
|
7894
|
-
import
|
|
7895
|
-
import
|
|
8406
|
+
import fs10 from "fs/promises";
|
|
8407
|
+
import path11 from "path";
|
|
7896
8408
|
import os from "os";
|
|
7897
8409
|
var Logger2 = class _Logger {
|
|
7898
8410
|
constructor() {
|
|
@@ -7915,9 +8427,9 @@ var Logger2 = class _Logger {
|
|
|
7915
8427
|
*/
|
|
7916
8428
|
async initialize(sessionId) {
|
|
7917
8429
|
this.sessionId = sessionId;
|
|
7918
|
-
const debugDir =
|
|
7919
|
-
await
|
|
7920
|
-
this.logFilePath =
|
|
8430
|
+
const debugDir = path11.join(os.homedir(), ".bike4mind", "debug");
|
|
8431
|
+
await fs10.mkdir(debugDir, { recursive: true });
|
|
8432
|
+
this.logFilePath = path11.join(debugDir, `${sessionId}.txt`);
|
|
7921
8433
|
await this.writeToFile("INFO", "=== CLI SESSION START ===");
|
|
7922
8434
|
}
|
|
7923
8435
|
/**
|
|
@@ -7981,7 +8493,7 @@ var Logger2 = class _Logger {
|
|
|
7981
8493
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19);
|
|
7982
8494
|
const logEntry = `[${timestamp}] [${level}] ${message}
|
|
7983
8495
|
`;
|
|
7984
|
-
await
|
|
8496
|
+
await fs10.appendFile(this.logFilePath, logEntry, "utf-8");
|
|
7985
8497
|
} catch (error) {
|
|
7986
8498
|
console.error("File logging failed:", error);
|
|
7987
8499
|
}
|
|
@@ -8127,15 +8639,15 @@ var Logger2 = class _Logger {
|
|
|
8127
8639
|
async cleanupOldLogs() {
|
|
8128
8640
|
if (!this.fileLoggingEnabled) return;
|
|
8129
8641
|
try {
|
|
8130
|
-
const debugDir =
|
|
8131
|
-
const files = await
|
|
8642
|
+
const debugDir = path11.join(os.homedir(), ".bike4mind", "debug");
|
|
8643
|
+
const files = await fs10.readdir(debugDir);
|
|
8132
8644
|
const now = Date.now();
|
|
8133
8645
|
const thirtyDaysAgo = now - 30 * 24 * 60 * 60 * 1e3;
|
|
8134
8646
|
for (const file of files) {
|
|
8135
|
-
const filePath =
|
|
8136
|
-
const stats = await
|
|
8647
|
+
const filePath = path11.join(debugDir, file);
|
|
8648
|
+
const stats = await fs10.stat(filePath);
|
|
8137
8649
|
if (stats.mtime.getTime() < thirtyDaysAgo) {
|
|
8138
|
-
await
|
|
8650
|
+
await fs10.unlink(filePath);
|
|
8139
8651
|
}
|
|
8140
8652
|
}
|
|
8141
8653
|
} catch (error) {
|
|
@@ -8148,8 +8660,8 @@ var logger = Logger2.getInstance();
|
|
|
8148
8660
|
// ../../b4m-core/packages/mcp/dist/src/client.js
|
|
8149
8661
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
8150
8662
|
import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
|
|
8151
|
-
import
|
|
8152
|
-
import { existsSync as
|
|
8663
|
+
import path12 from "path";
|
|
8664
|
+
import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
|
|
8153
8665
|
var MCPClient = class {
|
|
8154
8666
|
// Note: This class handles MCP server communication with repository filtering
|
|
8155
8667
|
mcp;
|
|
@@ -8174,20 +8686,20 @@ var MCPClient = class {
|
|
|
8174
8686
|
const root = process.env.INIT_CWD || process.cwd();
|
|
8175
8687
|
const candidatePaths = [
|
|
8176
8688
|
// When running from SST Lambda with node_modules structure (copyFiles)
|
|
8177
|
-
|
|
8689
|
+
path12.join(root, `node_modules/@bike4mind/mcp/dist/src/${this.serverName}/index.js`),
|
|
8178
8690
|
// When running from SST Lambda deployed environment (/var/task)
|
|
8179
|
-
|
|
8691
|
+
path12.join(root, `b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
|
|
8180
8692
|
// When running from SST Lambda (.sst/artifacts/mcpHandler-dev), navigate to monorepo root (3 levels up)
|
|
8181
|
-
|
|
8693
|
+
path12.join(root, `../../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
|
|
8182
8694
|
// When running from packages/client (Next.js app), navigate to monorepo root (2 levels up)
|
|
8183
|
-
|
|
8695
|
+
path12.join(root, `../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
|
|
8184
8696
|
// Original paths (backward compatibility)
|
|
8185
|
-
|
|
8186
|
-
|
|
8697
|
+
path12.join(root, `/b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
|
|
8698
|
+
path12.join(root, "core", "mcp", "servers", this.serverName, "dist", "index.js")
|
|
8187
8699
|
];
|
|
8188
|
-
const serverScriptPath = candidatePaths.find((p) =>
|
|
8700
|
+
const serverScriptPath = candidatePaths.find((p) => existsSync6(p));
|
|
8189
8701
|
if (!serverScriptPath) {
|
|
8190
|
-
const getDirectories = (source) =>
|
|
8702
|
+
const getDirectories = (source) => readdirSync3(source, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
|
|
8191
8703
|
console.error(`[MCP] Server script not found. Tried paths:`, candidatePaths);
|
|
8192
8704
|
console.error(`[MCP] Available directories in ${root}:`, getDirectories(root));
|
|
8193
8705
|
throw new Error(`MCP Server script path not found for ${this.serverName}. Root: ${root}, Available dirs: ${getDirectories(root).join(", ")}`);
|
|
@@ -8216,7 +8728,7 @@ var MCPClient = class {
|
|
|
8216
8728
|
}
|
|
8217
8729
|
};
|
|
8218
8730
|
await this.mcp.connect(this.transport);
|
|
8219
|
-
await new Promise((
|
|
8731
|
+
await new Promise((resolve3) => setTimeout(resolve3, 100));
|
|
8220
8732
|
const toolsResult = await this.mcp.listTools();
|
|
8221
8733
|
this.tools = toolsResult.tools.map((tool) => {
|
|
8222
8734
|
return {
|
|
@@ -8399,7 +8911,7 @@ var ServerLlmBackend = class {
|
|
|
8399
8911
|
*/
|
|
8400
8912
|
async complete(model, messages, options, callback) {
|
|
8401
8913
|
logger.debug(`[ServerLlmBackend] Starting complete() with model: ${model}`);
|
|
8402
|
-
return new Promise(async (
|
|
8914
|
+
return new Promise(async (resolve3, reject) => {
|
|
8403
8915
|
try {
|
|
8404
8916
|
logger.debug("[ServerLlmBackend] Making streaming request...");
|
|
8405
8917
|
const response = await this.makeStreamingRequest(model, messages, options);
|
|
@@ -8429,15 +8941,15 @@ var ServerLlmBackend = class {
|
|
|
8429
8941
|
reject(err);
|
|
8430
8942
|
}).then(() => {
|
|
8431
8943
|
logger.debug("[ServerLlmBackend] Callback completed, resolving");
|
|
8432
|
-
|
|
8944
|
+
resolve3();
|
|
8433
8945
|
});
|
|
8434
8946
|
} else if (cleanedText) {
|
|
8435
8947
|
callback([cleanedText], lastUsageInfo).catch((err) => {
|
|
8436
8948
|
logger.error("[ServerLlmBackend] Callback error:", err);
|
|
8437
8949
|
reject(err);
|
|
8438
|
-
}).then(() =>
|
|
8950
|
+
}).then(() => resolve3());
|
|
8439
8951
|
} else {
|
|
8440
|
-
|
|
8952
|
+
resolve3();
|
|
8441
8953
|
}
|
|
8442
8954
|
return;
|
|
8443
8955
|
}
|
|
@@ -8497,7 +9009,7 @@ var ServerLlmBackend = class {
|
|
|
8497
9009
|
response.data.on("end", () => {
|
|
8498
9010
|
if (!receivedDone) {
|
|
8499
9011
|
logger.debug("[ServerLlmBackend] Stream ended without [DONE], resolving anyway");
|
|
8500
|
-
|
|
9012
|
+
resolve3();
|
|
8501
9013
|
} else {
|
|
8502
9014
|
logger.debug("[ServerLlmBackend] Stream ended, [DONE] handler will resolve");
|
|
8503
9015
|
}
|
|
@@ -8761,10 +9273,13 @@ var ApiClient = class {
|
|
|
8761
9273
|
}
|
|
8762
9274
|
};
|
|
8763
9275
|
|
|
9276
|
+
// src/index.tsx
|
|
9277
|
+
import { isAxiosError as isAxiosError2 } from "axios";
|
|
9278
|
+
|
|
8764
9279
|
// package.json
|
|
8765
9280
|
var package_default = {
|
|
8766
9281
|
name: "@bike4mind/cli",
|
|
8767
|
-
version: "0.2.
|
|
9282
|
+
version: "0.2.3-basic-cli-shortcuts.17058+9fe0019f0",
|
|
8768
9283
|
type: "module",
|
|
8769
9284
|
description: "Interactive CLI tool for Bike4Mind with ReAct agents",
|
|
8770
9285
|
license: "UNLICENSED",
|
|
@@ -8775,6 +9290,9 @@ var package_default = {
|
|
|
8775
9290
|
},
|
|
8776
9291
|
homepage: "https://github.com/bike4mind/lumina5#readme",
|
|
8777
9292
|
bugs: "https://github.com/bike4mind/lumina5/issues",
|
|
9293
|
+
publishConfig: {
|
|
9294
|
+
access: "public"
|
|
9295
|
+
},
|
|
8778
9296
|
keywords: [
|
|
8779
9297
|
"cli",
|
|
8780
9298
|
"ai",
|
|
@@ -8795,6 +9313,8 @@ var package_default = {
|
|
|
8795
9313
|
dev: "tsx src/index.tsx",
|
|
8796
9314
|
build: "tsup",
|
|
8797
9315
|
typecheck: "tsc --noEmit",
|
|
9316
|
+
test: "vitest run",
|
|
9317
|
+
"test:watch": "vitest",
|
|
8798
9318
|
start: "node dist/index.js",
|
|
8799
9319
|
prepublishOnly: "pnpm build"
|
|
8800
9320
|
},
|
|
@@ -8861,10 +9381,10 @@ var package_default = {
|
|
|
8861
9381
|
},
|
|
8862
9382
|
devDependencies: {
|
|
8863
9383
|
"@bike4mind/agents": "0.1.0",
|
|
8864
|
-
"@bike4mind/common": "2.
|
|
8865
|
-
"@bike4mind/mcp": "1.19.
|
|
8866
|
-
"@bike4mind/services": "2.
|
|
8867
|
-
"@bike4mind/utils": "2.0.
|
|
9384
|
+
"@bike4mind/common": "2.37.1-basic-cli-shortcuts.17058+9fe0019f0",
|
|
9385
|
+
"@bike4mind/mcp": "1.19.5-basic-cli-shortcuts.17058+9fe0019f0",
|
|
9386
|
+
"@bike4mind/services": "2.31.1-basic-cli-shortcuts.17058+9fe0019f0",
|
|
9387
|
+
"@bike4mind/utils": "2.0.4-basic-cli-shortcuts.17058+9fe0019f0",
|
|
8868
9388
|
"@types/diff": "^5.0.9",
|
|
8869
9389
|
"@types/jsonwebtoken": "^9.0.4",
|
|
8870
9390
|
"@types/node": "^22.9.0",
|
|
@@ -8873,11 +9393,17 @@ var package_default = {
|
|
|
8873
9393
|
"@types/yargs": "^17.0.32",
|
|
8874
9394
|
tsup: "^8.5.1",
|
|
8875
9395
|
tsx: "^4.21.0",
|
|
8876
|
-
typescript: "^5.9.3"
|
|
9396
|
+
typescript: "^5.9.3",
|
|
9397
|
+
vitest: "^3.2.4"
|
|
8877
9398
|
},
|
|
8878
|
-
gitHead: "
|
|
9399
|
+
gitHead: "9fe0019f0bca23b4ffe22a4d9504a0e02054f48d"
|
|
8879
9400
|
};
|
|
8880
9401
|
|
|
9402
|
+
// src/config/constants.ts
|
|
9403
|
+
var USAGE_DAYS = 30;
|
|
9404
|
+
var MODEL_NAME_COLUMN_WIDTH = 18;
|
|
9405
|
+
var USAGE_CACHE_TTL = 5 * 60 * 1e3;
|
|
9406
|
+
|
|
8881
9407
|
// src/index.tsx
|
|
8882
9408
|
process.removeAllListeners("warning");
|
|
8883
9409
|
process.on("warning", (warning) => {
|
|
@@ -8888,9 +9414,10 @@ process.on("warning", (warning) => {
|
|
|
8888
9414
|
});
|
|
8889
9415
|
var exitTimestamp = null;
|
|
8890
9416
|
var EXIT_TIMEOUT_MS = 2e3;
|
|
9417
|
+
var usageCache = null;
|
|
8891
9418
|
function CliApp() {
|
|
8892
9419
|
const { exit } = useApp();
|
|
8893
|
-
const [state, setState] =
|
|
9420
|
+
const [state, setState] = useState4({
|
|
8894
9421
|
session: null,
|
|
8895
9422
|
sessionStore: new SessionStore(),
|
|
8896
9423
|
configStore: new ConfigStore(),
|
|
@@ -8900,9 +9427,9 @@ function CliApp() {
|
|
|
8900
9427
|
permissionManager: null,
|
|
8901
9428
|
permissionPrompt: null
|
|
8902
9429
|
});
|
|
8903
|
-
const [isInitialized, setIsInitialized] =
|
|
8904
|
-
const [initError, setInitError] =
|
|
8905
|
-
const [commandHistory, setCommandHistory] =
|
|
9430
|
+
const [isInitialized, setIsInitialized] = useState4(false);
|
|
9431
|
+
const [initError, setInitError] = useState4(null);
|
|
9432
|
+
const [commandHistory, setCommandHistory] = useState4([]);
|
|
8906
9433
|
const setStoreSession = useCliStore((state2) => state2.setSession);
|
|
8907
9434
|
const setPermissionPrompt = useCliStore((state2) => state2.setPermissionPrompt);
|
|
8908
9435
|
const setExitRequested = useCliStore((state2) => state2.setExitRequested);
|
|
@@ -8927,14 +9454,14 @@ function CliApp() {
|
|
|
8927
9454
|
}
|
|
8928
9455
|
await Promise.race([
|
|
8929
9456
|
Promise.all(cleanupTasks),
|
|
8930
|
-
new Promise((
|
|
9457
|
+
new Promise((resolve3) => setTimeout(resolve3, 500))
|
|
8931
9458
|
// 500ms timeout
|
|
8932
9459
|
]);
|
|
8933
9460
|
setTimeout(() => {
|
|
8934
9461
|
process.exit(0);
|
|
8935
9462
|
}, 100);
|
|
8936
9463
|
}, [state.session, state.sessionStore, state.mcpManager, state.agent]);
|
|
8937
|
-
|
|
9464
|
+
useInput3((input, key) => {
|
|
8938
9465
|
if (key.ctrl && input === "c") {
|
|
8939
9466
|
const now = Date.now();
|
|
8940
9467
|
if (exitTimestamp && now - exitTimestamp < EXIT_TIMEOUT_MS) {
|
|
@@ -9061,9 +9588,9 @@ function CliApp() {
|
|
|
9061
9588
|
};
|
|
9062
9589
|
const permissionManager = new PermissionManager(config.trustedTools || []);
|
|
9063
9590
|
const promptFn = (toolName, args, preview) => {
|
|
9064
|
-
return new Promise((
|
|
9591
|
+
return new Promise((resolve3) => {
|
|
9065
9592
|
const canBeTrusted = permissionManager.canBeTrusted(toolName);
|
|
9066
|
-
const prompt = { toolName, args, preview, canBeTrusted, resolve };
|
|
9593
|
+
const prompt = { toolName, args, preview, canBeTrusted, resolve: resolve3 };
|
|
9067
9594
|
setState((prev) => ({
|
|
9068
9595
|
...prev,
|
|
9069
9596
|
permissionManager,
|
|
@@ -9137,7 +9664,7 @@ Remember: Use context from previous messages to understand follow-up questions.`
|
|
|
9137
9664
|
llm,
|
|
9138
9665
|
model: modelInfo.id,
|
|
9139
9666
|
tools: allTools,
|
|
9140
|
-
maxIterations:
|
|
9667
|
+
maxIterations: 10,
|
|
9141
9668
|
maxTokens: config.preferences.maxTokens,
|
|
9142
9669
|
temperature: config.preferences.temperature,
|
|
9143
9670
|
systemPrompt: cliSystemPrompt
|
|
@@ -9252,6 +9779,7 @@ Available commands:
|
|
|
9252
9779
|
/login - Authenticate with your B4M account
|
|
9253
9780
|
/logout - Clear authentication and sign out
|
|
9254
9781
|
/whoami - Show current authenticated user
|
|
9782
|
+
/usage - Show credit usage and balance
|
|
9255
9783
|
/save <name> - Save current session
|
|
9256
9784
|
/sessions - List saved sessions
|
|
9257
9785
|
/config - Show configuration
|
|
@@ -9422,17 +9950,114 @@ Tool Permissions:
|
|
|
9422
9950
|
console.log(`Expires: ${new Date(authTokens.expiresAt).toLocaleString()}`);
|
|
9423
9951
|
console.log("");
|
|
9424
9952
|
break;
|
|
9953
|
+
case "usage": {
|
|
9954
|
+
const usageAuthTokens = await state.configStore.getAuthTokens();
|
|
9955
|
+
if (!usageAuthTokens || new Date(usageAuthTokens.expiresAt) <= /* @__PURE__ */ new Date()) {
|
|
9956
|
+
console.log("\nNot authenticated. Run /login to authenticate.\n");
|
|
9957
|
+
return;
|
|
9958
|
+
}
|
|
9959
|
+
console.log("\nFetching usage data...");
|
|
9960
|
+
const now = Date.now();
|
|
9961
|
+
let currentCredits;
|
|
9962
|
+
let transactions;
|
|
9963
|
+
if (usageCache && now - usageCache.timestamp < USAGE_CACHE_TTL) {
|
|
9964
|
+
currentCredits = usageCache.data.currentCredits;
|
|
9965
|
+
transactions = usageCache.data.transactions;
|
|
9966
|
+
console.log("(using cached data)\n");
|
|
9967
|
+
} else {
|
|
9968
|
+
try {
|
|
9969
|
+
const config = await state.configStore.get();
|
|
9970
|
+
const apiBaseURL = getApiUrl(config.apiConfig);
|
|
9971
|
+
const apiClient = new ApiClient(apiBaseURL, state.configStore);
|
|
9972
|
+
const [identifyResponse, transactionsResponse] = await Promise.all([
|
|
9973
|
+
apiClient.get("/api/identify"),
|
|
9974
|
+
apiClient.get(`/api/credits/transactions?days=${USAGE_DAYS}`)
|
|
9975
|
+
]);
|
|
9976
|
+
currentCredits = identifyResponse.user.currentCredits;
|
|
9977
|
+
transactions = transactionsResponse || [];
|
|
9978
|
+
usageCache = {
|
|
9979
|
+
data: { currentCredits, transactions },
|
|
9980
|
+
timestamp: now
|
|
9981
|
+
};
|
|
9982
|
+
} catch (error) {
|
|
9983
|
+
if (isAxiosError2(error)) {
|
|
9984
|
+
if (error.response?.status === 401) {
|
|
9985
|
+
console.log("\n\u274C Authentication failed. Run /login to re-authenticate.\n");
|
|
9986
|
+
} else if (error.response?.status === 403) {
|
|
9987
|
+
console.log("\n\u274C Access denied. You may not have permission to view usage data.\n");
|
|
9988
|
+
} else if (error.response?.status === 404) {
|
|
9989
|
+
console.log("\n\u274C Usage endpoint not available on this server.\n");
|
|
9990
|
+
} else if (error.code === "ECONNREFUSED" || error.code === "ENOTFOUND") {
|
|
9991
|
+
console.log("\n\u274C Cannot connect to server. Check your API configuration with /api-info\n");
|
|
9992
|
+
} else {
|
|
9993
|
+
console.log(`
|
|
9994
|
+
\u274C Error fetching usage data: ${error.message}
|
|
9995
|
+
`);
|
|
9996
|
+
}
|
|
9997
|
+
} else if (error instanceof Error) {
|
|
9998
|
+
console.log(`
|
|
9999
|
+
\u274C Error: ${error.message}
|
|
10000
|
+
`);
|
|
10001
|
+
} else {
|
|
10002
|
+
console.log("\n\u274C Unexpected error fetching usage data. Please try again.\n");
|
|
10003
|
+
}
|
|
10004
|
+
break;
|
|
10005
|
+
}
|
|
10006
|
+
}
|
|
10007
|
+
const deductTransactions = transactions.filter(
|
|
10008
|
+
(t) => CREDIT_DEDUCT_TRANSACTION_TYPES.includes(t.type)
|
|
10009
|
+
);
|
|
10010
|
+
const totalCreditsUsed = deductTransactions.reduce((sum2, t) => sum2 + Math.abs(t.credits), 0);
|
|
10011
|
+
const dailyBurnRate = Math.round(totalCreditsUsed / USAGE_DAYS);
|
|
10012
|
+
let daysRemainingStr;
|
|
10013
|
+
if (dailyBurnRate === 0) {
|
|
10014
|
+
daysRemainingStr = "N/A (no usage)";
|
|
10015
|
+
} else {
|
|
10016
|
+
const daysRemaining = Math.floor(currentCredits / dailyBurnRate);
|
|
10017
|
+
daysRemainingStr = `~${daysRemaining.toLocaleString()} days`;
|
|
10018
|
+
}
|
|
10019
|
+
const usageByModel = {};
|
|
10020
|
+
for (const t of deductTransactions) {
|
|
10021
|
+
const modelName = t.metadata?.modelName || ("model" in t ? t.model : null) || "Unknown";
|
|
10022
|
+
usageByModel[modelName] = (usageByModel[modelName] || 0) + Math.abs(t.credits);
|
|
10023
|
+
}
|
|
10024
|
+
const sortedModels = Object.entries(usageByModel).sort((a, b) => b[1] - a[1]);
|
|
10025
|
+
console.log("\n Credit Usage Summary");
|
|
10026
|
+
console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n");
|
|
10027
|
+
console.log(`Credits Balance: ${currentCredits.toLocaleString()}`);
|
|
10028
|
+
console.log(`Daily Burn Rate: ${dailyBurnRate.toLocaleString()} credits/day`);
|
|
10029
|
+
console.log(`Days Remaining: ${daysRemainingStr}`);
|
|
10030
|
+
if (currentCredits === 0) {
|
|
10031
|
+
console.log("\n\u26A0\uFE0F You have no credits remaining.");
|
|
10032
|
+
console.log("\u{1F4A1} Visit bike4mind.io to purchase more credits.\n");
|
|
10033
|
+
break;
|
|
10034
|
+
}
|
|
10035
|
+
if (sortedModels.length > 0) {
|
|
10036
|
+
console.log(`
|
|
10037
|
+
Usage by Model (Last ${USAGE_DAYS} days):`);
|
|
10038
|
+
for (const [model, credits] of sortedModels) {
|
|
10039
|
+
const percentage = totalCreditsUsed > 0 ? Math.round(credits / totalCreditsUsed * 100) : 0;
|
|
10040
|
+
const paddedModel = model.padEnd(MODEL_NAME_COLUMN_WIDTH);
|
|
10041
|
+
console.log(` ${paddedModel}${credits.toLocaleString()} credits (${percentage}%)`);
|
|
10042
|
+
}
|
|
10043
|
+
} else {
|
|
10044
|
+
console.log(`
|
|
10045
|
+
No usage data available for the last ${USAGE_DAYS} days.`);
|
|
10046
|
+
}
|
|
10047
|
+
console.log("");
|
|
10048
|
+
break;
|
|
10049
|
+
}
|
|
9425
10050
|
default:
|
|
9426
10051
|
console.log(`Unknown command: /${command}`);
|
|
9427
10052
|
console.log("Type /help for available commands");
|
|
9428
10053
|
}
|
|
9429
10054
|
};
|
|
9430
10055
|
if (initError) {
|
|
9431
|
-
return /* @__PURE__ */
|
|
10056
|
+
return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React12.createElement(Text11, { color: "red", bold: true }, "\u274C Initialization Error"), /* @__PURE__ */ React12.createElement(Text11, null, initError), /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "\n", "Tip: Run /config to set up your API keys"));
|
|
9432
10057
|
}
|
|
9433
10058
|
if (state.showLoginFlow) {
|
|
9434
10059
|
const loginApiUrl = getApiUrl(state.config?.apiConfig);
|
|
9435
|
-
return /* @__PURE__ */
|
|
10060
|
+
return /* @__PURE__ */ React12.createElement(
|
|
9436
10061
|
LoginFlow,
|
|
9437
10062
|
{
|
|
9438
10063
|
apiUrl: loginApiUrl,
|
|
@@ -9455,9 +10080,9 @@ Tool Permissions:
|
|
|
9455
10080
|
);
|
|
9456
10081
|
}
|
|
9457
10082
|
if (!isInitialized) {
|
|
9458
|
-
return /* @__PURE__ */
|
|
10083
|
+
return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React12.createElement(Text11, null, "\u{1F680} Initializing..."));
|
|
9459
10084
|
}
|
|
9460
|
-
return /* @__PURE__ */
|
|
10085
|
+
return /* @__PURE__ */ React12.createElement(
|
|
9461
10086
|
App,
|
|
9462
10087
|
{
|
|
9463
10088
|
onMessage: handleMessage,
|
|
@@ -9488,6 +10113,6 @@ var isDevMode = import.meta.url.includes("/src/") || process.env.NODE_ENV === "d
|
|
|
9488
10113
|
if (isDevMode) {
|
|
9489
10114
|
console.log("\u{1F527} Running in development mode (using TypeScript source)\n");
|
|
9490
10115
|
}
|
|
9491
|
-
render(/* @__PURE__ */
|
|
10116
|
+
render(/* @__PURE__ */ React12.createElement(CliApp, null), {
|
|
9492
10117
|
exitOnCtrlC: false
|
|
9493
10118
|
});
|