@gemdoq/codi 0.1.9 → 0.2.0
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/cli.js +2093 -724
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
5
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
6
|
+
}) : x)(function(x) {
|
|
7
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
8
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
9
|
+
});
|
|
4
10
|
var __esm = (fn, res) => function __init() {
|
|
5
11
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
12
|
};
|
|
@@ -95,15 +101,20 @@ function renderToolCall(toolName, args) {
|
|
|
95
101
|
return `${header}
|
|
96
102
|
${argStr}`;
|
|
97
103
|
}
|
|
98
|
-
function renderToolResult(toolName, result, isError) {
|
|
104
|
+
function renderToolResult(toolName, result, isError, durationMs) {
|
|
99
105
|
const icon = isError ? chalk2.red("\u2717") : chalk2.green("\u2713");
|
|
100
|
-
const
|
|
106
|
+
const duration = durationMs != null ? chalk2.dim(` (${formatDuration(durationMs)})`) : "";
|
|
107
|
+
const header = `${icon} ${chalk2.yellow(toolName)}${duration}`;
|
|
101
108
|
const content = isError ? chalk2.red(result) : chalk2.dim(result);
|
|
102
109
|
const maxLen = 500;
|
|
103
110
|
const truncated = content.length > maxLen ? content.slice(0, maxLen) + chalk2.dim("\n... (truncated)") : content;
|
|
104
111
|
return `${header}
|
|
105
112
|
${truncated}`;
|
|
106
113
|
}
|
|
114
|
+
function formatDuration(ms) {
|
|
115
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
116
|
+
return `${(ms / 1e3).toFixed(1)}s`;
|
|
117
|
+
}
|
|
107
118
|
function renderError(message) {
|
|
108
119
|
return chalk2.red(`\u2717 ${message}`);
|
|
109
120
|
}
|
|
@@ -323,9 +334,98 @@ var init_task_tools = __esm({
|
|
|
323
334
|
}
|
|
324
335
|
});
|
|
325
336
|
|
|
337
|
+
// src/tools/file-backup.ts
|
|
338
|
+
var file_backup_exports = {};
|
|
339
|
+
__export(file_backup_exports, {
|
|
340
|
+
backupFile: () => backupFile,
|
|
341
|
+
cleanupBackups: () => cleanupBackups,
|
|
342
|
+
getBackupHistory: () => getBackupHistory,
|
|
343
|
+
restoreFile: () => restoreFile,
|
|
344
|
+
undoLast: () => undoLast
|
|
345
|
+
});
|
|
346
|
+
import * as fs11 from "fs";
|
|
347
|
+
import * as os12 from "os";
|
|
348
|
+
import * as path12 from "path";
|
|
349
|
+
function ensureBackupDir() {
|
|
350
|
+
if (!fs11.existsSync(backupDir)) {
|
|
351
|
+
fs11.mkdirSync(backupDir, { recursive: true });
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
function registerCleanup() {
|
|
355
|
+
if (cleanupRegistered) return;
|
|
356
|
+
cleanupRegistered = true;
|
|
357
|
+
process.on("exit", () => {
|
|
358
|
+
cleanupBackups();
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
function backupFile(filePath) {
|
|
362
|
+
const resolved = path12.resolve(filePath);
|
|
363
|
+
ensureBackupDir();
|
|
364
|
+
registerCleanup();
|
|
365
|
+
const wasNew = !fs11.existsSync(resolved);
|
|
366
|
+
const backupName = `${Date.now()}-${Math.random().toString(36).slice(2)}-${path12.basename(resolved)}`;
|
|
367
|
+
const backupPath = path12.join(backupDir, backupName);
|
|
368
|
+
if (!wasNew) {
|
|
369
|
+
fs11.copyFileSync(resolved, backupPath);
|
|
370
|
+
}
|
|
371
|
+
const entry = {
|
|
372
|
+
backupPath,
|
|
373
|
+
originalPath: resolved,
|
|
374
|
+
wasNew,
|
|
375
|
+
timestamp: Date.now()
|
|
376
|
+
};
|
|
377
|
+
backupHistory.push(entry);
|
|
378
|
+
return backupPath;
|
|
379
|
+
}
|
|
380
|
+
function restoreFile(entry) {
|
|
381
|
+
if (entry.wasNew) {
|
|
382
|
+
if (fs11.existsSync(entry.originalPath)) {
|
|
383
|
+
fs11.unlinkSync(entry.originalPath);
|
|
384
|
+
}
|
|
385
|
+
} else {
|
|
386
|
+
if (fs11.existsSync(entry.backupPath)) {
|
|
387
|
+
fs11.copyFileSync(entry.backupPath, entry.originalPath);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
function cleanupBackups() {
|
|
392
|
+
if (fs11.existsSync(backupDir)) {
|
|
393
|
+
try {
|
|
394
|
+
fs11.rmSync(backupDir, { recursive: true, force: true });
|
|
395
|
+
} catch {
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
backupHistory.length = 0;
|
|
399
|
+
}
|
|
400
|
+
function getBackupHistory() {
|
|
401
|
+
return backupHistory;
|
|
402
|
+
}
|
|
403
|
+
function undoLast() {
|
|
404
|
+
const entry = backupHistory.pop();
|
|
405
|
+
if (!entry) return null;
|
|
406
|
+
restoreFile(entry);
|
|
407
|
+
if (!entry.wasNew && fs11.existsSync(entry.backupPath)) {
|
|
408
|
+
try {
|
|
409
|
+
fs11.unlinkSync(entry.backupPath);
|
|
410
|
+
} catch {
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return entry;
|
|
414
|
+
}
|
|
415
|
+
var backupDir, backupHistory, cleanupRegistered;
|
|
416
|
+
var init_file_backup = __esm({
|
|
417
|
+
"src/tools/file-backup.ts"() {
|
|
418
|
+
"use strict";
|
|
419
|
+
init_esm_shims();
|
|
420
|
+
backupDir = path12.join(os12.tmpdir(), `codi-backups-${process.pid}`);
|
|
421
|
+
backupHistory = [];
|
|
422
|
+
cleanupRegistered = false;
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
|
|
326
426
|
// src/cli.ts
|
|
327
427
|
init_esm_shims();
|
|
328
|
-
import
|
|
428
|
+
import chalk14 from "chalk";
|
|
329
429
|
|
|
330
430
|
// src/setup-wizard.ts
|
|
331
431
|
init_esm_shims();
|
|
@@ -637,6 +737,7 @@ init_esm_shims();
|
|
|
637
737
|
import * as readline2 from "readline/promises";
|
|
638
738
|
import { stdin as input2, stdout as output2 } from "process";
|
|
639
739
|
import * as os3 from "os";
|
|
740
|
+
import * as fs4 from "fs";
|
|
640
741
|
import chalk4 from "chalk";
|
|
641
742
|
import { execSync } from "child_process";
|
|
642
743
|
import { edit } from "external-editor";
|
|
@@ -779,14 +880,17 @@ function completer(line) {
|
|
|
779
880
|
}
|
|
780
881
|
|
|
781
882
|
// src/repl.ts
|
|
782
|
-
import { readFileSync as
|
|
883
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
783
884
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
784
885
|
import * as path5 from "path";
|
|
785
886
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
786
887
|
var __dirname2 = path5.dirname(__filename2);
|
|
888
|
+
var HISTORY_DIR = path5.join(os3.homedir(), ".codi");
|
|
889
|
+
var HISTORY_FILE = path5.join(HISTORY_DIR, "history");
|
|
890
|
+
var MAX_HISTORY = 1e3;
|
|
787
891
|
function getVersion() {
|
|
788
892
|
try {
|
|
789
|
-
const pkg = JSON.parse(
|
|
893
|
+
const pkg = JSON.parse(readFileSync4(path5.join(__dirname2, "..", "package.json"), "utf-8"));
|
|
790
894
|
return `v${pkg.version}`;
|
|
791
895
|
} catch {
|
|
792
896
|
return "v0.1.4";
|
|
@@ -815,15 +919,50 @@ var Repl = class {
|
|
|
815
919
|
description: "Clear screen"
|
|
816
920
|
});
|
|
817
921
|
}
|
|
922
|
+
loadHistory() {
|
|
923
|
+
try {
|
|
924
|
+
const content = fs4.readFileSync(HISTORY_FILE, "utf-8");
|
|
925
|
+
return content.split("\n").filter(Boolean).slice(-MAX_HISTORY);
|
|
926
|
+
} catch {
|
|
927
|
+
return [];
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
saveHistory() {
|
|
931
|
+
if (!this.rl) return;
|
|
932
|
+
try {
|
|
933
|
+
const rlAny = this.rl;
|
|
934
|
+
const history = rlAny.history ?? [];
|
|
935
|
+
const entries = history.slice(0, MAX_HISTORY).reverse();
|
|
936
|
+
fs4.mkdirSync(HISTORY_DIR, { recursive: true });
|
|
937
|
+
fs4.writeFileSync(HISTORY_FILE, entries.join("\n") + "\n", "utf-8");
|
|
938
|
+
} catch {
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
shouldSaveToHistory(line) {
|
|
942
|
+
const trimmed = line.trim();
|
|
943
|
+
if (!trimmed) return false;
|
|
944
|
+
if (trimmed.startsWith("/")) return false;
|
|
945
|
+
return true;
|
|
946
|
+
}
|
|
818
947
|
async start() {
|
|
819
948
|
this.running = true;
|
|
949
|
+
const loadedHistory = this.loadHistory();
|
|
820
950
|
this.rl = readline2.createInterface({
|
|
821
951
|
input: input2,
|
|
822
952
|
output: output2,
|
|
823
953
|
prompt: renderPrompt(),
|
|
824
954
|
completer: (line) => completer(line),
|
|
825
|
-
terminal: true
|
|
955
|
+
terminal: true,
|
|
956
|
+
history: loadedHistory,
|
|
957
|
+
historySize: MAX_HISTORY
|
|
826
958
|
});
|
|
959
|
+
const rlAny = this.rl;
|
|
960
|
+
if (rlAny.history && loadedHistory.length > 0) {
|
|
961
|
+
rlAny.history.length = 0;
|
|
962
|
+
for (let i = loadedHistory.length - 1; i >= 0; i--) {
|
|
963
|
+
rlAny.history.push(loadedHistory[i]);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
827
966
|
if (process.stdin.isTTY && os3.platform() !== "win32") {
|
|
828
967
|
process.stdout.write("\x1B[?2004h");
|
|
829
968
|
}
|
|
@@ -857,10 +996,10 @@ var Repl = class {
|
|
|
857
996
|
}
|
|
858
997
|
this.rl.setPrompt(renderPrompt());
|
|
859
998
|
this.rl.prompt();
|
|
860
|
-
const line = await new Promise((
|
|
999
|
+
const line = await new Promise((resolve11, reject) => {
|
|
861
1000
|
const onLine = (data) => {
|
|
862
1001
|
cleanup();
|
|
863
|
-
|
|
1002
|
+
resolve11(data);
|
|
864
1003
|
};
|
|
865
1004
|
const onClose = () => {
|
|
866
1005
|
cleanup();
|
|
@@ -876,7 +1015,7 @@ var Repl = class {
|
|
|
876
1015
|
this.lastInterruptTime = now;
|
|
877
1016
|
this.options.onInterrupt();
|
|
878
1017
|
console.log(chalk4.dim("\n(Press Ctrl+C again to exit)"));
|
|
879
|
-
|
|
1018
|
+
resolve11("");
|
|
880
1019
|
};
|
|
881
1020
|
const cleanup = () => {
|
|
882
1021
|
this.rl.removeListener("line", onLine);
|
|
@@ -889,6 +1028,17 @@ var Repl = class {
|
|
|
889
1028
|
});
|
|
890
1029
|
const trimmed = line.trim();
|
|
891
1030
|
if (!trimmed) continue;
|
|
1031
|
+
{
|
|
1032
|
+
const rlAny2 = this.rl;
|
|
1033
|
+
const hist = rlAny2.history;
|
|
1034
|
+
if (hist && hist.length > 0) {
|
|
1035
|
+
if (!this.shouldSaveToHistory(trimmed)) {
|
|
1036
|
+
hist.shift();
|
|
1037
|
+
} else if (hist.length > 1 && hist[0] === hist[1]) {
|
|
1038
|
+
hist.shift();
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
892
1042
|
if (trimmed.endsWith("\\")) {
|
|
893
1043
|
this.multilineBuffer.push(trimmed.slice(0, -1));
|
|
894
1044
|
this.inMultiline = true;
|
|
@@ -972,7 +1122,7 @@ var Repl = class {
|
|
|
972
1122
|
try {
|
|
973
1123
|
const ext = path5.extname(filePath).toLowerCase();
|
|
974
1124
|
if (IMAGE_EXTS.has(ext)) {
|
|
975
|
-
const data =
|
|
1125
|
+
const data = readFileSync4(filePath);
|
|
976
1126
|
const base64 = data.toString("base64");
|
|
977
1127
|
const mime = MIME_MAP[ext] || "image/png";
|
|
978
1128
|
imageBlocks.push({
|
|
@@ -982,7 +1132,7 @@ var Repl = class {
|
|
|
982
1132
|
message = message.replace(match, `[\uC774\uBBF8\uC9C0: ${path5.basename(filePath)}]`);
|
|
983
1133
|
hasImages = true;
|
|
984
1134
|
} else {
|
|
985
|
-
const content =
|
|
1135
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
986
1136
|
message = message.replace(match, `
|
|
987
1137
|
[File: ${filePath}]
|
|
988
1138
|
\`\`\`
|
|
@@ -1020,6 +1170,7 @@ ${content}
|
|
|
1020
1170
|
}
|
|
1021
1171
|
}
|
|
1022
1172
|
async gracefulExit() {
|
|
1173
|
+
this.saveHistory();
|
|
1023
1174
|
this.stop();
|
|
1024
1175
|
if (this.options.onExit) {
|
|
1025
1176
|
await this.options.onExit();
|
|
@@ -1046,6 +1197,64 @@ init_esm_shims();
|
|
|
1046
1197
|
|
|
1047
1198
|
// src/agent/conversation.ts
|
|
1048
1199
|
init_esm_shims();
|
|
1200
|
+
|
|
1201
|
+
// src/utils/tokenizer.ts
|
|
1202
|
+
init_esm_shims();
|
|
1203
|
+
import { getEncoding, encodingForModel } from "js-tiktoken";
|
|
1204
|
+
var encoderCache = /* @__PURE__ */ new Map();
|
|
1205
|
+
function getEncoder(model) {
|
|
1206
|
+
const cacheKey = model ?? "cl100k_base";
|
|
1207
|
+
const cached = encoderCache.get(cacheKey);
|
|
1208
|
+
if (cached) return cached;
|
|
1209
|
+
try {
|
|
1210
|
+
const encoder = model ? encodingForModel(model) : getEncoding("cl100k_base");
|
|
1211
|
+
encoderCache.set(cacheKey, encoder);
|
|
1212
|
+
return encoder;
|
|
1213
|
+
} catch {
|
|
1214
|
+
const fallback = encoderCache.get("cl100k_base");
|
|
1215
|
+
if (fallback) return fallback;
|
|
1216
|
+
const encoder = getEncoding("cl100k_base");
|
|
1217
|
+
encoderCache.set("cl100k_base", encoder);
|
|
1218
|
+
return encoder;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
function countTokens(text, model) {
|
|
1222
|
+
if (!text) return 0;
|
|
1223
|
+
try {
|
|
1224
|
+
const encoder = getEncoder(model);
|
|
1225
|
+
return encoder.encode(text).length;
|
|
1226
|
+
} catch {
|
|
1227
|
+
return Math.ceil(text.length / 4);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
function countContentBlockTokens(blocks, model) {
|
|
1231
|
+
let total = 0;
|
|
1232
|
+
for (const block of blocks) {
|
|
1233
|
+
if (block.type === "text") {
|
|
1234
|
+
total += countTokens(block.text, model);
|
|
1235
|
+
} else if (block.type === "tool_use") {
|
|
1236
|
+
total += countTokens(block.name, model);
|
|
1237
|
+
total += countTokens(JSON.stringify(block.input), model);
|
|
1238
|
+
} else if (block.type === "tool_result") {
|
|
1239
|
+
if (typeof block.content === "string") {
|
|
1240
|
+
total += countTokens(block.content, model);
|
|
1241
|
+
} else {
|
|
1242
|
+
total += countTokens(JSON.stringify(block.content), model);
|
|
1243
|
+
}
|
|
1244
|
+
} else {
|
|
1245
|
+
total += countTokens(JSON.stringify(block), model);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
return total;
|
|
1249
|
+
}
|
|
1250
|
+
function countMessageTokens(content, model) {
|
|
1251
|
+
if (typeof content === "string") {
|
|
1252
|
+
return countTokens(content, model);
|
|
1253
|
+
}
|
|
1254
|
+
return countContentBlockTokens(content, model);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
// src/agent/conversation.ts
|
|
1049
1258
|
var Conversation = class _Conversation {
|
|
1050
1259
|
messages = [];
|
|
1051
1260
|
systemPrompt = "";
|
|
@@ -1131,24 +1340,14 @@ ${summary}` },
|
|
|
1131
1340
|
return conv;
|
|
1132
1341
|
}
|
|
1133
1342
|
/**
|
|
1134
|
-
*
|
|
1343
|
+
* tiktoken을 사용하여 정확한 토큰 수를 계산한다.
|
|
1135
1344
|
*/
|
|
1136
|
-
estimateTokens() {
|
|
1137
|
-
let
|
|
1345
|
+
estimateTokens(model) {
|
|
1346
|
+
let tokens = countTokens(this.systemPrompt, model);
|
|
1138
1347
|
for (const msg of this.messages) {
|
|
1139
|
-
|
|
1140
|
-
chars += msg.content.length;
|
|
1141
|
-
} else {
|
|
1142
|
-
for (const block of msg.content) {
|
|
1143
|
-
if (block.type === "text") chars += block.text.length;
|
|
1144
|
-
else if (block.type === "tool_use") chars += JSON.stringify(block.input).length;
|
|
1145
|
-
else if (block.type === "tool_result") {
|
|
1146
|
-
chars += typeof block.content === "string" ? block.content.length : JSON.stringify(block.content).length;
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1348
|
+
tokens += countMessageTokens(msg.content, model);
|
|
1150
1349
|
}
|
|
1151
|
-
return
|
|
1350
|
+
return tokens;
|
|
1152
1351
|
}
|
|
1153
1352
|
};
|
|
1154
1353
|
|
|
@@ -1156,147 +1355,679 @@ ${summary}` },
|
|
|
1156
1355
|
init_esm_shims();
|
|
1157
1356
|
init_tool();
|
|
1158
1357
|
init_renderer();
|
|
1358
|
+
|
|
1359
|
+
// src/ui/spinner.ts
|
|
1360
|
+
init_esm_shims();
|
|
1361
|
+
import ora from "ora";
|
|
1159
1362
|
import chalk5 from "chalk";
|
|
1160
|
-
var
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1363
|
+
var currentSpinner = null;
|
|
1364
|
+
function startSpinner(text) {
|
|
1365
|
+
stopSpinner();
|
|
1366
|
+
currentSpinner = ora({
|
|
1367
|
+
text: chalk5.dim(text),
|
|
1368
|
+
spinner: "dots",
|
|
1369
|
+
color: "cyan"
|
|
1370
|
+
}).start();
|
|
1371
|
+
return currentSpinner;
|
|
1372
|
+
}
|
|
1373
|
+
function updateSpinner(text) {
|
|
1374
|
+
if (currentSpinner) {
|
|
1375
|
+
currentSpinner.text = chalk5.dim(text);
|
|
1164
1376
|
}
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
};
|
|
1173
|
-
}
|
|
1174
|
-
if (this.options.planMode && !tool.readOnly) {
|
|
1175
|
-
return {
|
|
1176
|
-
toolUseId: toolCall.id,
|
|
1177
|
-
toolName: toolCall.name,
|
|
1178
|
-
result: makeToolError(`Tool '${toolCall.name}' is not available in plan mode (read-only). Use only read-only tools.`)
|
|
1179
|
-
};
|
|
1180
|
-
}
|
|
1181
|
-
if (tool.dangerous && this.options.permissionCheck) {
|
|
1182
|
-
const allowed = await this.options.permissionCheck(tool, toolCall.input);
|
|
1183
|
-
if (!allowed) {
|
|
1184
|
-
return {
|
|
1185
|
-
toolUseId: toolCall.id,
|
|
1186
|
-
toolName: toolCall.name,
|
|
1187
|
-
result: makeToolError(`Permission denied for tool: ${toolCall.name}`)
|
|
1188
|
-
};
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
let input3 = toolCall.input;
|
|
1192
|
-
if (this.options.preHook) {
|
|
1193
|
-
try {
|
|
1194
|
-
const hookResult = await this.options.preHook(toolCall.name, input3);
|
|
1195
|
-
if (!hookResult.proceed) {
|
|
1196
|
-
return {
|
|
1197
|
-
toolUseId: toolCall.id,
|
|
1198
|
-
toolName: toolCall.name,
|
|
1199
|
-
result: makeToolError(`Tool execution blocked by hook for: ${toolCall.name}`)
|
|
1200
|
-
};
|
|
1201
|
-
}
|
|
1202
|
-
if (hookResult.updatedInput) {
|
|
1203
|
-
input3 = hookResult.updatedInput;
|
|
1204
|
-
}
|
|
1205
|
-
} catch (err) {
|
|
1206
|
-
console.error(chalk5.yellow(`Hook error for ${toolCall.name}: ${err}`));
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
if (this.options.showToolCalls) {
|
|
1210
|
-
console.log(renderToolCall(toolCall.name, input3));
|
|
1377
|
+
}
|
|
1378
|
+
function stopSpinner(symbol) {
|
|
1379
|
+
if (currentSpinner) {
|
|
1380
|
+
if (symbol) {
|
|
1381
|
+
currentSpinner.stopAndPersist({ symbol });
|
|
1382
|
+
} else {
|
|
1383
|
+
currentSpinner.stop();
|
|
1211
1384
|
}
|
|
1212
|
-
|
|
1385
|
+
currentSpinner = null;
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
// src/tools/bash.ts
|
|
1390
|
+
init_esm_shims();
|
|
1391
|
+
init_tool();
|
|
1392
|
+
import { spawn } from "child_process";
|
|
1393
|
+
import * as os5 from "os";
|
|
1394
|
+
import chalk7 from "chalk";
|
|
1395
|
+
|
|
1396
|
+
// src/security/command-validator.ts
|
|
1397
|
+
init_esm_shims();
|
|
1398
|
+
|
|
1399
|
+
// src/utils/logger.ts
|
|
1400
|
+
init_esm_shims();
|
|
1401
|
+
import * as fs5 from "fs";
|
|
1402
|
+
import * as path6 from "path";
|
|
1403
|
+
import * as os4 from "os";
|
|
1404
|
+
var LOG_LEVEL_PRIORITY = {
|
|
1405
|
+
debug: 0,
|
|
1406
|
+
info: 1,
|
|
1407
|
+
warn: 2,
|
|
1408
|
+
error: 3
|
|
1409
|
+
};
|
|
1410
|
+
var Logger = class _Logger {
|
|
1411
|
+
static instance;
|
|
1412
|
+
level;
|
|
1413
|
+
logDir;
|
|
1414
|
+
initialized = false;
|
|
1415
|
+
constructor(logDir) {
|
|
1416
|
+
const envLevel = process.env["CODI_LOG_LEVEL"]?.toLowerCase();
|
|
1417
|
+
this.level = this.isValidLevel(envLevel) ? envLevel : "info";
|
|
1418
|
+
this.logDir = logDir ?? path6.join(os4.homedir(), ".codi", "logs");
|
|
1419
|
+
}
|
|
1420
|
+
static getInstance() {
|
|
1421
|
+
if (!_Logger.instance) {
|
|
1422
|
+
_Logger.instance = new _Logger();
|
|
1423
|
+
}
|
|
1424
|
+
return _Logger.instance;
|
|
1425
|
+
}
|
|
1426
|
+
/** 테스트용: 커스텀 logDir로 새 인스턴스 생성 */
|
|
1427
|
+
static createForTest(logDir, level = "debug") {
|
|
1428
|
+
const instance = new _Logger(logDir);
|
|
1429
|
+
instance.level = level;
|
|
1430
|
+
return instance;
|
|
1431
|
+
}
|
|
1432
|
+
isValidLevel(val) {
|
|
1433
|
+
return val !== void 0 && val in LOG_LEVEL_PRIORITY;
|
|
1434
|
+
}
|
|
1435
|
+
ensureDir() {
|
|
1436
|
+
if (this.initialized) return;
|
|
1213
1437
|
try {
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
);
|
|
1219
|
-
}
|
|
1220
|
-
if (this.options.showToolCalls) {
|
|
1221
|
-
console.log(renderToolResult(toolCall.name, result.output, !result.success));
|
|
1438
|
+
fs5.mkdirSync(this.logDir, { recursive: true });
|
|
1439
|
+
this.initialized = true;
|
|
1440
|
+
this.rotateOldLogs();
|
|
1441
|
+
} catch {
|
|
1222
1442
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1443
|
+
}
|
|
1444
|
+
/** 7일 이상 된 로그 파일 삭제 */
|
|
1445
|
+
rotateOldLogs() {
|
|
1446
|
+
try {
|
|
1447
|
+
const files = fs5.readdirSync(this.logDir);
|
|
1448
|
+
const now = Date.now();
|
|
1449
|
+
const maxAge = 7 * 24 * 60 * 60 * 1e3;
|
|
1450
|
+
for (const file of files) {
|
|
1451
|
+
if (!file.startsWith("codi-") || !file.endsWith(".log")) continue;
|
|
1452
|
+
const filePath = path6.join(this.logDir, file);
|
|
1453
|
+
try {
|
|
1454
|
+
const stat = fs5.statSync(filePath);
|
|
1455
|
+
if (now - stat.mtimeMs > maxAge) {
|
|
1456
|
+
fs5.unlinkSync(filePath);
|
|
1457
|
+
}
|
|
1458
|
+
} catch {
|
|
1459
|
+
}
|
|
1227
1460
|
}
|
|
1461
|
+
} catch {
|
|
1228
1462
|
}
|
|
1229
|
-
return {
|
|
1230
|
-
toolUseId: toolCall.id,
|
|
1231
|
-
toolName: toolCall.name,
|
|
1232
|
-
result
|
|
1233
|
-
};
|
|
1234
1463
|
}
|
|
1235
|
-
|
|
1236
|
-
const
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1464
|
+
getLogFilePath() {
|
|
1465
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1466
|
+
return path6.join(this.logDir, `codi-${date}.log`);
|
|
1467
|
+
}
|
|
1468
|
+
shouldLog(level) {
|
|
1469
|
+
return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[this.level];
|
|
1470
|
+
}
|
|
1471
|
+
write(level, message, context, error) {
|
|
1472
|
+
if (!this.shouldLog(level)) return;
|
|
1473
|
+
this.ensureDir();
|
|
1474
|
+
if (!this.initialized) return;
|
|
1475
|
+
const entry = {
|
|
1476
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1477
|
+
level,
|
|
1478
|
+
message
|
|
1479
|
+
};
|
|
1480
|
+
if (context && Object.keys(context).length > 0) {
|
|
1481
|
+
entry.context = context;
|
|
1245
1482
|
}
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1483
|
+
if (error) {
|
|
1484
|
+
entry.error = {
|
|
1485
|
+
message: error.message,
|
|
1486
|
+
stack: error.stack
|
|
1487
|
+
};
|
|
1251
1488
|
}
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
const r = safeResults[i];
|
|
1256
|
-
if (r.status === "fulfilled") {
|
|
1257
|
-
results.push(r.value);
|
|
1258
|
-
} else {
|
|
1259
|
-
results.push({
|
|
1260
|
-
toolUseId: safeCalls[i].id,
|
|
1261
|
-
toolName: safeCalls[i].name,
|
|
1262
|
-
result: makeToolError(`Tool execution failed: ${r.reason}`)
|
|
1263
|
-
});
|
|
1264
|
-
}
|
|
1489
|
+
try {
|
|
1490
|
+
fs5.appendFileSync(this.getLogFilePath(), JSON.stringify(entry) + "\n");
|
|
1491
|
+
} catch {
|
|
1265
1492
|
}
|
|
1266
|
-
results.push(...dangerousResults);
|
|
1267
|
-
const orderMap = new Map(toolCalls.map((tc, i) => [tc.id, i]));
|
|
1268
|
-
results.sort((a, b) => (orderMap.get(a.toolUseId) ?? 0) - (orderMap.get(b.toolUseId) ?? 0));
|
|
1269
|
-
return results;
|
|
1270
1493
|
}
|
|
1271
|
-
|
|
1272
|
-
|
|
1494
|
+
debug(message, context) {
|
|
1495
|
+
this.write("debug", message, context);
|
|
1496
|
+
}
|
|
1497
|
+
info(message, context) {
|
|
1498
|
+
this.write("info", message, context);
|
|
1499
|
+
}
|
|
1500
|
+
warn(message, context) {
|
|
1501
|
+
this.write("warn", message, context);
|
|
1502
|
+
}
|
|
1503
|
+
error(message, context, error) {
|
|
1504
|
+
this.write("error", message, context, error);
|
|
1273
1505
|
}
|
|
1274
1506
|
};
|
|
1507
|
+
var logger = Logger.getInstance();
|
|
1275
1508
|
|
|
1276
|
-
// src/
|
|
1509
|
+
// src/security/command-validator.ts
|
|
1510
|
+
var BLOCKED_PATTERNS = [
|
|
1511
|
+
// 광범위 삭제
|
|
1512
|
+
{ pattern: /\brm\s+(-[a-zA-Z]*r[a-zA-Z]*f|(-[a-zA-Z]*f[a-zA-Z]*r))\s+[/~*]/, reason: "\uAD11\uBC94\uC704 \uC0AD\uC81C \uBA85\uB839\uC5B4(rm -rf /, ~, *)\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1513
|
+
{ pattern: /\brm\s+-rf\s*$/, reason: "\uB300\uC0C1 \uC5C6\uB294 rm -rf\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1514
|
+
{ pattern: /\bRemove-Item\s+.*-Recurse.*[/\\]\s*$/, reason: "PowerShell \uAD11\uBC94\uC704 \uC0AD\uC81C\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1515
|
+
{ pattern: /\bRemove-Item\s+.*-Recurse.*(\*|~|[A-Z]:\\)/, reason: "PowerShell \uAD11\uBC94\uC704 \uC0AD\uC81C\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1516
|
+
// 디스크 연산
|
|
1517
|
+
{ pattern: /\bmkfs\b/, reason: "\uD30C\uC77C\uC2DC\uC2A4\uD15C \uD3EC\uB9F7(mkfs) \uBA85\uB839\uC5B4\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1518
|
+
{ pattern: /\bformat\s+[A-Z]:/i, reason: "\uB514\uC2A4\uD06C \uD3EC\uB9F7(format) \uBA85\uB839\uC5B4\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1519
|
+
{ pattern: /\bdd\s+if=/, reason: "dd \uB514\uC2A4\uD06C \uC4F0\uAE30 \uBA85\uB839\uC5B4\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1520
|
+
// 광범위 권한 변경
|
|
1521
|
+
{ pattern: /\bchmod\s+(-[a-zA-Z]*R[a-zA-Z]*\s+)?777\b/, reason: "\uAD11\uBC94\uC704 \uAD8C\uD55C \uBCC0\uACBD(chmod 777)\uC774 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1522
|
+
{ pattern: /\bchown\s+-[a-zA-Z]*R/, reason: "\uC7AC\uADC0\uC801 \uC18C\uC720\uC790 \uBCC0\uACBD(chown -R)\uC774 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1523
|
+
// 포크 폭탄
|
|
1524
|
+
{ pattern: /:\(\)\s*\{.*\|.*&\s*\}\s*;?\s*:/, reason: "\uD3EC\uD06C \uD3ED\uD0C4\uC774 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1525
|
+
{ pattern: /\bwhile\s+true\s*;\s*do\s+fork/, reason: "\uD3EC\uD06C \uD3ED\uD0C4 \uD328\uD134\uC774 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1526
|
+
// 디바이스 리디렉션
|
|
1527
|
+
{ pattern: />\s*\/dev\/sd[a-z]/, reason: "\uB514\uBC14\uC774\uC2A4 \uC9C1\uC811 \uC4F0\uAE30\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1528
|
+
{ pattern: />\s*\/dev\/nvme/, reason: "\uB514\uBC14\uC774\uC2A4 \uC9C1\uC811 \uC4F0\uAE30\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1529
|
+
{ pattern: />\s*\\\\\.\\PhysicalDrive/, reason: "Windows \uB514\uBC14\uC774\uC2A4 \uC9C1\uC811 \uC4F0\uAE30\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1530
|
+
// 인터넷에서 받아서 바로 실행
|
|
1531
|
+
{ pattern: /\bcurl\b.*\|\s*(sh|bash|zsh|powershell|pwsh)\b/, reason: "\uC6D0\uACA9 \uC2A4\uD06C\uB9BD\uD2B8 \uD30C\uC774\uD504 \uC2E4\uD589(curl | sh)\uC774 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1532
|
+
{ pattern: /\bwget\b.*\|\s*(sh|bash|zsh|powershell|pwsh)\b/, reason: "\uC6D0\uACA9 \uC2A4\uD06C\uB9BD\uD2B8 \uD30C\uC774\uD504 \uC2E4\uD589(wget | sh)\uC774 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1533
|
+
{ pattern: /\bInvoke-WebRequest\b.*\|\s*Invoke-Expression\b/, reason: "PowerShell \uC6D0\uACA9 \uC2A4\uD06C\uB9BD\uD2B8 \uC2E4\uD589\uC774 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1534
|
+
{ pattern: /\biwr\b.*\|\s*iex\b/, reason: "PowerShell \uC6D0\uACA9 \uC2A4\uD06C\uB9BD\uD2B8 \uC2E4\uD589(iwr | iex)\uC774 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1535
|
+
{ pattern: /\bIEX\s*\(\s*(New-Object|Invoke-WebRequest|iwr)\b/, reason: "PowerShell \uC6D0\uACA9 \uC2A4\uD06C\uB9BD\uD2B8 \uC2E4\uD589\uC774 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1536
|
+
// 시스템 종료/재부팅
|
|
1537
|
+
{ pattern: /\bshutdown\b/, reason: "\uC2DC\uC2A4\uD15C \uC885\uB8CC(shutdown) \uBA85\uB839\uC5B4\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1538
|
+
{ pattern: /\breboot\b/, reason: "\uC2DC\uC2A4\uD15C \uC7AC\uBD80\uD305(reboot) \uBA85\uB839\uC5B4\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1539
|
+
{ pattern: /\bhalt\b/, reason: "\uC2DC\uC2A4\uD15C \uC911\uC9C0(halt) \uBA85\uB839\uC5B4\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1540
|
+
{ pattern: /\bStop-Computer\b/, reason: "PowerShell \uC2DC\uC2A4\uD15C \uC885\uB8CC\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1541
|
+
{ pattern: /\bRestart-Computer\b/, reason: "PowerShell \uC2DC\uC2A4\uD15C \uC7AC\uBD80\uD305\uC774 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1542
|
+
// 프로세스 종료
|
|
1543
|
+
{ pattern: /\bkill\s+-9\s+1\b/, reason: "init \uD504\uB85C\uC138\uC2A4 \uC885\uB8CC(kill -9 1)\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1544
|
+
{ pattern: /\bkillall\b/, reason: "\uC804\uCCB4 \uD504\uB85C\uC138\uC2A4 \uC885\uB8CC(killall)\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1545
|
+
{ pattern: /\bStop-Process\s+.*-Id\s+1\b/, reason: "PowerShell init \uD504\uB85C\uC138\uC2A4 \uC885\uB8CC\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." }
|
|
1546
|
+
];
|
|
1547
|
+
var WARNED_PATTERNS = [
|
|
1548
|
+
{ pattern: /\brm\s+(-[a-zA-Z]*r[a-zA-Z]*f|(-[a-zA-Z]*f[a-zA-Z]*r))\s+/, reason: "rm -rf \uBA85\uB839\uC5B4\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694." },
|
|
1549
|
+
{ pattern: /\bRemove-Item\s+.*-Recurse/, reason: "PowerShell \uC7AC\uADC0 \uC0AD\uC81C\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694." },
|
|
1550
|
+
{ pattern: /\bsudo\b/, reason: "sudo \uBA85\uB839\uC5B4\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1551
|
+
{ pattern: /\bRunAs\b/i, reason: "Windows \uAD00\uB9AC\uC790 \uAD8C\uD55C \uC2E4\uD589\uC774 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1552
|
+
{ pattern: /\bnpm\s+publish\b/, reason: "npm \uD328\uD0A4\uC9C0 \uBC30\uD3EC(npm publish)\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1553
|
+
{ pattern: /\bdocker\s+push\b/, reason: "Docker \uC774\uBBF8\uC9C0 \uD478\uC2DC(docker push)\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1554
|
+
{ pattern: /\bgit\s+push\b/, reason: "git push\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." }
|
|
1555
|
+
];
|
|
1556
|
+
function validateCommand(command) {
|
|
1557
|
+
const trimmed = command.trim();
|
|
1558
|
+
if (!trimmed) {
|
|
1559
|
+
return { allowed: true, level: "allowed" };
|
|
1560
|
+
}
|
|
1561
|
+
for (const { pattern, reason } of BLOCKED_PATTERNS) {
|
|
1562
|
+
if (pattern.test(trimmed)) {
|
|
1563
|
+
logger.warn("\uBA85\uB839\uC5B4 \uCC28\uB2E8\uB428", { command: trimmed, reason });
|
|
1564
|
+
return { allowed: false, level: "blocked", reason };
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
for (const { pattern, reason } of WARNED_PATTERNS) {
|
|
1568
|
+
if (pattern.test(trimmed)) {
|
|
1569
|
+
logger.info("\uBA85\uB839\uC5B4 \uACBD\uACE0", { command: trimmed, reason });
|
|
1570
|
+
return { allowed: true, level: "warned", reason };
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
return { allowed: true, level: "allowed" };
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
// src/security/permission-manager.ts
|
|
1277
1577
|
init_esm_shims();
|
|
1578
|
+
import * as readline3 from "readline/promises";
|
|
1579
|
+
import chalk6 from "chalk";
|
|
1278
1580
|
|
|
1279
|
-
// src/
|
|
1581
|
+
// src/config/permissions.ts
|
|
1280
1582
|
init_esm_shims();
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1583
|
+
function parseRule(rule) {
|
|
1584
|
+
const match = rule.match(/^(\w+)(?:\((.+)\))?$/);
|
|
1585
|
+
if (match) {
|
|
1586
|
+
return { tool: match[1], pattern: match[2] };
|
|
1587
|
+
}
|
|
1588
|
+
return { tool: rule };
|
|
1589
|
+
}
|
|
1590
|
+
function matchesRule(rule, toolName, input3) {
|
|
1591
|
+
if (rule.tool !== toolName) return false;
|
|
1592
|
+
if (!rule.pattern) return true;
|
|
1593
|
+
const pattern = new RegExp(rule.pattern.replace(/\*/g, ".*"));
|
|
1594
|
+
for (const value of Object.values(input3)) {
|
|
1595
|
+
if (typeof value === "string" && pattern.test(value)) {
|
|
1596
|
+
return true;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
return false;
|
|
1600
|
+
}
|
|
1601
|
+
function evaluatePermission(toolName, input3, rules) {
|
|
1602
|
+
for (const rule of rules.deny) {
|
|
1603
|
+
if (matchesRule(parseRule(rule), toolName, input3)) {
|
|
1604
|
+
return "deny";
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
for (const rule of rules.ask) {
|
|
1608
|
+
if (matchesRule(parseRule(rule), toolName, input3)) {
|
|
1609
|
+
return "ask";
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
for (const rule of rules.allow) {
|
|
1613
|
+
if (matchesRule(parseRule(rule), toolName, input3)) {
|
|
1614
|
+
return "allow";
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
return "ask";
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
// src/security/permission-manager.ts
|
|
1621
|
+
var sessionAllowed = /* @__PURE__ */ new Set();
|
|
1622
|
+
var sessionDenied = /* @__PURE__ */ new Set();
|
|
1623
|
+
var currentMode = "default";
|
|
1624
|
+
function setPermissionMode(mode) {
|
|
1625
|
+
currentMode = mode;
|
|
1626
|
+
}
|
|
1627
|
+
function getPermissionMode() {
|
|
1628
|
+
return currentMode;
|
|
1629
|
+
}
|
|
1630
|
+
async function checkPermission(tool, input3) {
|
|
1631
|
+
if (!tool.dangerous) return true;
|
|
1632
|
+
if (currentMode === "yolo") return true;
|
|
1633
|
+
if (currentMode === "acceptEdits" && ["write_file", "edit_file", "multi_edit"].includes(tool.name)) {
|
|
1634
|
+
return true;
|
|
1635
|
+
}
|
|
1636
|
+
if (currentMode === "plan" && !tool.readOnly) {
|
|
1637
|
+
return false;
|
|
1638
|
+
}
|
|
1639
|
+
const config = configManager.get();
|
|
1640
|
+
const decision = evaluatePermission(tool.name, input3, config.permissions);
|
|
1641
|
+
if (decision === "allow") return true;
|
|
1642
|
+
if (decision === "deny") {
|
|
1643
|
+
console.log(chalk6.red(`\u2717 Permission denied for ${tool.name} (denied by rule)`));
|
|
1644
|
+
return false;
|
|
1645
|
+
}
|
|
1646
|
+
const key = `${tool.name}:${JSON.stringify(input3)}`;
|
|
1647
|
+
if (sessionAllowed.has(tool.name)) return true;
|
|
1648
|
+
if (sessionDenied.has(tool.name)) return false;
|
|
1649
|
+
return promptUser(tool, input3);
|
|
1650
|
+
}
|
|
1651
|
+
async function promptUser(tool, input3) {
|
|
1652
|
+
console.log("");
|
|
1653
|
+
console.log(chalk6.yellow.bold(`\u26A0 Permission Required: ${tool.name}`));
|
|
1654
|
+
const relevantParams = Object.entries(input3).filter(([, v]) => v !== void 0);
|
|
1655
|
+
for (const [key, value] of relevantParams) {
|
|
1656
|
+
const displayValue = typeof value === "string" && value.length > 200 ? value.slice(0, 200) + "..." : String(value);
|
|
1657
|
+
console.log(chalk6.dim(` ${key}: `) + displayValue);
|
|
1658
|
+
}
|
|
1659
|
+
console.log("");
|
|
1660
|
+
const rl = readline3.createInterface({
|
|
1661
|
+
input: process.stdin,
|
|
1662
|
+
output: process.stdout
|
|
1663
|
+
});
|
|
1664
|
+
try {
|
|
1665
|
+
const answer = await rl.question(
|
|
1666
|
+
chalk6.yellow(`Allow? [${chalk6.bold("Y")}es / ${chalk6.bold("n")}o / ${chalk6.bold("a")}lways for this tool] `)
|
|
1667
|
+
);
|
|
1668
|
+
rl.close();
|
|
1669
|
+
const choice = answer.trim().toLowerCase();
|
|
1670
|
+
if (choice === "a" || choice === "always") {
|
|
1671
|
+
sessionAllowed.add(tool.name);
|
|
1672
|
+
return true;
|
|
1673
|
+
}
|
|
1674
|
+
if (choice === "n" || choice === "no") {
|
|
1675
|
+
return false;
|
|
1676
|
+
}
|
|
1677
|
+
return true;
|
|
1678
|
+
} catch {
|
|
1679
|
+
rl.close();
|
|
1680
|
+
return false;
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
// src/tools/bash.ts
|
|
1685
|
+
var _currentOnOutput = null;
|
|
1686
|
+
function setBashOutputCallback(cb) {
|
|
1687
|
+
_currentOnOutput = cb;
|
|
1688
|
+
}
|
|
1689
|
+
function getDefaultShell() {
|
|
1690
|
+
if (os5.platform() === "win32") {
|
|
1691
|
+
return "powershell.exe";
|
|
1692
|
+
}
|
|
1693
|
+
return process.env["SHELL"] || "/bin/bash";
|
|
1694
|
+
}
|
|
1695
|
+
var backgroundTasks = /* @__PURE__ */ new Map();
|
|
1696
|
+
var taskCounter = 0;
|
|
1697
|
+
var bashTool = {
|
|
1698
|
+
name: "bash",
|
|
1699
|
+
description: `Execute a shell command. Supports timeout (max 600s, default 120s) and background execution. The working directory persists between calls. Uses the platform default shell (bash on Unix, PowerShell on Windows).`,
|
|
1700
|
+
inputSchema: {
|
|
1701
|
+
type: "object",
|
|
1702
|
+
properties: {
|
|
1703
|
+
command: { type: "string", description: "The bash command to execute" },
|
|
1704
|
+
description: { type: "string", description: "Brief description of what the command does" },
|
|
1705
|
+
timeout: { type: "number", description: "Timeout in milliseconds (max 600000, default 120000)" },
|
|
1706
|
+
run_in_background: { type: "boolean", description: "Run in background and return a task ID" }
|
|
1707
|
+
},
|
|
1708
|
+
required: ["command"]
|
|
1709
|
+
},
|
|
1710
|
+
dangerous: true,
|
|
1711
|
+
readOnly: false,
|
|
1712
|
+
async execute(input3) {
|
|
1713
|
+
const command = String(input3["command"]);
|
|
1714
|
+
const timeout = Math.min(Number(input3["timeout"]) || 12e4, 6e5);
|
|
1715
|
+
const runInBackground = input3["run_in_background"] === true;
|
|
1716
|
+
if (!command.trim()) {
|
|
1717
|
+
return makeToolError("Command cannot be empty");
|
|
1718
|
+
}
|
|
1719
|
+
if (getPermissionMode() !== "yolo") {
|
|
1720
|
+
const validation = validateCommand(command);
|
|
1721
|
+
if (!validation.allowed) {
|
|
1722
|
+
return makeToolError(`\uBA85\uB839\uC5B4\uAC00 \uCC28\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${validation.reason}`);
|
|
1723
|
+
}
|
|
1724
|
+
if (validation.level === "warned") {
|
|
1725
|
+
console.log(chalk7.yellow(`\u26A0 \uACBD\uACE0: ${validation.reason}`));
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
if (runInBackground) {
|
|
1729
|
+
return runBackgroundTask(command);
|
|
1730
|
+
}
|
|
1731
|
+
return new Promise((resolve11) => {
|
|
1732
|
+
const finalCommand = os5.platform() === "win32" ? `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; ${command}` : command;
|
|
1733
|
+
const startTime = Date.now();
|
|
1734
|
+
let stdout = "";
|
|
1735
|
+
let stderr = "";
|
|
1736
|
+
let timedOut = false;
|
|
1737
|
+
const proc = spawn(finalCommand, {
|
|
1738
|
+
shell: getDefaultShell(),
|
|
1739
|
+
cwd: process.cwd(),
|
|
1740
|
+
env: { ...process.env },
|
|
1741
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1742
|
+
});
|
|
1743
|
+
const timer = setTimeout(() => {
|
|
1744
|
+
timedOut = true;
|
|
1745
|
+
proc.kill("SIGTERM");
|
|
1746
|
+
setTimeout(() => proc.kill("SIGKILL"), 5e3);
|
|
1747
|
+
}, timeout);
|
|
1748
|
+
const onOutput = _currentOnOutput;
|
|
1749
|
+
proc.stdout?.on("data", (data) => {
|
|
1750
|
+
const chunk = data.toString();
|
|
1751
|
+
stdout += chunk;
|
|
1752
|
+
if (onOutput) {
|
|
1753
|
+
onOutput(chunk);
|
|
1754
|
+
}
|
|
1755
|
+
});
|
|
1756
|
+
proc.stderr?.on("data", (data) => {
|
|
1757
|
+
const chunk = data.toString();
|
|
1758
|
+
stderr += chunk;
|
|
1759
|
+
if (onOutput) {
|
|
1760
|
+
onOutput(chunk);
|
|
1761
|
+
}
|
|
1762
|
+
});
|
|
1763
|
+
proc.on("close", (code) => {
|
|
1764
|
+
clearTimeout(timer);
|
|
1765
|
+
const durationMs = Date.now() - startTime;
|
|
1766
|
+
const exitCode = code ?? 1;
|
|
1767
|
+
if (timedOut) {
|
|
1768
|
+
logger.info("bash \uBA85\uB839\uC5B4 \uC2E4\uD589", { command, exitCode, durationMs, timedOut: true });
|
|
1769
|
+
const output4 = [
|
|
1770
|
+
stdout ? `stdout:
|
|
1771
|
+
${stdout}` : "",
|
|
1772
|
+
stderr ? `stderr:
|
|
1773
|
+
${stderr}` : "",
|
|
1774
|
+
`Exit code: ${exitCode}`
|
|
1775
|
+
].filter(Boolean).join("\n\n");
|
|
1776
|
+
resolve11(makeToolError(`Command timed out after ${timeout / 1e3}s
|
|
1777
|
+
${output4}`));
|
|
1778
|
+
return;
|
|
1779
|
+
}
|
|
1780
|
+
if (exitCode !== 0) {
|
|
1781
|
+
logger.info("bash \uBA85\uB839\uC5B4 \uC2E4\uD589", { command, exitCode, durationMs, timedOut: false });
|
|
1782
|
+
const output4 = [
|
|
1783
|
+
stdout ? `stdout:
|
|
1784
|
+
${stdout}` : "",
|
|
1785
|
+
stderr ? `stderr:
|
|
1786
|
+
${stderr}` : "",
|
|
1787
|
+
`Exit code: ${exitCode}`
|
|
1788
|
+
].filter(Boolean).join("\n\n");
|
|
1789
|
+
resolve11(makeToolResult(output4 || `Command failed with exit code ${exitCode}`));
|
|
1790
|
+
return;
|
|
1791
|
+
}
|
|
1792
|
+
logger.info("bash \uBA85\uB839\uC5B4 \uC2E4\uD589", { command, exitCode: 0, durationMs });
|
|
1793
|
+
const output3 = [
|
|
1794
|
+
stdout ? stdout : "",
|
|
1795
|
+
stderr ? `stderr:
|
|
1796
|
+
${stderr}` : ""
|
|
1797
|
+
].filter(Boolean).join("\n");
|
|
1798
|
+
resolve11(makeToolResult(output3 || "(no output)"));
|
|
1799
|
+
});
|
|
1800
|
+
proc.on("error", (err) => {
|
|
1801
|
+
clearTimeout(timer);
|
|
1802
|
+
const durationMs = Date.now() - startTime;
|
|
1803
|
+
logger.info("bash \uBA85\uB839\uC5B4 \uC2E4\uD589 \uC2E4\uD328", { command, durationMs, error: err.message });
|
|
1804
|
+
resolve11(makeToolError(`Failed to execute command: ${err.message}`));
|
|
1805
|
+
});
|
|
1806
|
+
});
|
|
1807
|
+
}
|
|
1808
|
+
};
|
|
1809
|
+
function runBackgroundTask(command) {
|
|
1810
|
+
const taskId = `bg_${++taskCounter}`;
|
|
1811
|
+
const bgCommand = os5.platform() === "win32" ? `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; ${command}` : command;
|
|
1812
|
+
const proc = spawn(bgCommand, {
|
|
1813
|
+
shell: getDefaultShell(),
|
|
1814
|
+
cwd: process.cwd(),
|
|
1815
|
+
env: { ...process.env },
|
|
1816
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1817
|
+
detached: false
|
|
1818
|
+
});
|
|
1819
|
+
const task = { process: proc, output: "", status: "running", exitCode: void 0 };
|
|
1820
|
+
backgroundTasks.set(taskId, task);
|
|
1821
|
+
proc.stdout?.on("data", (data) => {
|
|
1822
|
+
task.output += data.toString();
|
|
1823
|
+
});
|
|
1824
|
+
proc.stderr?.on("data", (data) => {
|
|
1825
|
+
task.output += data.toString();
|
|
1826
|
+
});
|
|
1827
|
+
proc.on("close", (code) => {
|
|
1828
|
+
task.status = code === 0 ? "done" : "error";
|
|
1829
|
+
task.exitCode = code ?? 1;
|
|
1830
|
+
});
|
|
1831
|
+
proc.on("error", (err) => {
|
|
1832
|
+
task.status = "error";
|
|
1833
|
+
task.output += `
|
|
1834
|
+
Process error: ${err.message}`;
|
|
1835
|
+
});
|
|
1836
|
+
return makeToolResult(`Background task started with ID: ${taskId}
|
|
1837
|
+
Use task_output tool to check results.`);
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
// src/tools/executor.ts
|
|
1841
|
+
import chalk8 from "chalk";
|
|
1842
|
+
var ToolExecutor = class {
|
|
1843
|
+
constructor(registry, options = {}) {
|
|
1844
|
+
this.registry = registry;
|
|
1845
|
+
this.options = options;
|
|
1846
|
+
}
|
|
1847
|
+
async executeOne(toolCall) {
|
|
1848
|
+
const tool = this.registry.get(toolCall.name);
|
|
1849
|
+
if (!tool) {
|
|
1850
|
+
return {
|
|
1851
|
+
toolUseId: toolCall.id,
|
|
1852
|
+
toolName: toolCall.name,
|
|
1853
|
+
result: makeToolError(`Unknown tool: ${toolCall.name}. Available tools: ${this.registry.listNames().join(", ")}`)
|
|
1854
|
+
};
|
|
1855
|
+
}
|
|
1856
|
+
if (this.options.planMode && !tool.readOnly) {
|
|
1857
|
+
return {
|
|
1858
|
+
toolUseId: toolCall.id,
|
|
1859
|
+
toolName: toolCall.name,
|
|
1860
|
+
result: makeToolError(`Tool '${toolCall.name}' is not available in plan mode (read-only). Use only read-only tools.`)
|
|
1861
|
+
};
|
|
1862
|
+
}
|
|
1863
|
+
if (tool.dangerous && this.options.permissionCheck) {
|
|
1864
|
+
const allowed = await this.options.permissionCheck(tool, toolCall.input);
|
|
1865
|
+
if (!allowed) {
|
|
1866
|
+
return {
|
|
1867
|
+
toolUseId: toolCall.id,
|
|
1868
|
+
toolName: toolCall.name,
|
|
1869
|
+
result: makeToolError(`Permission denied for tool: ${toolCall.name}`)
|
|
1870
|
+
};
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
let input3 = toolCall.input;
|
|
1874
|
+
if (this.options.preHook) {
|
|
1875
|
+
try {
|
|
1876
|
+
const hookResult = await this.options.preHook(toolCall.name, input3);
|
|
1877
|
+
if (!hookResult.proceed) {
|
|
1878
|
+
return {
|
|
1879
|
+
toolUseId: toolCall.id,
|
|
1880
|
+
toolName: toolCall.name,
|
|
1881
|
+
result: makeToolError(`Tool execution blocked by hook for: ${toolCall.name}`)
|
|
1882
|
+
};
|
|
1883
|
+
}
|
|
1884
|
+
if (hookResult.updatedInput) {
|
|
1885
|
+
input3 = hookResult.updatedInput;
|
|
1886
|
+
}
|
|
1887
|
+
} catch (err) {
|
|
1888
|
+
console.error(chalk8.yellow(`Hook error for ${toolCall.name}: ${err}`));
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
if (this.options.showToolCalls) {
|
|
1892
|
+
console.log(renderToolCall(toolCall.name, input3));
|
|
1893
|
+
}
|
|
1894
|
+
let result;
|
|
1895
|
+
const execStart = Date.now();
|
|
1896
|
+
let elapsedTimer = null;
|
|
1897
|
+
if (this.options.showToolCalls) {
|
|
1898
|
+
elapsedTimer = setInterval(() => {
|
|
1899
|
+
const elapsed = ((Date.now() - execStart) / 1e3).toFixed(1);
|
|
1900
|
+
updateSpinner(`${toolCall.name} (${elapsed}s...)`);
|
|
1901
|
+
}, 200);
|
|
1902
|
+
startSpinner(`${toolCall.name}...`);
|
|
1903
|
+
if (toolCall.name === "bash") {
|
|
1904
|
+
setBashOutputCallback((_chunk) => {
|
|
1905
|
+
const elapsed = ((Date.now() - execStart) / 1e3).toFixed(1);
|
|
1906
|
+
updateSpinner(`${toolCall.name} (${elapsed}s...) \u25B8 \uCD9C\uB825 \uC218\uC2E0 \uC911`);
|
|
1907
|
+
});
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
try {
|
|
1911
|
+
result = await tool.execute(input3);
|
|
1912
|
+
} catch (err) {
|
|
1913
|
+
result = makeToolError(
|
|
1914
|
+
`Tool '${toolCall.name}' threw an error: ${err instanceof Error ? err.message : String(err)}`
|
|
1915
|
+
);
|
|
1916
|
+
} finally {
|
|
1917
|
+
if (elapsedTimer) {
|
|
1918
|
+
clearInterval(elapsedTimer);
|
|
1919
|
+
}
|
|
1920
|
+
if (toolCall.name === "bash") {
|
|
1921
|
+
setBashOutputCallback(null);
|
|
1922
|
+
}
|
|
1923
|
+
stopSpinner();
|
|
1924
|
+
}
|
|
1925
|
+
const execDuration = Date.now() - execStart;
|
|
1926
|
+
logger.debug("\uB3C4\uAD6C \uC2E4\uD589 \uC644\uB8CC", {
|
|
1927
|
+
tool: toolCall.name,
|
|
1928
|
+
durationMs: execDuration,
|
|
1929
|
+
success: result.success
|
|
1930
|
+
});
|
|
1931
|
+
if (this.options.showToolCalls) {
|
|
1932
|
+
console.log(renderToolResult(toolCall.name, result.output, !result.success, execDuration));
|
|
1933
|
+
}
|
|
1934
|
+
if (this.options.postHook) {
|
|
1935
|
+
try {
|
|
1936
|
+
await this.options.postHook(toolCall.name, input3, result);
|
|
1937
|
+
} catch {
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
return {
|
|
1941
|
+
toolUseId: toolCall.id,
|
|
1942
|
+
toolName: toolCall.name,
|
|
1943
|
+
result
|
|
1944
|
+
};
|
|
1945
|
+
}
|
|
1946
|
+
async executeMany(toolCalls) {
|
|
1947
|
+
const safeCalls = [];
|
|
1948
|
+
const dangerousCalls = [];
|
|
1949
|
+
for (const tc of toolCalls) {
|
|
1950
|
+
const tool = this.registry.get(tc.name);
|
|
1951
|
+
if (tool?.dangerous) {
|
|
1952
|
+
dangerousCalls.push(tc);
|
|
1953
|
+
} else {
|
|
1954
|
+
safeCalls.push(tc);
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
const safePromises = safeCalls.map((tc) => this.executeOne(tc));
|
|
1958
|
+
const dangerousResults = [];
|
|
1959
|
+
for (const tc of dangerousCalls) {
|
|
1960
|
+
const result = await this.executeOne(tc);
|
|
1961
|
+
dangerousResults.push(result);
|
|
1962
|
+
}
|
|
1963
|
+
const safeResults = await Promise.allSettled(safePromises);
|
|
1964
|
+
const results = [];
|
|
1965
|
+
for (let i = 0; i < safeResults.length; i++) {
|
|
1966
|
+
const r = safeResults[i];
|
|
1967
|
+
if (r.status === "fulfilled") {
|
|
1968
|
+
results.push(r.value);
|
|
1969
|
+
} else {
|
|
1970
|
+
results.push({
|
|
1971
|
+
toolUseId: safeCalls[i].id,
|
|
1972
|
+
toolName: safeCalls[i].name,
|
|
1973
|
+
result: makeToolError(`Tool execution failed: ${r.reason}`)
|
|
1974
|
+
});
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
results.push(...dangerousResults);
|
|
1978
|
+
const orderMap = new Map(toolCalls.map((tc, i) => [tc.id, i]));
|
|
1979
|
+
results.sort((a, b) => (orderMap.get(a.toolUseId) ?? 0) - (orderMap.get(b.toolUseId) ?? 0));
|
|
1980
|
+
return results;
|
|
1981
|
+
}
|
|
1982
|
+
setOptions(options) {
|
|
1983
|
+
Object.assign(this.options, options);
|
|
1984
|
+
}
|
|
1985
|
+
};
|
|
1986
|
+
|
|
1987
|
+
// src/agent/token-tracker.ts
|
|
1988
|
+
init_esm_shims();
|
|
1989
|
+
|
|
1990
|
+
// src/llm/types.ts
|
|
1991
|
+
init_esm_shims();
|
|
1992
|
+
var MODEL_COSTS = {
|
|
1993
|
+
// Claude models
|
|
1994
|
+
"claude-sonnet-4-20250514": { input: 3e-3, output: 0.015 },
|
|
1995
|
+
"claude-opus-4-20250514": { input: 0.015, output: 0.075 },
|
|
1996
|
+
"claude-haiku-3-5-20241022": { input: 8e-4, output: 4e-3 },
|
|
1997
|
+
// GPT models
|
|
1998
|
+
"gpt-4o": { input: 25e-4, output: 0.01 },
|
|
1999
|
+
"gpt-4o-mini": { input: 15e-5, output: 6e-4 },
|
|
2000
|
+
"gpt-4.1": { input: 2e-3, output: 8e-3 },
|
|
2001
|
+
"gpt-4.1-mini": { input: 4e-4, output: 16e-4 },
|
|
2002
|
+
"gpt-4.1-nano": { input: 1e-4, output: 4e-4 },
|
|
2003
|
+
"o1": { input: 0.015, output: 0.06 },
|
|
2004
|
+
"o1-mini": { input: 3e-3, output: 0.012 },
|
|
2005
|
+
"o3-mini": { input: 11e-4, output: 44e-4 },
|
|
2006
|
+
// Gemini models
|
|
2007
|
+
"gemini-2.5-flash": { input: 15e-5, output: 6e-4 },
|
|
2008
|
+
"gemini-2.5-pro": { input: 125e-5, output: 0.01 },
|
|
2009
|
+
"gemini-2.0-flash": { input: 1e-4, output: 4e-4 },
|
|
2010
|
+
"gemini-1.5-flash": { input: 75e-6, output: 3e-4 },
|
|
2011
|
+
"gemini-1.5-pro": { input: 125e-5, output: 5e-3 }
|
|
1290
2012
|
};
|
|
1291
2013
|
function getModelCost(model) {
|
|
2014
|
+
if (model.startsWith("ollama:") || model.startsWith("ollama/")) {
|
|
2015
|
+
return { input: 0, output: 0 };
|
|
2016
|
+
}
|
|
1292
2017
|
return MODEL_COSTS[model] ?? { input: 0, output: 0 };
|
|
1293
2018
|
}
|
|
1294
2019
|
|
|
1295
2020
|
// src/agent/token-tracker.ts
|
|
1296
2021
|
var TokenTracker = class {
|
|
2022
|
+
// Global (accumulated) counters
|
|
1297
2023
|
inputTokens = 0;
|
|
1298
2024
|
outputTokens = 0;
|
|
1299
2025
|
requests = 0;
|
|
2026
|
+
// Per-session counters
|
|
2027
|
+
sessionInputTokens = 0;
|
|
2028
|
+
sessionOutputTokens = 0;
|
|
2029
|
+
sessionRequests = 0;
|
|
2030
|
+
lastRequestCost = null;
|
|
1300
2031
|
model = "";
|
|
1301
2032
|
setModel(model) {
|
|
1302
2033
|
this.model = model;
|
|
@@ -1305,6 +2036,17 @@ var TokenTracker = class {
|
|
|
1305
2036
|
this.inputTokens += usage.input_tokens;
|
|
1306
2037
|
this.outputTokens += usage.output_tokens;
|
|
1307
2038
|
this.requests++;
|
|
2039
|
+
this.sessionInputTokens += usage.input_tokens;
|
|
2040
|
+
this.sessionOutputTokens += usage.output_tokens;
|
|
2041
|
+
this.sessionRequests++;
|
|
2042
|
+
const costs = getModelCost(this.model);
|
|
2043
|
+
const reqCost = usage.input_tokens / 1e3 * costs.input + usage.output_tokens / 1e3 * costs.output;
|
|
2044
|
+
this.lastRequestCost = {
|
|
2045
|
+
inputTokens: usage.input_tokens,
|
|
2046
|
+
outputTokens: usage.output_tokens,
|
|
2047
|
+
cost: reqCost,
|
|
2048
|
+
timestamp: Date.now()
|
|
2049
|
+
};
|
|
1308
2050
|
}
|
|
1309
2051
|
getStats() {
|
|
1310
2052
|
const costs = getModelCost(this.model);
|
|
@@ -1317,6 +2059,25 @@ var TokenTracker = class {
|
|
|
1317
2059
|
requests: this.requests
|
|
1318
2060
|
};
|
|
1319
2061
|
}
|
|
2062
|
+
getSessionStats() {
|
|
2063
|
+
const costs = getModelCost(this.model);
|
|
2064
|
+
const cost = this.sessionInputTokens / 1e3 * costs.input + this.sessionOutputTokens / 1e3 * costs.output;
|
|
2065
|
+
return {
|
|
2066
|
+
inputTokens: this.sessionInputTokens,
|
|
2067
|
+
outputTokens: this.sessionOutputTokens,
|
|
2068
|
+
totalTokens: this.sessionInputTokens + this.sessionOutputTokens,
|
|
2069
|
+
cost,
|
|
2070
|
+
requests: this.sessionRequests,
|
|
2071
|
+
lastRequestCost: this.lastRequestCost,
|
|
2072
|
+
avgCostPerRequest: this.sessionRequests > 0 ? cost / this.sessionRequests : 0
|
|
2073
|
+
};
|
|
2074
|
+
}
|
|
2075
|
+
resetSession() {
|
|
2076
|
+
this.sessionInputTokens = 0;
|
|
2077
|
+
this.sessionOutputTokens = 0;
|
|
2078
|
+
this.sessionRequests = 0;
|
|
2079
|
+
this.lastRequestCost = null;
|
|
2080
|
+
}
|
|
1320
2081
|
getCost() {
|
|
1321
2082
|
return this.getStats().cost;
|
|
1322
2083
|
}
|
|
@@ -1346,39 +2107,9 @@ var TokenTracker = class {
|
|
|
1346
2107
|
};
|
|
1347
2108
|
var tokenTracker = new TokenTracker();
|
|
1348
2109
|
|
|
1349
|
-
// src/ui/spinner.ts
|
|
1350
|
-
init_esm_shims();
|
|
1351
|
-
import ora from "ora";
|
|
1352
|
-
import chalk6 from "chalk";
|
|
1353
|
-
var currentSpinner = null;
|
|
1354
|
-
function startSpinner(text) {
|
|
1355
|
-
stopSpinner();
|
|
1356
|
-
currentSpinner = ora({
|
|
1357
|
-
text: chalk6.dim(text),
|
|
1358
|
-
spinner: "dots",
|
|
1359
|
-
color: "cyan"
|
|
1360
|
-
}).start();
|
|
1361
|
-
return currentSpinner;
|
|
1362
|
-
}
|
|
1363
|
-
function updateSpinner(text) {
|
|
1364
|
-
if (currentSpinner) {
|
|
1365
|
-
currentSpinner.text = chalk6.dim(text);
|
|
1366
|
-
}
|
|
1367
|
-
}
|
|
1368
|
-
function stopSpinner(symbol) {
|
|
1369
|
-
if (currentSpinner) {
|
|
1370
|
-
if (symbol) {
|
|
1371
|
-
currentSpinner.stopAndPersist({ symbol });
|
|
1372
|
-
} else {
|
|
1373
|
-
currentSpinner.stop();
|
|
1374
|
-
}
|
|
1375
|
-
currentSpinner = null;
|
|
1376
|
-
}
|
|
1377
|
-
}
|
|
1378
|
-
|
|
1379
2110
|
// src/agent/agent-loop.ts
|
|
1380
2111
|
init_renderer();
|
|
1381
|
-
import
|
|
2112
|
+
import chalk9 from "chalk";
|
|
1382
2113
|
var MAX_RETRIES = 3;
|
|
1383
2114
|
var RETRY_DELAYS = [1e3, 2e3, 4e3];
|
|
1384
2115
|
async function agentLoop(userMessage, options) {
|
|
@@ -1412,8 +2143,9 @@ async function agentLoop(userMessage, options) {
|
|
|
1412
2143
|
} catch (err) {
|
|
1413
2144
|
stopSpinner();
|
|
1414
2145
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
2146
|
+
logger.error("LLM \uD638\uCD9C \uC2E4\uD328", { model: provider.model }, err instanceof Error ? err : new Error(errMsg));
|
|
1415
2147
|
if (showOutput) {
|
|
1416
|
-
console.error(
|
|
2148
|
+
console.error(chalk9.red(`
|
|
1417
2149
|
LLM Error: ${errMsg}`));
|
|
1418
2150
|
}
|
|
1419
2151
|
return `Error communicating with LLM: ${errMsg}`;
|
|
@@ -1427,6 +2159,13 @@ LLM Error: ${errMsg}`));
|
|
|
1427
2159
|
outputTokens: stats.outputTokens,
|
|
1428
2160
|
cost: stats.cost
|
|
1429
2161
|
});
|
|
2162
|
+
logger.debug("LLM \uC751\uB2F5 \uC218\uC2E0", {
|
|
2163
|
+
model: provider.model,
|
|
2164
|
+
inputTokens: response.usage.input_tokens,
|
|
2165
|
+
outputTokens: response.usage.output_tokens,
|
|
2166
|
+
stopReason: response.stopReason,
|
|
2167
|
+
toolCalls: response.toolCalls?.length ?? 0
|
|
2168
|
+
});
|
|
1430
2169
|
}
|
|
1431
2170
|
conversation.addAssistantMessage(response.content);
|
|
1432
2171
|
const wasStreamed = response._streamed === true;
|
|
@@ -1447,7 +2186,7 @@ LLM Error: ${errMsg}`));
|
|
|
1447
2186
|
}
|
|
1448
2187
|
if (response.stopReason === "max_tokens") {
|
|
1449
2188
|
if (showOutput) {
|
|
1450
|
-
console.log(
|
|
2189
|
+
console.log(chalk9.yellow("\n\u26A0 Response truncated (max tokens reached)"));
|
|
1451
2190
|
}
|
|
1452
2191
|
}
|
|
1453
2192
|
if (response.toolCalls && response.toolCalls.length > 0) {
|
|
@@ -1485,7 +2224,7 @@ LLM Error: ${errMsg}`));
|
|
|
1485
2224
|
}
|
|
1486
2225
|
if (iterations >= maxIterations) {
|
|
1487
2226
|
if (showOutput) {
|
|
1488
|
-
console.log(
|
|
2227
|
+
console.log(chalk9.yellow(`
|
|
1489
2228
|
\u26A0 Agent loop reached maximum iterations (${maxIterations})`));
|
|
1490
2229
|
}
|
|
1491
2230
|
}
|
|
@@ -1539,12 +2278,12 @@ async function callLlmWithRetry(provider, conversation, registry, stream, option
|
|
|
1539
2278
|
throw lastError || new Error("Max retries exceeded");
|
|
1540
2279
|
}
|
|
1541
2280
|
function sleep(ms) {
|
|
1542
|
-
return new Promise((
|
|
2281
|
+
return new Promise((resolve11) => setTimeout(resolve11, ms));
|
|
1543
2282
|
}
|
|
1544
2283
|
|
|
1545
2284
|
// src/agent/system-prompt.ts
|
|
1546
2285
|
init_esm_shims();
|
|
1547
|
-
import * as
|
|
2286
|
+
import * as os6 from "os";
|
|
1548
2287
|
import { execSync as execSync2 } from "child_process";
|
|
1549
2288
|
var ROLE_DEFINITION = `You are Codi (\uCF54\uB514), a terminal-based AI coding agent. You help users with software engineering tasks including writing code, debugging, refactoring, and explaining code. You have access to tools for file manipulation, code search, shell execution, and more.
|
|
1550
2289
|
|
|
@@ -1604,7 +2343,8 @@ var TOOL_HIERARCHY = `# Tool Usage Rules
|
|
|
1604
2343
|
- Use grep instead of bash grep/rg for content search
|
|
1605
2344
|
- Reserve bash for system commands that have no dedicated tool
|
|
1606
2345
|
- Use sub_agent for complex multi-step exploration tasks
|
|
1607
|
-
- Call multiple tools in parallel when they are independent
|
|
2346
|
+
- Call multiple tools in parallel when they are independent
|
|
2347
|
+
- Use update_memory to persist important information (architecture, user preferences, patterns, decisions) across conversations. Proactively save useful context when you discover it.`;
|
|
1608
2348
|
var WINDOWS_RULES = `# Windows Shell Rules
|
|
1609
2349
|
You are running on Windows. The shell is PowerShell. Follow these rules:
|
|
1610
2350
|
- Use PowerShell syntax, NOT bash/sh syntax
|
|
@@ -1656,7 +2396,7 @@ function buildSystemPrompt(context) {
|
|
|
1656
2396
|
fragments.push(buildEnvironmentInfo(context));
|
|
1657
2397
|
fragments.push(CONVERSATION_RULES);
|
|
1658
2398
|
fragments.push(TOOL_HIERARCHY);
|
|
1659
|
-
if (
|
|
2399
|
+
if (os6.platform() === "win32") {
|
|
1660
2400
|
fragments.push(WINDOWS_RULES);
|
|
1661
2401
|
}
|
|
1662
2402
|
fragments.push(CODE_RULES);
|
|
@@ -1687,8 +2427,8 @@ function buildEnvironmentInfo(context) {
|
|
|
1687
2427
|
const lines = [
|
|
1688
2428
|
"# Environment",
|
|
1689
2429
|
`- Date: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`,
|
|
1690
|
-
`- OS: ${
|
|
1691
|
-
`- Shell: ${
|
|
2430
|
+
`- OS: ${os6.platform()} ${os6.release()}`,
|
|
2431
|
+
`- Shell: ${os6.platform() === "win32" ? "PowerShell" : process.env["SHELL"] || "/bin/bash"}`,
|
|
1692
2432
|
`- Working Directory: ${context.cwd}`,
|
|
1693
2433
|
`- Model: ${context.model}`,
|
|
1694
2434
|
`- Provider: ${context.provider}`
|
|
@@ -1720,7 +2460,7 @@ var ContextCompressor = class {
|
|
|
1720
2460
|
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
1721
2461
|
}
|
|
1722
2462
|
shouldCompress(conversation) {
|
|
1723
|
-
const estimatedTokens = conversation.estimateTokens();
|
|
2463
|
+
const estimatedTokens = conversation.estimateTokens(this.options.model);
|
|
1724
2464
|
return estimatedTokens > this.options.maxContextTokens * this.options.threshold;
|
|
1725
2465
|
}
|
|
1726
2466
|
async compress(conversation, provider, focusHint) {
|
|
@@ -1758,58 +2498,58 @@ ${summaryContent}`;
|
|
|
1758
2498
|
|
|
1759
2499
|
// src/agent/memory.ts
|
|
1760
2500
|
init_esm_shims();
|
|
1761
|
-
import * as
|
|
1762
|
-
import * as
|
|
1763
|
-
import * as
|
|
2501
|
+
import * as fs6 from "fs";
|
|
2502
|
+
import * as os7 from "os";
|
|
2503
|
+
import * as path7 from "path";
|
|
1764
2504
|
import * as crypto from "crypto";
|
|
1765
2505
|
var MemoryManager = class {
|
|
1766
2506
|
memoryDir;
|
|
1767
2507
|
constructor() {
|
|
1768
|
-
const home = process.env["HOME"] || process.env["USERPROFILE"] ||
|
|
2508
|
+
const home = process.env["HOME"] || process.env["USERPROFILE"] || os7.homedir();
|
|
1769
2509
|
const projectHash = crypto.createHash("md5").update(process.cwd()).digest("hex").slice(0, 8);
|
|
1770
|
-
const projectName =
|
|
1771
|
-
this.memoryDir =
|
|
2510
|
+
const projectName = path7.basename(process.cwd());
|
|
2511
|
+
this.memoryDir = path7.join(home, ".codi", "projects", `${projectName}-${projectHash}`, "memory");
|
|
1772
2512
|
}
|
|
1773
2513
|
ensureDir() {
|
|
1774
|
-
if (!
|
|
1775
|
-
|
|
2514
|
+
if (!fs6.existsSync(this.memoryDir)) {
|
|
2515
|
+
fs6.mkdirSync(this.memoryDir, { recursive: true });
|
|
1776
2516
|
}
|
|
1777
2517
|
}
|
|
1778
2518
|
getMemoryDir() {
|
|
1779
2519
|
return this.memoryDir;
|
|
1780
2520
|
}
|
|
1781
2521
|
loadIndex() {
|
|
1782
|
-
const indexPath =
|
|
1783
|
-
if (!
|
|
1784
|
-
const content =
|
|
2522
|
+
const indexPath = path7.join(this.memoryDir, "MEMORY.md");
|
|
2523
|
+
if (!fs6.existsSync(indexPath)) return "";
|
|
2524
|
+
const content = fs6.readFileSync(indexPath, "utf-8");
|
|
1785
2525
|
const lines = content.split("\n");
|
|
1786
2526
|
return lines.slice(0, 200).join("\n");
|
|
1787
2527
|
}
|
|
1788
2528
|
saveIndex(content) {
|
|
1789
2529
|
this.ensureDir();
|
|
1790
|
-
const indexPath =
|
|
1791
|
-
|
|
2530
|
+
const indexPath = path7.join(this.memoryDir, "MEMORY.md");
|
|
2531
|
+
fs6.writeFileSync(indexPath, content, "utf-8");
|
|
1792
2532
|
}
|
|
1793
2533
|
loadTopic(name) {
|
|
1794
|
-
const topicPath =
|
|
1795
|
-
if (!
|
|
1796
|
-
return
|
|
2534
|
+
const topicPath = path7.join(this.memoryDir, `${name}.md`);
|
|
2535
|
+
if (!fs6.existsSync(topicPath)) return null;
|
|
2536
|
+
return fs6.readFileSync(topicPath, "utf-8");
|
|
1797
2537
|
}
|
|
1798
2538
|
saveTopic(name, content) {
|
|
1799
2539
|
this.ensureDir();
|
|
1800
|
-
const topicPath =
|
|
1801
|
-
|
|
2540
|
+
const topicPath = path7.join(this.memoryDir, `${name}.md`);
|
|
2541
|
+
fs6.writeFileSync(topicPath, content, "utf-8");
|
|
1802
2542
|
}
|
|
1803
2543
|
listTopics() {
|
|
1804
|
-
if (!
|
|
1805
|
-
return
|
|
2544
|
+
if (!fs6.existsSync(this.memoryDir)) return [];
|
|
2545
|
+
return fs6.readdirSync(this.memoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md").map((f) => f.replace(".md", ""));
|
|
1806
2546
|
}
|
|
1807
2547
|
buildMemoryPrompt() {
|
|
1808
2548
|
const index = this.loadIndex();
|
|
1809
2549
|
if (!index) return "";
|
|
1810
2550
|
const lines = [
|
|
1811
2551
|
`You have a persistent memory directory at ${this.memoryDir}.`,
|
|
1812
|
-
"Use
|
|
2552
|
+
"Use the update_memory tool to save, delete, or list memory topics as you learn patterns.",
|
|
1813
2553
|
"",
|
|
1814
2554
|
"Current MEMORY.md:",
|
|
1815
2555
|
index
|
|
@@ -1821,25 +2561,25 @@ var memoryManager = new MemoryManager();
|
|
|
1821
2561
|
|
|
1822
2562
|
// src/agent/session.ts
|
|
1823
2563
|
init_esm_shims();
|
|
1824
|
-
import * as
|
|
1825
|
-
import * as
|
|
1826
|
-
import * as
|
|
2564
|
+
import * as fs7 from "fs";
|
|
2565
|
+
import * as os8 from "os";
|
|
2566
|
+
import * as path8 from "path";
|
|
1827
2567
|
import * as crypto2 from "crypto";
|
|
1828
2568
|
var SessionManager = class {
|
|
1829
2569
|
sessionsDir;
|
|
1830
2570
|
constructor() {
|
|
1831
|
-
const home = process.env["HOME"] || process.env["USERPROFILE"] ||
|
|
1832
|
-
this.sessionsDir =
|
|
2571
|
+
const home = process.env["HOME"] || process.env["USERPROFILE"] || os8.homedir();
|
|
2572
|
+
this.sessionsDir = path8.join(home, ".codi", "sessions");
|
|
1833
2573
|
}
|
|
1834
2574
|
ensureDir() {
|
|
1835
|
-
if (!
|
|
1836
|
-
|
|
2575
|
+
if (!fs7.existsSync(this.sessionsDir)) {
|
|
2576
|
+
fs7.mkdirSync(this.sessionsDir, { recursive: true });
|
|
1837
2577
|
}
|
|
1838
2578
|
}
|
|
1839
2579
|
save(conversation, name, model) {
|
|
1840
2580
|
this.ensureDir();
|
|
1841
2581
|
const id = name || crypto2.randomUUID().slice(0, 8);
|
|
1842
|
-
const filePath =
|
|
2582
|
+
const filePath = path8.join(this.sessionsDir, `${id}.jsonl`);
|
|
1843
2583
|
const data = conversation.serialize();
|
|
1844
2584
|
const meta = {
|
|
1845
2585
|
id,
|
|
@@ -1855,13 +2595,13 @@ var SessionManager = class {
|
|
|
1855
2595
|
JSON.stringify({ type: "system", content: data.systemPrompt }),
|
|
1856
2596
|
...data.messages.map((m) => JSON.stringify({ type: "message", ...m }))
|
|
1857
2597
|
];
|
|
1858
|
-
|
|
2598
|
+
fs7.writeFileSync(filePath, lines.join("\n") + "\n", "utf-8");
|
|
1859
2599
|
return id;
|
|
1860
2600
|
}
|
|
1861
2601
|
load(id) {
|
|
1862
|
-
const filePath =
|
|
1863
|
-
if (!
|
|
1864
|
-
const content =
|
|
2602
|
+
const filePath = path8.join(this.sessionsDir, `${id}.jsonl`);
|
|
2603
|
+
if (!fs7.existsSync(filePath)) return null;
|
|
2604
|
+
const content = fs7.readFileSync(filePath, "utf-8");
|
|
1865
2605
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
1866
2606
|
let meta = null;
|
|
1867
2607
|
let systemPrompt = "";
|
|
@@ -1886,12 +2626,12 @@ var SessionManager = class {
|
|
|
1886
2626
|
}
|
|
1887
2627
|
list() {
|
|
1888
2628
|
this.ensureDir();
|
|
1889
|
-
const files =
|
|
2629
|
+
const files = fs7.readdirSync(this.sessionsDir).filter((f) => f.endsWith(".jsonl"));
|
|
1890
2630
|
const sessions = [];
|
|
1891
2631
|
for (const file of files) {
|
|
1892
|
-
const filePath =
|
|
2632
|
+
const filePath = path8.join(this.sessionsDir, file);
|
|
1893
2633
|
try {
|
|
1894
|
-
const firstLine =
|
|
2634
|
+
const firstLine = fs7.readFileSync(filePath, "utf-8").split("\n")[0];
|
|
1895
2635
|
if (firstLine) {
|
|
1896
2636
|
const meta = JSON.parse(firstLine);
|
|
1897
2637
|
if (meta.type === "meta") {
|
|
@@ -1909,9 +2649,9 @@ var SessionManager = class {
|
|
|
1909
2649
|
return sessions[0] ?? null;
|
|
1910
2650
|
}
|
|
1911
2651
|
delete(id) {
|
|
1912
|
-
const filePath =
|
|
1913
|
-
if (
|
|
1914
|
-
|
|
2652
|
+
const filePath = path8.join(this.sessionsDir, `${id}.jsonl`);
|
|
2653
|
+
if (fs7.existsSync(filePath)) {
|
|
2654
|
+
fs7.unlinkSync(filePath);
|
|
1915
2655
|
return true;
|
|
1916
2656
|
}
|
|
1917
2657
|
return false;
|
|
@@ -1933,25 +2673,79 @@ var sessionManager = new SessionManager();
|
|
|
1933
2673
|
|
|
1934
2674
|
// src/agent/checkpoint.ts
|
|
1935
2675
|
init_esm_shims();
|
|
2676
|
+
import * as fs8 from "fs";
|
|
2677
|
+
import * as os9 from "os";
|
|
2678
|
+
import * as path9 from "path";
|
|
2679
|
+
import * as crypto3 from "crypto";
|
|
1936
2680
|
import { execSync as execSync3 } from "child_process";
|
|
2681
|
+
var MAX_CHECKPOINTS = 20;
|
|
1937
2682
|
var CheckpointManager = class {
|
|
1938
2683
|
checkpoints = [];
|
|
1939
2684
|
nextId = 1;
|
|
1940
2685
|
isGitRepo;
|
|
1941
|
-
|
|
2686
|
+
sessionId;
|
|
2687
|
+
checkpointDir;
|
|
2688
|
+
constructor(sessionId) {
|
|
2689
|
+
this.sessionId = sessionId || crypto3.randomUUID().slice(0, 8);
|
|
2690
|
+
const home = process.env["HOME"] || process.env["USERPROFILE"] || os9.homedir();
|
|
2691
|
+
this.checkpointDir = path9.join(home, ".codi", "checkpoints", this.sessionId);
|
|
1942
2692
|
try {
|
|
1943
2693
|
execSync3("git rev-parse --is-inside-work-tree", { stdio: "pipe" });
|
|
1944
2694
|
this.isGitRepo = true;
|
|
1945
2695
|
} catch {
|
|
1946
2696
|
this.isGitRepo = false;
|
|
1947
2697
|
}
|
|
2698
|
+
this.loadFromDisk();
|
|
2699
|
+
}
|
|
2700
|
+
getSessionId() {
|
|
2701
|
+
return this.sessionId;
|
|
2702
|
+
}
|
|
2703
|
+
ensureDir() {
|
|
2704
|
+
if (!fs8.existsSync(this.checkpointDir)) {
|
|
2705
|
+
fs8.mkdirSync(this.checkpointDir, { recursive: true });
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
checkpointPath(id) {
|
|
2709
|
+
return path9.join(this.checkpointDir, `checkpoint-${id}.json`);
|
|
2710
|
+
}
|
|
2711
|
+
saveToDisk(checkpoint) {
|
|
2712
|
+
this.ensureDir();
|
|
2713
|
+
const filePath = this.checkpointPath(checkpoint.id);
|
|
2714
|
+
fs8.writeFileSync(filePath, JSON.stringify(checkpoint), "utf-8");
|
|
2715
|
+
}
|
|
2716
|
+
deleteFromDisk(id) {
|
|
2717
|
+
const filePath = this.checkpointPath(id);
|
|
2718
|
+
if (fs8.existsSync(filePath)) {
|
|
2719
|
+
fs8.unlinkSync(filePath);
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
loadFromDisk() {
|
|
2723
|
+
if (!fs8.existsSync(this.checkpointDir)) return;
|
|
2724
|
+
const files = fs8.readdirSync(this.checkpointDir).filter((f) => f.startsWith("checkpoint-") && f.endsWith(".json")).sort((a, b) => {
|
|
2725
|
+
const idA = parseInt(a.replace("checkpoint-", "").replace(".json", ""), 10);
|
|
2726
|
+
const idB = parseInt(b.replace("checkpoint-", "").replace(".json", ""), 10);
|
|
2727
|
+
return idA - idB;
|
|
2728
|
+
});
|
|
2729
|
+
for (const file of files) {
|
|
2730
|
+
try {
|
|
2731
|
+
const content = fs8.readFileSync(path9.join(this.checkpointDir, file), "utf-8");
|
|
2732
|
+
const checkpoint = JSON.parse(content);
|
|
2733
|
+
this.checkpoints.push(checkpoint);
|
|
2734
|
+
if (checkpoint.id >= this.nextId) {
|
|
2735
|
+
this.nextId = checkpoint.id + 1;
|
|
2736
|
+
}
|
|
2737
|
+
} catch {
|
|
2738
|
+
continue;
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
1948
2741
|
}
|
|
1949
2742
|
create(conversation, description) {
|
|
1950
2743
|
const checkpoint = {
|
|
1951
2744
|
id: this.nextId++,
|
|
1952
2745
|
timestamp: Date.now(),
|
|
1953
2746
|
conversation: conversation.serialize(),
|
|
1954
|
-
description
|
|
2747
|
+
description,
|
|
2748
|
+
messageCount: conversation.getMessageCount()
|
|
1955
2749
|
};
|
|
1956
2750
|
if (this.isGitRepo) {
|
|
1957
2751
|
try {
|
|
@@ -1973,8 +2767,12 @@ var CheckpointManager = class {
|
|
|
1973
2767
|
}
|
|
1974
2768
|
}
|
|
1975
2769
|
this.checkpoints.push(checkpoint);
|
|
1976
|
-
|
|
1977
|
-
|
|
2770
|
+
this.saveToDisk(checkpoint);
|
|
2771
|
+
if (this.checkpoints.length > MAX_CHECKPOINTS) {
|
|
2772
|
+
const removed = this.checkpoints.splice(0, this.checkpoints.length - MAX_CHECKPOINTS);
|
|
2773
|
+
for (const cp of removed) {
|
|
2774
|
+
this.deleteFromDisk(cp.id);
|
|
2775
|
+
}
|
|
1978
2776
|
}
|
|
1979
2777
|
return checkpoint.id;
|
|
1980
2778
|
}
|
|
@@ -1994,7 +2792,10 @@ var CheckpointManager = class {
|
|
|
1994
2792
|
}
|
|
1995
2793
|
const idx = this.checkpoints.indexOf(checkpoint);
|
|
1996
2794
|
if (idx >= 0) {
|
|
1997
|
-
|
|
2795
|
+
const removed = this.checkpoints.splice(idx + 1);
|
|
2796
|
+
for (const cp of removed) {
|
|
2797
|
+
this.deleteFromDisk(cp.id);
|
|
2798
|
+
}
|
|
1998
2799
|
}
|
|
1999
2800
|
return {
|
|
2000
2801
|
conversation: Conversation.deserialize(checkpoint.conversation),
|
|
@@ -2005,54 +2806,64 @@ var CheckpointManager = class {
|
|
|
2005
2806
|
return this.checkpoints.map((cp) => ({
|
|
2006
2807
|
id: cp.id,
|
|
2007
2808
|
timestamp: cp.timestamp,
|
|
2008
|
-
description: cp.description
|
|
2809
|
+
description: cp.description,
|
|
2810
|
+
messageCount: cp.messageCount
|
|
2009
2811
|
}));
|
|
2010
2812
|
}
|
|
2813
|
+
/**
|
|
2814
|
+
* 세션 종료 시 체크포인트 파일 정리
|
|
2815
|
+
*/
|
|
2816
|
+
cleanup() {
|
|
2817
|
+
if (fs8.existsSync(this.checkpointDir)) {
|
|
2818
|
+
fs8.rmSync(this.checkpointDir, { recursive: true, force: true });
|
|
2819
|
+
}
|
|
2820
|
+
this.checkpoints = [];
|
|
2821
|
+
}
|
|
2011
2822
|
};
|
|
2012
2823
|
var checkpointManager = new CheckpointManager();
|
|
2013
2824
|
|
|
2014
2825
|
// src/agent/codi-md.ts
|
|
2015
2826
|
init_esm_shims();
|
|
2016
|
-
import * as
|
|
2017
|
-
import * as
|
|
2827
|
+
import * as fs9 from "fs";
|
|
2828
|
+
import * as path10 from "path";
|
|
2018
2829
|
function loadCodiMd() {
|
|
2019
2830
|
const fragments = [];
|
|
2020
2831
|
let dir = process.cwd();
|
|
2021
|
-
const root =
|
|
2832
|
+
const root = path10.parse(dir).root;
|
|
2022
2833
|
while (dir !== root) {
|
|
2023
2834
|
loadFromDir(dir, fragments);
|
|
2024
|
-
const parent =
|
|
2835
|
+
const parent = path10.dirname(dir);
|
|
2025
2836
|
if (parent === dir) break;
|
|
2026
2837
|
dir = parent;
|
|
2027
2838
|
}
|
|
2028
2839
|
return fragments.join("\n\n---\n\n");
|
|
2029
2840
|
}
|
|
2030
2841
|
function loadFromDir(dir, fragments) {
|
|
2031
|
-
const codiPath =
|
|
2032
|
-
if (
|
|
2842
|
+
const codiPath = path10.join(dir, "CODI.md");
|
|
2843
|
+
if (fs9.existsSync(codiPath)) {
|
|
2033
2844
|
try {
|
|
2034
|
-
let content =
|
|
2845
|
+
let content = fs9.readFileSync(codiPath, "utf-8");
|
|
2035
2846
|
content = processImports(content, dir);
|
|
2036
2847
|
fragments.push(`[CODI.md from ${dir}]
|
|
2037
2848
|
${content}`);
|
|
2038
2849
|
} catch {
|
|
2039
2850
|
}
|
|
2040
2851
|
}
|
|
2041
|
-
const localPath =
|
|
2042
|
-
if (
|
|
2852
|
+
const localPath = path10.join(dir, "CODI.local.md");
|
|
2853
|
+
if (fs9.existsSync(localPath)) {
|
|
2043
2854
|
try {
|
|
2044
|
-
const content =
|
|
2855
|
+
const content = fs9.readFileSync(localPath, "utf-8");
|
|
2045
2856
|
fragments.push(`[CODI.local.md from ${dir}]
|
|
2046
2857
|
${content}`);
|
|
2047
2858
|
} catch {
|
|
2048
2859
|
}
|
|
2049
2860
|
}
|
|
2050
|
-
const rulesDir =
|
|
2051
|
-
if (
|
|
2861
|
+
const rulesDir = path10.join(dir, ".codi", "rules");
|
|
2862
|
+
if (fs9.existsSync(rulesDir)) {
|
|
2052
2863
|
try {
|
|
2053
|
-
const files =
|
|
2864
|
+
const files = fs9.readdirSync(rulesDir).filter((f) => f.endsWith(".md")).sort();
|
|
2054
2865
|
for (const file of files) {
|
|
2055
|
-
const content =
|
|
2866
|
+
const content = fs9.readFileSync(path10.join(rulesDir, file), "utf-8");
|
|
2056
2867
|
fragments.push(`[Rule: ${file}]
|
|
2057
2868
|
${content}`);
|
|
2058
2869
|
}
|
|
@@ -2062,10 +2873,10 @@ ${content}`);
|
|
|
2062
2873
|
}
|
|
2063
2874
|
function processImports(content, baseDir) {
|
|
2064
2875
|
return content.replace(/@([\w./-]+)/g, (match, importPath) => {
|
|
2065
|
-
const resolved =
|
|
2066
|
-
if (
|
|
2876
|
+
const resolved = path10.resolve(baseDir, importPath);
|
|
2877
|
+
if (fs9.existsSync(resolved)) {
|
|
2067
2878
|
try {
|
|
2068
|
-
return
|
|
2879
|
+
return fs9.readFileSync(resolved, "utf-8");
|
|
2069
2880
|
} catch {
|
|
2070
2881
|
return match;
|
|
2071
2882
|
}
|
|
@@ -2076,12 +2887,12 @@ function processImports(content, baseDir) {
|
|
|
2076
2887
|
|
|
2077
2888
|
// src/agent/mode-manager.ts
|
|
2078
2889
|
init_esm_shims();
|
|
2079
|
-
var
|
|
2890
|
+
var currentMode2 = "execute";
|
|
2080
2891
|
function getMode() {
|
|
2081
|
-
return
|
|
2892
|
+
return currentMode2;
|
|
2082
2893
|
}
|
|
2083
2894
|
function setMode(mode) {
|
|
2084
|
-
|
|
2895
|
+
currentMode2 = mode;
|
|
2085
2896
|
}
|
|
2086
2897
|
|
|
2087
2898
|
// src/tools/registry.ts
|
|
@@ -2143,117 +2954,12 @@ var ToolRegistry = class _ToolRegistry {
|
|
|
2143
2954
|
}
|
|
2144
2955
|
};
|
|
2145
2956
|
|
|
2146
|
-
// src/security/permission-manager.ts
|
|
2147
|
-
init_esm_shims();
|
|
2148
|
-
import * as readline3 from "readline/promises";
|
|
2149
|
-
import chalk8 from "chalk";
|
|
2150
|
-
|
|
2151
|
-
// src/config/permissions.ts
|
|
2152
|
-
init_esm_shims();
|
|
2153
|
-
function parseRule(rule) {
|
|
2154
|
-
const match = rule.match(/^(\w+)(?:\((.+)\))?$/);
|
|
2155
|
-
if (match) {
|
|
2156
|
-
return { tool: match[1], pattern: match[2] };
|
|
2157
|
-
}
|
|
2158
|
-
return { tool: rule };
|
|
2159
|
-
}
|
|
2160
|
-
function matchesRule(rule, toolName, input3) {
|
|
2161
|
-
if (rule.tool !== toolName) return false;
|
|
2162
|
-
if (!rule.pattern) return true;
|
|
2163
|
-
const pattern = new RegExp(rule.pattern.replace(/\*/g, ".*"));
|
|
2164
|
-
for (const value of Object.values(input3)) {
|
|
2165
|
-
if (typeof value === "string" && pattern.test(value)) {
|
|
2166
|
-
return true;
|
|
2167
|
-
}
|
|
2168
|
-
}
|
|
2169
|
-
return false;
|
|
2170
|
-
}
|
|
2171
|
-
function evaluatePermission(toolName, input3, rules) {
|
|
2172
|
-
for (const rule of rules.deny) {
|
|
2173
|
-
if (matchesRule(parseRule(rule), toolName, input3)) {
|
|
2174
|
-
return "deny";
|
|
2175
|
-
}
|
|
2176
|
-
}
|
|
2177
|
-
for (const rule of rules.ask) {
|
|
2178
|
-
if (matchesRule(parseRule(rule), toolName, input3)) {
|
|
2179
|
-
return "ask";
|
|
2180
|
-
}
|
|
2181
|
-
}
|
|
2182
|
-
for (const rule of rules.allow) {
|
|
2183
|
-
if (matchesRule(parseRule(rule), toolName, input3)) {
|
|
2184
|
-
return "allow";
|
|
2185
|
-
}
|
|
2186
|
-
}
|
|
2187
|
-
return "ask";
|
|
2188
|
-
}
|
|
2189
|
-
|
|
2190
|
-
// src/security/permission-manager.ts
|
|
2191
|
-
var sessionAllowed = /* @__PURE__ */ new Set();
|
|
2192
|
-
var sessionDenied = /* @__PURE__ */ new Set();
|
|
2193
|
-
var currentMode2 = "default";
|
|
2194
|
-
function setPermissionMode(mode) {
|
|
2195
|
-
currentMode2 = mode;
|
|
2196
|
-
}
|
|
2197
|
-
async function checkPermission(tool, input3) {
|
|
2198
|
-
if (!tool.dangerous) return true;
|
|
2199
|
-
if (currentMode2 === "yolo") return true;
|
|
2200
|
-
if (currentMode2 === "acceptEdits" && ["write_file", "edit_file", "multi_edit"].includes(tool.name)) {
|
|
2201
|
-
return true;
|
|
2202
|
-
}
|
|
2203
|
-
if (currentMode2 === "plan" && !tool.readOnly) {
|
|
2204
|
-
return false;
|
|
2205
|
-
}
|
|
2206
|
-
const config = configManager.get();
|
|
2207
|
-
const decision = evaluatePermission(tool.name, input3, config.permissions);
|
|
2208
|
-
if (decision === "allow") return true;
|
|
2209
|
-
if (decision === "deny") {
|
|
2210
|
-
console.log(chalk8.red(`\u2717 Permission denied for ${tool.name} (denied by rule)`));
|
|
2211
|
-
return false;
|
|
2212
|
-
}
|
|
2213
|
-
const key = `${tool.name}:${JSON.stringify(input3)}`;
|
|
2214
|
-
if (sessionAllowed.has(tool.name)) return true;
|
|
2215
|
-
if (sessionDenied.has(tool.name)) return false;
|
|
2216
|
-
return promptUser(tool, input3);
|
|
2217
|
-
}
|
|
2218
|
-
async function promptUser(tool, input3) {
|
|
2219
|
-
console.log("");
|
|
2220
|
-
console.log(chalk8.yellow.bold(`\u26A0 Permission Required: ${tool.name}`));
|
|
2221
|
-
const relevantParams = Object.entries(input3).filter(([, v]) => v !== void 0);
|
|
2222
|
-
for (const [key, value] of relevantParams) {
|
|
2223
|
-
const displayValue = typeof value === "string" && value.length > 200 ? value.slice(0, 200) + "..." : String(value);
|
|
2224
|
-
console.log(chalk8.dim(` ${key}: `) + displayValue);
|
|
2225
|
-
}
|
|
2226
|
-
console.log("");
|
|
2227
|
-
const rl = readline3.createInterface({
|
|
2228
|
-
input: process.stdin,
|
|
2229
|
-
output: process.stdout
|
|
2230
|
-
});
|
|
2231
|
-
try {
|
|
2232
|
-
const answer = await rl.question(
|
|
2233
|
-
chalk8.yellow(`Allow? [${chalk8.bold("Y")}es / ${chalk8.bold("n")}o / ${chalk8.bold("a")}lways for this tool] `)
|
|
2234
|
-
);
|
|
2235
|
-
rl.close();
|
|
2236
|
-
const choice = answer.trim().toLowerCase();
|
|
2237
|
-
if (choice === "a" || choice === "always") {
|
|
2238
|
-
sessionAllowed.add(tool.name);
|
|
2239
|
-
return true;
|
|
2240
|
-
}
|
|
2241
|
-
if (choice === "n" || choice === "no") {
|
|
2242
|
-
return false;
|
|
2243
|
-
}
|
|
2244
|
-
return true;
|
|
2245
|
-
} catch {
|
|
2246
|
-
rl.close();
|
|
2247
|
-
return false;
|
|
2248
|
-
}
|
|
2249
|
-
}
|
|
2250
|
-
|
|
2251
2957
|
// src/hooks/hook-manager.ts
|
|
2252
2958
|
init_esm_shims();
|
|
2253
2959
|
import { exec } from "child_process";
|
|
2254
|
-
import * as
|
|
2255
|
-
function
|
|
2256
|
-
if (
|
|
2960
|
+
import * as os10 from "os";
|
|
2961
|
+
function getDefaultShell2() {
|
|
2962
|
+
if (os10.platform() === "win32") {
|
|
2257
2963
|
return "powershell.exe";
|
|
2258
2964
|
}
|
|
2259
2965
|
return void 0;
|
|
@@ -2283,41 +2989,41 @@ var HookManager = class {
|
|
|
2283
2989
|
return { proceed: true };
|
|
2284
2990
|
}
|
|
2285
2991
|
async runCommandHook(command, context, timeout) {
|
|
2286
|
-
return new Promise((
|
|
2992
|
+
return new Promise((resolve11) => {
|
|
2287
2993
|
const stdinData = JSON.stringify({
|
|
2288
2994
|
tool: context["tool"],
|
|
2289
2995
|
args: context["args"],
|
|
2290
2996
|
session_id: context["sessionId"],
|
|
2291
2997
|
cwd: context["cwd"] || process.cwd()
|
|
2292
2998
|
});
|
|
2293
|
-
const isWin =
|
|
2999
|
+
const isWin = os10.platform() === "win32";
|
|
2294
3000
|
const finalCommand = isWin ? `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; ${command}` : command;
|
|
2295
3001
|
const proc = exec(finalCommand, {
|
|
2296
3002
|
timeout: timeout || 5e3,
|
|
2297
3003
|
cwd: process.cwd(),
|
|
2298
3004
|
env: { ...process.env },
|
|
2299
|
-
shell:
|
|
3005
|
+
shell: getDefaultShell2()
|
|
2300
3006
|
}, (err, stdout, stderr) => {
|
|
2301
3007
|
if (err) {
|
|
2302
3008
|
if (err.code === 2) {
|
|
2303
|
-
|
|
3009
|
+
resolve11({
|
|
2304
3010
|
proceed: false,
|
|
2305
3011
|
reason: stderr || stdout || "Blocked by hook"
|
|
2306
3012
|
});
|
|
2307
3013
|
return;
|
|
2308
3014
|
}
|
|
2309
|
-
|
|
3015
|
+
resolve11({ proceed: true });
|
|
2310
3016
|
return;
|
|
2311
3017
|
}
|
|
2312
3018
|
try {
|
|
2313
3019
|
const output3 = JSON.parse(stdout);
|
|
2314
|
-
|
|
3020
|
+
resolve11({
|
|
2315
3021
|
proceed: output3.decision !== "block",
|
|
2316
3022
|
reason: output3.reason,
|
|
2317
3023
|
updatedInput: output3.updatedInput
|
|
2318
3024
|
});
|
|
2319
3025
|
} catch {
|
|
2320
|
-
|
|
3026
|
+
resolve11({ proceed: true });
|
|
2321
3027
|
}
|
|
2322
3028
|
});
|
|
2323
3029
|
if (proc.stdin) {
|
|
@@ -2332,12 +3038,12 @@ var hookManager = new HookManager();
|
|
|
2332
3038
|
// src/mcp/mcp-manager.ts
|
|
2333
3039
|
init_esm_shims();
|
|
2334
3040
|
init_tool();
|
|
2335
|
-
import * as
|
|
2336
|
-
import * as
|
|
2337
|
-
import * as
|
|
3041
|
+
import * as fs10 from "fs";
|
|
3042
|
+
import * as os11 from "os";
|
|
3043
|
+
import * as path11 from "path";
|
|
2338
3044
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2339
3045
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
2340
|
-
import
|
|
3046
|
+
import chalk10 from "chalk";
|
|
2341
3047
|
var McpManager = class {
|
|
2342
3048
|
servers = /* @__PURE__ */ new Map();
|
|
2343
3049
|
async initialize(registry) {
|
|
@@ -2348,21 +3054,21 @@ var McpManager = class {
|
|
|
2348
3054
|
try {
|
|
2349
3055
|
await this.connectServer(name, serverConfig, registry);
|
|
2350
3056
|
} catch (err) {
|
|
2351
|
-
console.error(
|
|
3057
|
+
console.error(chalk10.yellow(` \u26A0 Failed to connect MCP server '${name}': ${err instanceof Error ? err.message : String(err)}`));
|
|
2352
3058
|
}
|
|
2353
3059
|
}
|
|
2354
3060
|
}
|
|
2355
3061
|
loadMcpConfigs() {
|
|
2356
3062
|
const configs = {};
|
|
2357
|
-
const home = process.env["HOME"] || process.env["USERPROFILE"] ||
|
|
3063
|
+
const home = process.env["HOME"] || process.env["USERPROFILE"] || os11.homedir();
|
|
2358
3064
|
const paths = [
|
|
2359
|
-
|
|
2360
|
-
|
|
3065
|
+
path11.join(home, ".codi", "mcp.json"),
|
|
3066
|
+
path11.join(process.cwd(), ".codi", "mcp.json")
|
|
2361
3067
|
];
|
|
2362
3068
|
for (const p of paths) {
|
|
2363
3069
|
try {
|
|
2364
|
-
if (
|
|
2365
|
-
const content = JSON.parse(
|
|
3070
|
+
if (fs10.existsSync(p)) {
|
|
3071
|
+
const content = JSON.parse(fs10.readFileSync(p, "utf-8"));
|
|
2366
3072
|
if (content.mcpServers) {
|
|
2367
3073
|
Object.assign(configs, content.mcpServers);
|
|
2368
3074
|
}
|
|
@@ -2414,7 +3120,7 @@ var McpManager = class {
|
|
|
2414
3120
|
registry.register(tool);
|
|
2415
3121
|
}
|
|
2416
3122
|
this.servers.set(name, { name, client, transport, tools: toolNames });
|
|
2417
|
-
console.log(
|
|
3123
|
+
console.log(chalk10.dim(` \u2713 MCP server '${name}' connected (${toolNames.length} tools)`));
|
|
2418
3124
|
}
|
|
2419
3125
|
async disconnect(name) {
|
|
2420
3126
|
const server = this.servers.get(name);
|
|
@@ -2445,7 +3151,7 @@ var mcpManager = new McpManager();
|
|
|
2445
3151
|
// src/agent/sub-agent.ts
|
|
2446
3152
|
init_esm_shims();
|
|
2447
3153
|
init_tool();
|
|
2448
|
-
import
|
|
3154
|
+
import chalk11 from "chalk";
|
|
2449
3155
|
var AGENT_PRESETS = {
|
|
2450
3156
|
explore: {
|
|
2451
3157
|
tools: ["read_file", "glob", "grep", "list_dir"],
|
|
@@ -2484,7 +3190,7 @@ function createSubAgentHandler(provider, mainRegistry) {
|
|
|
2484
3190
|
}
|
|
2485
3191
|
const conversation = new Conversation();
|
|
2486
3192
|
const maxIterations = input3["maxIterations"] || preset.maxIterations;
|
|
2487
|
-
console.log(
|
|
3193
|
+
console.log(chalk11.dim(`
|
|
2488
3194
|
\u25B8 Launching ${type} agent: ${task.slice(0, 80)}...`));
|
|
2489
3195
|
const runAgent = async () => {
|
|
2490
3196
|
const result = await agentLoop(task, {
|
|
@@ -2513,7 +3219,7 @@ function createSubAgentHandler(provider, mainRegistry) {
|
|
|
2513
3219
|
}
|
|
2514
3220
|
try {
|
|
2515
3221
|
const result = await runAgent();
|
|
2516
|
-
console.log(
|
|
3222
|
+
console.log(chalk11.dim(` \u25B8 ${type} agent completed.`));
|
|
2517
3223
|
return makeToolResult(result);
|
|
2518
3224
|
} catch (err) {
|
|
2519
3225
|
return makeToolError(`Sub-agent failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -2557,10 +3263,10 @@ var subAgentTool = {
|
|
|
2557
3263
|
|
|
2558
3264
|
// src/config/slash-commands.ts
|
|
2559
3265
|
init_esm_shims();
|
|
2560
|
-
import * as
|
|
2561
|
-
import * as
|
|
2562
|
-
import * as
|
|
2563
|
-
import
|
|
3266
|
+
import * as fs12 from "fs";
|
|
3267
|
+
import * as os13 from "os";
|
|
3268
|
+
import * as path13 from "path";
|
|
3269
|
+
import chalk12 from "chalk";
|
|
2564
3270
|
function createBuiltinCommands() {
|
|
2565
3271
|
return [
|
|
2566
3272
|
{
|
|
@@ -2568,14 +3274,14 @@ function createBuiltinCommands() {
|
|
|
2568
3274
|
description: "Show available commands",
|
|
2569
3275
|
handler: async () => {
|
|
2570
3276
|
const commands = createBuiltinCommands();
|
|
2571
|
-
console.log(
|
|
3277
|
+
console.log(chalk12.bold("\nAvailable Commands:\n"));
|
|
2572
3278
|
for (const cmd of commands) {
|
|
2573
|
-
const aliases = cmd.aliases ?
|
|
2574
|
-
console.log(` ${
|
|
3279
|
+
const aliases = cmd.aliases ? chalk12.dim(` (${cmd.aliases.join(", ")})`) : "";
|
|
3280
|
+
console.log(` ${chalk12.cyan(cmd.name)}${aliases} - ${cmd.description}`);
|
|
2575
3281
|
}
|
|
2576
3282
|
console.log("");
|
|
2577
|
-
console.log(
|
|
2578
|
-
console.log(
|
|
3283
|
+
console.log(chalk12.dim(" Prefixes: ! (bash), @ (file reference)"));
|
|
3284
|
+
console.log(chalk12.dim(" Use \\ at end of line for multiline input"));
|
|
2579
3285
|
console.log("");
|
|
2580
3286
|
return true;
|
|
2581
3287
|
}
|
|
@@ -2588,7 +3294,7 @@ function createBuiltinCommands() {
|
|
|
2588
3294
|
if (ctx.exitFn) {
|
|
2589
3295
|
await ctx.exitFn();
|
|
2590
3296
|
}
|
|
2591
|
-
console.log(
|
|
3297
|
+
console.log(chalk12.dim("\nGoodbye!\n"));
|
|
2592
3298
|
process.exit(0);
|
|
2593
3299
|
}
|
|
2594
3300
|
},
|
|
@@ -2599,7 +3305,7 @@ function createBuiltinCommands() {
|
|
|
2599
3305
|
handler: async (_args, ctx) => {
|
|
2600
3306
|
ctx.conversation.clear();
|
|
2601
3307
|
ctx.reloadSystemPrompt();
|
|
2602
|
-
console.log(
|
|
3308
|
+
console.log(chalk12.green("\u2713 Conversation cleared"));
|
|
2603
3309
|
return true;
|
|
2604
3310
|
}
|
|
2605
3311
|
},
|
|
@@ -2609,7 +3315,7 @@ function createBuiltinCommands() {
|
|
|
2609
3315
|
handler: async (args, ctx) => {
|
|
2610
3316
|
if (!args) {
|
|
2611
3317
|
const info = statusLine.getInfo();
|
|
2612
|
-
console.log(
|
|
3318
|
+
console.log(chalk12.cyan(`Current model: ${info.model} (${info.provider})`));
|
|
2613
3319
|
return true;
|
|
2614
3320
|
}
|
|
2615
3321
|
if (args.includes(":")) {
|
|
@@ -2618,7 +3324,7 @@ function createBuiltinCommands() {
|
|
|
2618
3324
|
} else {
|
|
2619
3325
|
ctx.setProvider("", args);
|
|
2620
3326
|
}
|
|
2621
|
-
console.log(
|
|
3327
|
+
console.log(chalk12.green(`\u2713 Model switched to: ${args}`));
|
|
2622
3328
|
return true;
|
|
2623
3329
|
}
|
|
2624
3330
|
},
|
|
@@ -2626,10 +3332,10 @@ function createBuiltinCommands() {
|
|
|
2626
3332
|
name: "/compact",
|
|
2627
3333
|
description: "Compress conversation history (optional: focus hint)",
|
|
2628
3334
|
handler: async (args, ctx) => {
|
|
2629
|
-
console.log(
|
|
3335
|
+
console.log(chalk12.dim("Compressing conversation..."));
|
|
2630
3336
|
await ctx.compressor.compress(ctx.conversation, ctx.provider, args || void 0);
|
|
2631
3337
|
ctx.reloadSystemPrompt();
|
|
2632
|
-
console.log(
|
|
3338
|
+
console.log(chalk12.green("\u2713 Conversation compressed"));
|
|
2633
3339
|
return true;
|
|
2634
3340
|
}
|
|
2635
3341
|
},
|
|
@@ -2637,9 +3343,31 @@ function createBuiltinCommands() {
|
|
|
2637
3343
|
name: "/cost",
|
|
2638
3344
|
description: "Show token usage and cost",
|
|
2639
3345
|
handler: async () => {
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
3346
|
+
const session = tokenTracker.getSessionStats();
|
|
3347
|
+
const total = tokenTracker.getStats();
|
|
3348
|
+
console.log(chalk12.bold("\nToken Usage & Cost:\n"));
|
|
3349
|
+
console.log(chalk12.cyan(" [Current Session]"));
|
|
3350
|
+
console.log(` Requests: ${session.requests}`);
|
|
3351
|
+
console.log(` Input: ${formatTokens(session.inputTokens)}`);
|
|
3352
|
+
console.log(` Output: ${formatTokens(session.outputTokens)}`);
|
|
3353
|
+
console.log(` Total: ${formatTokens(session.totalTokens)}`);
|
|
3354
|
+
console.log(` Cost: $${session.cost.toFixed(4)}`);
|
|
3355
|
+
if (session.requests > 0) {
|
|
3356
|
+
console.log(` Avg/Request: $${session.avgCostPerRequest.toFixed(4)}`);
|
|
3357
|
+
}
|
|
3358
|
+
if (session.lastRequestCost) {
|
|
3359
|
+
console.log(chalk12.dim(` Last Request: $${session.lastRequestCost.cost.toFixed(4)} (${formatTokens(session.lastRequestCost.inputTokens)} in / ${formatTokens(session.lastRequestCost.outputTokens)} out)`));
|
|
3360
|
+
}
|
|
3361
|
+
if (total.requests !== session.requests) {
|
|
3362
|
+
console.log("");
|
|
3363
|
+
console.log(chalk12.cyan(" [Total Accumulated]"));
|
|
3364
|
+
console.log(` Requests: ${total.requests}`);
|
|
3365
|
+
console.log(` Input: ${formatTokens(total.inputTokens)}`);
|
|
3366
|
+
console.log(` Output: ${formatTokens(total.outputTokens)}`);
|
|
3367
|
+
console.log(` Total: ${formatTokens(total.totalTokens)}`);
|
|
3368
|
+
console.log(` Cost: $${total.cost.toFixed(4)}`);
|
|
3369
|
+
}
|
|
3370
|
+
console.log("");
|
|
2643
3371
|
return true;
|
|
2644
3372
|
}
|
|
2645
3373
|
},
|
|
@@ -2648,9 +3376,9 @@ ${tokenTracker.format()}
|
|
|
2648
3376
|
description: "Show current configuration",
|
|
2649
3377
|
handler: async () => {
|
|
2650
3378
|
const config = configManager.get();
|
|
2651
|
-
console.log(
|
|
2652
|
-
console.log(
|
|
2653
|
-
console.log(
|
|
3379
|
+
console.log(chalk12.bold("\nConfiguration:\n"));
|
|
3380
|
+
console.log(chalk12.dim(JSON.stringify(config, null, 2)));
|
|
3381
|
+
console.log(chalk12.dim(`
|
|
2654
3382
|
Config files: ${configManager.getConfigPaths().join(", ") || "(none)"}`));
|
|
2655
3383
|
console.log("");
|
|
2656
3384
|
return true;
|
|
@@ -2661,10 +3389,10 @@ Config files: ${configManager.getConfigPaths().join(", ") || "(none)"}`));
|
|
|
2661
3389
|
description: "Show permission rules",
|
|
2662
3390
|
handler: async () => {
|
|
2663
3391
|
const config = configManager.get();
|
|
2664
|
-
console.log(
|
|
2665
|
-
console.log(
|
|
2666
|
-
console.log(
|
|
2667
|
-
console.log(
|
|
3392
|
+
console.log(chalk12.bold("\nPermission Rules:\n"));
|
|
3393
|
+
console.log(chalk12.green(" Allow: ") + config.permissions.allow.join(", "));
|
|
3394
|
+
console.log(chalk12.red(" Deny: ") + (config.permissions.deny.join(", ") || "(none)"));
|
|
3395
|
+
console.log(chalk12.yellow(" Ask: ") + config.permissions.ask.join(", "));
|
|
2668
3396
|
console.log("");
|
|
2669
3397
|
return true;
|
|
2670
3398
|
}
|
|
@@ -2675,7 +3403,7 @@ Config files: ${configManager.getConfigPaths().join(", ") || "(none)"}`));
|
|
|
2675
3403
|
handler: async (args, ctx) => {
|
|
2676
3404
|
const name = args || void 0;
|
|
2677
3405
|
const id = sessionManager.save(ctx.conversation, name, statusLine.getInfo().model);
|
|
2678
|
-
console.log(
|
|
3406
|
+
console.log(chalk12.green(`\u2713 Session saved: ${id}`));
|
|
2679
3407
|
return true;
|
|
2680
3408
|
}
|
|
2681
3409
|
},
|
|
@@ -2686,12 +3414,12 @@ Config files: ${configManager.getConfigPaths().join(", ") || "(none)"}`));
|
|
|
2686
3414
|
handler: async (args, ctx) => {
|
|
2687
3415
|
const id = args || sessionManager.getLatest()?.id;
|
|
2688
3416
|
if (!id) {
|
|
2689
|
-
console.log(
|
|
3417
|
+
console.log(chalk12.yellow("No sessions found. Use /save to save a session first."));
|
|
2690
3418
|
return true;
|
|
2691
3419
|
}
|
|
2692
3420
|
const session = sessionManager.load(id);
|
|
2693
3421
|
if (!session) {
|
|
2694
|
-
console.log(
|
|
3422
|
+
console.log(chalk12.red(`Session not found: ${id}`));
|
|
2695
3423
|
return true;
|
|
2696
3424
|
}
|
|
2697
3425
|
const data = session.conversation.serialize();
|
|
@@ -2701,7 +3429,7 @@ Config files: ${configManager.getConfigPaths().join(", ") || "(none)"}`));
|
|
|
2701
3429
|
if (msg.role === "user") ctx.conversation.addUserMessage(msg.content);
|
|
2702
3430
|
else if (msg.role === "assistant") ctx.conversation.addAssistantMessage(msg.content);
|
|
2703
3431
|
}
|
|
2704
|
-
console.log(
|
|
3432
|
+
console.log(chalk12.green(`\u2713 Resumed session: ${id} (${session.meta.messageCount} messages)`));
|
|
2705
3433
|
return true;
|
|
2706
3434
|
}
|
|
2707
3435
|
},
|
|
@@ -2711,7 +3439,7 @@ Config files: ${configManager.getConfigPaths().join(", ") || "(none)"}`));
|
|
|
2711
3439
|
handler: async (args, ctx) => {
|
|
2712
3440
|
const name = args || `fork-${Date.now()}`;
|
|
2713
3441
|
const id = sessionManager.save(ctx.conversation, name, statusLine.getInfo().model);
|
|
2714
|
-
console.log(
|
|
3442
|
+
console.log(chalk12.green(`\u2713 Conversation forked: ${id}`));
|
|
2715
3443
|
return true;
|
|
2716
3444
|
}
|
|
2717
3445
|
},
|
|
@@ -2723,7 +3451,7 @@ Config files: ${configManager.getConfigPaths().join(", ") || "(none)"}`));
|
|
|
2723
3451
|
const newMode = current === "plan" ? "execute" : "plan";
|
|
2724
3452
|
setMode(newMode);
|
|
2725
3453
|
statusLine.update({ mode: newMode });
|
|
2726
|
-
console.log(
|
|
3454
|
+
console.log(chalk12.cyan(`Mode: ${newMode === "plan" ? "PLAN (read-only)" : "EXECUTE"}`));
|
|
2727
3455
|
return true;
|
|
2728
3456
|
}
|
|
2729
3457
|
},
|
|
@@ -2733,16 +3461,16 @@ Config files: ${configManager.getConfigPaths().join(", ") || "(none)"}`));
|
|
|
2733
3461
|
handler: async () => {
|
|
2734
3462
|
const index = memoryManager.loadIndex();
|
|
2735
3463
|
const topics = memoryManager.listTopics();
|
|
2736
|
-
console.log(
|
|
2737
|
-
console.log(
|
|
3464
|
+
console.log(chalk12.bold("\nAuto Memory:\n"));
|
|
3465
|
+
console.log(chalk12.dim(`Directory: ${memoryManager.getMemoryDir()}`));
|
|
2738
3466
|
if (index) {
|
|
2739
|
-
console.log(
|
|
3467
|
+
console.log(chalk12.dim("\nMEMORY.md:"));
|
|
2740
3468
|
console.log(index);
|
|
2741
3469
|
} else {
|
|
2742
|
-
console.log(
|
|
3470
|
+
console.log(chalk12.dim("\nNo memory saved yet."));
|
|
2743
3471
|
}
|
|
2744
3472
|
if (topics.length > 0) {
|
|
2745
|
-
console.log(
|
|
3473
|
+
console.log(chalk12.dim(`
|
|
2746
3474
|
Topics: ${topics.join(", ")}`));
|
|
2747
3475
|
}
|
|
2748
3476
|
console.log("");
|
|
@@ -2753,12 +3481,12 @@ Topics: ${topics.join(", ")}`));
|
|
|
2753
3481
|
name: "/init",
|
|
2754
3482
|
description: "Initialize CODI.md in the current project",
|
|
2755
3483
|
handler: async () => {
|
|
2756
|
-
const codiPath =
|
|
2757
|
-
if (
|
|
2758
|
-
console.log(
|
|
3484
|
+
const codiPath = path13.join(process.cwd(), "CODI.md");
|
|
3485
|
+
if (fs12.existsSync(codiPath)) {
|
|
3486
|
+
console.log(chalk12.yellow("CODI.md already exists"));
|
|
2759
3487
|
return true;
|
|
2760
3488
|
}
|
|
2761
|
-
const content = `# Project: ${
|
|
3489
|
+
const content = `# Project: ${path13.basename(process.cwd())}
|
|
2762
3490
|
|
|
2763
3491
|
## Overview
|
|
2764
3492
|
<!-- Describe your project here -->
|
|
@@ -2772,8 +3500,8 @@ Topics: ${topics.join(", ")}`));
|
|
|
2772
3500
|
## Conventions
|
|
2773
3501
|
<!-- Code style, naming conventions, etc. -->
|
|
2774
3502
|
`;
|
|
2775
|
-
|
|
2776
|
-
console.log(
|
|
3503
|
+
fs12.writeFileSync(codiPath, content, "utf-8");
|
|
3504
|
+
console.log(chalk12.green("\u2713 Created CODI.md"));
|
|
2777
3505
|
return true;
|
|
2778
3506
|
}
|
|
2779
3507
|
},
|
|
@@ -2801,8 +3529,8 @@ ${content}
|
|
|
2801
3529
|
|
|
2802
3530
|
`;
|
|
2803
3531
|
}
|
|
2804
|
-
|
|
2805
|
-
console.log(
|
|
3532
|
+
fs12.writeFileSync(filePath, md, "utf-8");
|
|
3533
|
+
console.log(chalk12.green(`\u2713 Exported to ${filePath}`));
|
|
2806
3534
|
return true;
|
|
2807
3535
|
}
|
|
2808
3536
|
},
|
|
@@ -2813,12 +3541,12 @@ ${content}
|
|
|
2813
3541
|
const { taskManager: taskManager2 } = await Promise.resolve().then(() => (init_task_tools(), task_tools_exports));
|
|
2814
3542
|
const tasks = taskManager2.list();
|
|
2815
3543
|
if (tasks.length === 0) {
|
|
2816
|
-
console.log(
|
|
3544
|
+
console.log(chalk12.dim("\nNo tasks.\n"));
|
|
2817
3545
|
return true;
|
|
2818
3546
|
}
|
|
2819
|
-
console.log(
|
|
3547
|
+
console.log(chalk12.bold("\nTasks:\n"));
|
|
2820
3548
|
for (const task of tasks) {
|
|
2821
|
-
const statusIcon = task.status === "completed" ?
|
|
3549
|
+
const statusIcon = task.status === "completed" ? chalk12.green("\u2713") : task.status === "in_progress" ? chalk12.yellow("\u27F3") : chalk12.dim("\u25CB");
|
|
2822
3550
|
console.log(` ${statusIcon} #${task.id} ${task.subject} [${task.status}]`);
|
|
2823
3551
|
}
|
|
2824
3552
|
console.log("");
|
|
@@ -2833,7 +3561,7 @@ ${content}
|
|
|
2833
3561
|
const info = statusLine.getInfo();
|
|
2834
3562
|
const stats = tokenTracker.getStats();
|
|
2835
3563
|
const mcpServers = mcpManager.listServers();
|
|
2836
|
-
console.log(
|
|
3564
|
+
console.log(chalk12.bold("\nCodi Status:\n"));
|
|
2837
3565
|
console.log(` Version: 0.1.0`);
|
|
2838
3566
|
console.log(` Model: ${info.model}`);
|
|
2839
3567
|
console.log(` Provider: ${config.provider}`);
|
|
@@ -2855,7 +3583,7 @@ ${content}
|
|
|
2855
3583
|
const max = 2e5;
|
|
2856
3584
|
const pct = Math.round(estimated / max * 100);
|
|
2857
3585
|
const bar = "\u2588".repeat(Math.round(pct / 5)) + "\u2591".repeat(20 - Math.round(pct / 5));
|
|
2858
|
-
console.log(
|
|
3586
|
+
console.log(chalk12.bold("\nContext Window:\n"));
|
|
2859
3587
|
console.log(` ${bar} ${pct}%`);
|
|
2860
3588
|
console.log(` ~${estimated.toLocaleString()} / ${max.toLocaleString()} tokens`);
|
|
2861
3589
|
console.log(` Messages: ${ctx.conversation.getMessageCount()}`);
|
|
@@ -2869,12 +3597,12 @@ ${content}
|
|
|
2869
3597
|
handler: async (_args, ctx) => {
|
|
2870
3598
|
const checkpoints = checkpointManager.list();
|
|
2871
3599
|
if (checkpoints.length === 0) {
|
|
2872
|
-
console.log(
|
|
3600
|
+
console.log(chalk12.yellow("No checkpoints available."));
|
|
2873
3601
|
return true;
|
|
2874
3602
|
}
|
|
2875
3603
|
const result = checkpointManager.rewind();
|
|
2876
3604
|
if (!result) {
|
|
2877
|
-
console.log(
|
|
3605
|
+
console.log(chalk12.yellow("No checkpoint to rewind to."));
|
|
2878
3606
|
return true;
|
|
2879
3607
|
}
|
|
2880
3608
|
const data = result.conversation.serialize();
|
|
@@ -2884,7 +3612,7 @@ ${content}
|
|
|
2884
3612
|
if (msg.role === "user") ctx.conversation.addUserMessage(msg.content);
|
|
2885
3613
|
else if (msg.role === "assistant") ctx.conversation.addAssistantMessage(msg.content);
|
|
2886
3614
|
}
|
|
2887
|
-
console.log(
|
|
3615
|
+
console.log(chalk12.green(`\u2713 Rewound to checkpoint${result.description ? `: ${result.description}` : ""}`));
|
|
2888
3616
|
return true;
|
|
2889
3617
|
}
|
|
2890
3618
|
},
|
|
@@ -2896,13 +3624,13 @@ ${content}
|
|
|
2896
3624
|
const { execSync: execSync5 } = await import("child_process");
|
|
2897
3625
|
const diff = execSync5("git diff", { encoding: "utf-8", cwd: process.cwd() });
|
|
2898
3626
|
if (!diff.trim()) {
|
|
2899
|
-
console.log(
|
|
3627
|
+
console.log(chalk12.dim("\nNo changes.\n"));
|
|
2900
3628
|
} else {
|
|
2901
3629
|
const { renderDiff: renderDiff2 } = await Promise.resolve().then(() => (init_renderer(), renderer_exports));
|
|
2902
3630
|
console.log("\n" + renderDiff2("", "", diff) + "\n");
|
|
2903
3631
|
}
|
|
2904
3632
|
} catch {
|
|
2905
|
-
console.log(
|
|
3633
|
+
console.log(chalk12.yellow("Not a git repository or git not available."));
|
|
2906
3634
|
}
|
|
2907
3635
|
return true;
|
|
2908
3636
|
}
|
|
@@ -2915,21 +3643,64 @@ ${content}
|
|
|
2915
3643
|
try {
|
|
2916
3644
|
const staged = execSync5("git diff --cached", { encoding: "utf-8", cwd: process.cwd() });
|
|
2917
3645
|
const unstaged = execSync5("git diff", { encoding: "utf-8", cwd: process.cwd() });
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
3646
|
+
if (!staged.trim() && unstaged.trim()) {
|
|
3647
|
+
const untracked = execSync5("git ls-files --others --exclude-standard", {
|
|
3648
|
+
encoding: "utf-8",
|
|
3649
|
+
cwd: process.cwd()
|
|
3650
|
+
}).trim();
|
|
3651
|
+
if (untracked) {
|
|
3652
|
+
console.log(chalk12.yellow(`
|
|
3653
|
+
\uC8FC\uC758: \uCD94\uC801\uB418\uC9C0 \uC54A\uB294 \uD30C\uC77C\uC774 \uC788\uC2B5\uB2C8\uB2E4 (\uC790\uB3D9 \uC2A4\uD14C\uC774\uC9D5 \uC548 \uB428):`));
|
|
3654
|
+
for (const f of untracked.split("\n").slice(0, 10)) {
|
|
3655
|
+
console.log(chalk12.dim(` ${f}`));
|
|
3656
|
+
}
|
|
3657
|
+
if (untracked.split("\n").length > 10) {
|
|
3658
|
+
console.log(chalk12.dim(` ... \uC678 ${untracked.split("\n").length - 10}\uAC1C`));
|
|
3659
|
+
}
|
|
3660
|
+
console.log("");
|
|
3661
|
+
}
|
|
3662
|
+
console.log(chalk12.dim("\uC218\uC815\uB41C \uD30C\uC77C\uC744 \uC790\uB3D9 \uC2A4\uD14C\uC774\uC9D5\uD569\uB2C8\uB2E4..."));
|
|
3663
|
+
execSync5("git add -u", { encoding: "utf-8", cwd: process.cwd() });
|
|
3664
|
+
}
|
|
3665
|
+
const finalDiff = execSync5("git diff --cached", { encoding: "utf-8", cwd: process.cwd() });
|
|
3666
|
+
if (!finalDiff.trim()) {
|
|
3667
|
+
console.log(chalk12.dim("\nNo changes to commit.\n"));
|
|
2921
3668
|
return true;
|
|
2922
3669
|
}
|
|
3670
|
+
let conventionHint = "";
|
|
3671
|
+
try {
|
|
3672
|
+
const recentLog = execSync5("git log --oneline -10", {
|
|
3673
|
+
encoding: "utf-8",
|
|
3674
|
+
cwd: process.cwd()
|
|
3675
|
+
}).trim();
|
|
3676
|
+
if (recentLog) {
|
|
3677
|
+
const conventionalPattern = /^[a-f0-9]+\s+(feat|fix|chore|docs|style|refactor|perf|test|build|ci|revert)(\(.+\))?[!]?:/;
|
|
3678
|
+
const lines = recentLog.split("\n");
|
|
3679
|
+
const conventionalCount = lines.filter((l) => conventionalPattern.test(l)).length;
|
|
3680
|
+
if (conventionalCount >= 3) {
|
|
3681
|
+
conventionHint = `
|
|
3682
|
+
|
|
3683
|
+
\uC774 \uD504\uB85C\uC81D\uD2B8\uB294 Conventional Commits \uD615\uC2DD\uC744 \uC0AC\uC6A9\uD569\uB2C8\uB2E4 (\uC608: feat:, fix:, chore: \uB4F1). \uAC19\uC740 \uD615\uC2DD\uC744 \uB530\uB77C\uC8FC\uC138\uC694.`;
|
|
3684
|
+
}
|
|
3685
|
+
conventionHint += `
|
|
3686
|
+
|
|
3687
|
+
\uCD5C\uADFC \uCEE4\uBC0B \uCC38\uACE0:
|
|
3688
|
+
\`\`\`
|
|
3689
|
+
${recentLog}
|
|
3690
|
+
\`\`\``;
|
|
3691
|
+
}
|
|
3692
|
+
} catch {
|
|
3693
|
+
}
|
|
2923
3694
|
ctx.conversation.addUserMessage(
|
|
2924
|
-
`\uB2E4\uC74C git diff\uB97C \uBD84\uC11D\uD574\uC11C \uC801\uC808\uD55C \uCEE4\uBC0B \uBA54\uC2DC\uC9C0\uB97C \uC0DD\uC131\uD558\uACE0, git \uB3C4\uAD6C\uB85C \
|
|
3695
|
+
`\uB2E4\uC74C git diff\uB97C \uBD84\uC11D\uD574\uC11C \uC801\uC808\uD55C \uCEE4\uBC0B \uBA54\uC2DC\uC9C0\uB97C \uC0DD\uC131\uD558\uACE0, git \uB3C4\uAD6C\uB85C \uCEE4\uBC0B\uD574\uC918. \uC774\uBBF8 \uC2A4\uD14C\uC774\uC9D5 \uC644\uB8CC\uB418\uC5C8\uC73C\uBBC0\uB85C add \uC5C6\uC774 commit\uB9CC \uD558\uBA74 \uB429\uB2C8\uB2E4.${conventionHint}
|
|
2925
3696
|
|
|
2926
3697
|
\`\`\`diff
|
|
2927
|
-
${
|
|
3698
|
+
${finalDiff}
|
|
2928
3699
|
\`\`\``
|
|
2929
3700
|
);
|
|
2930
3701
|
return false;
|
|
2931
3702
|
} catch {
|
|
2932
|
-
console.log(
|
|
3703
|
+
console.log(chalk12.yellow("Not a git repository or git not available."));
|
|
2933
3704
|
return true;
|
|
2934
3705
|
}
|
|
2935
3706
|
}
|
|
@@ -2944,7 +3715,7 @@ ${diff}
|
|
|
2944
3715
|
const unstaged = execSync5("git diff", { encoding: "utf-8", cwd: process.cwd() });
|
|
2945
3716
|
const diff = staged + unstaged;
|
|
2946
3717
|
if (!diff.trim()) {
|
|
2947
|
-
console.log(
|
|
3718
|
+
console.log(chalk12.dim("\nNo changes to review.\n"));
|
|
2948
3719
|
return true;
|
|
2949
3720
|
}
|
|
2950
3721
|
ctx.conversation.addUserMessage(
|
|
@@ -2956,7 +3727,7 @@ ${diff}
|
|
|
2956
3727
|
);
|
|
2957
3728
|
return false;
|
|
2958
3729
|
} catch {
|
|
2959
|
-
console.log(
|
|
3730
|
+
console.log(chalk12.yellow("Not a git repository or git not available."));
|
|
2960
3731
|
return true;
|
|
2961
3732
|
}
|
|
2962
3733
|
}
|
|
@@ -2966,27 +3737,27 @@ ${diff}
|
|
|
2966
3737
|
description: "Search past conversation sessions",
|
|
2967
3738
|
handler: async (args) => {
|
|
2968
3739
|
if (!args) {
|
|
2969
|
-
console.log(
|
|
3740
|
+
console.log(chalk12.yellow("Usage: /search <keyword>"));
|
|
2970
3741
|
return true;
|
|
2971
3742
|
}
|
|
2972
|
-
const home = process.env["HOME"] || process.env["USERPROFILE"] ||
|
|
2973
|
-
const sessionsDir =
|
|
2974
|
-
if (!
|
|
2975
|
-
console.log(
|
|
3743
|
+
const home = process.env["HOME"] || process.env["USERPROFILE"] || os13.homedir();
|
|
3744
|
+
const sessionsDir = path13.join(home, ".codi", "sessions");
|
|
3745
|
+
if (!fs12.existsSync(sessionsDir)) {
|
|
3746
|
+
console.log(chalk12.dim("\nNo sessions found.\n"));
|
|
2976
3747
|
return true;
|
|
2977
3748
|
}
|
|
2978
|
-
const files =
|
|
3749
|
+
const files = fs12.readdirSync(sessionsDir).filter((f) => f.endsWith(".jsonl"));
|
|
2979
3750
|
const results = [];
|
|
2980
3751
|
const keyword = args.toLowerCase();
|
|
2981
3752
|
for (const file of files) {
|
|
2982
3753
|
if (results.length >= 10) break;
|
|
2983
|
-
const filePath =
|
|
2984
|
-
const lines =
|
|
3754
|
+
const filePath = path13.join(sessionsDir, file);
|
|
3755
|
+
const lines = fs12.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
2985
3756
|
for (const line of lines) {
|
|
2986
3757
|
if (results.length >= 10) break;
|
|
2987
3758
|
if (line.toLowerCase().includes(keyword)) {
|
|
2988
3759
|
const sessionId = file.replace(".jsonl", "");
|
|
2989
|
-
const stat =
|
|
3760
|
+
const stat = fs12.statSync(filePath);
|
|
2990
3761
|
const date = stat.mtime.toISOString().split("T")[0];
|
|
2991
3762
|
const preview = line.length > 100 ? line.slice(0, 100) + "..." : line;
|
|
2992
3763
|
results.push({ sessionId, date, preview });
|
|
@@ -2995,16 +3766,16 @@ ${diff}
|
|
|
2995
3766
|
}
|
|
2996
3767
|
}
|
|
2997
3768
|
if (results.length === 0) {
|
|
2998
|
-
console.log(
|
|
3769
|
+
console.log(chalk12.dim(`
|
|
2999
3770
|
No results for "${args}".
|
|
3000
3771
|
`));
|
|
3001
3772
|
} else {
|
|
3002
|
-
console.log(
|
|
3773
|
+
console.log(chalk12.bold(`
|
|
3003
3774
|
Search results for "${args}":
|
|
3004
3775
|
`));
|
|
3005
3776
|
for (const r of results) {
|
|
3006
|
-
console.log(` ${
|
|
3007
|
-
console.log(` ${
|
|
3777
|
+
console.log(` ${chalk12.cyan(r.sessionId)} ${chalk12.dim(r.date)}`);
|
|
3778
|
+
console.log(` ${chalk12.dim(r.preview)}`);
|
|
3008
3779
|
}
|
|
3009
3780
|
console.log("");
|
|
3010
3781
|
}
|
|
@@ -3016,24 +3787,24 @@ Search results for "${args}":
|
|
|
3016
3787
|
description: "Run a command and auto-fix errors (e.g., /fix npm run build)",
|
|
3017
3788
|
handler: async (args, ctx) => {
|
|
3018
3789
|
if (!args) {
|
|
3019
|
-
console.log(
|
|
3790
|
+
console.log(chalk12.yellow("Usage: /fix <command>"));
|
|
3020
3791
|
return true;
|
|
3021
3792
|
}
|
|
3022
3793
|
const { execSync: execSync5 } = await import("child_process");
|
|
3023
3794
|
try {
|
|
3024
|
-
const isWin =
|
|
3795
|
+
const isWin = os13.platform() === "win32";
|
|
3025
3796
|
const shell = isWin ? "powershell.exe" : void 0;
|
|
3026
3797
|
const fixCmd = isWin ? `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; ${args}` : args;
|
|
3027
3798
|
const output3 = execSync5(fixCmd, { encoding: "utf-8", cwd: process.cwd(), stdio: "pipe", shell });
|
|
3028
|
-
console.log(
|
|
3799
|
+
console.log(chalk12.green(`
|
|
3029
3800
|
\u2713 Command succeeded. No errors to fix.
|
|
3030
3801
|
`));
|
|
3031
|
-
if (output3.trim()) console.log(
|
|
3802
|
+
if (output3.trim()) console.log(chalk12.dim(output3));
|
|
3032
3803
|
return true;
|
|
3033
3804
|
} catch (err) {
|
|
3034
3805
|
const error = err;
|
|
3035
3806
|
const errorOutput = (error.stderr || "") + (error.stdout || "");
|
|
3036
|
-
console.log(
|
|
3807
|
+
console.log(chalk12.red(`
|
|
3037
3808
|
Command failed: ${args}
|
|
3038
3809
|
`));
|
|
3039
3810
|
ctx.conversation.addUserMessage(
|
|
@@ -3049,21 +3820,236 @@ ${errorOutput}
|
|
|
3049
3820
|
}
|
|
3050
3821
|
}
|
|
3051
3822
|
},
|
|
3823
|
+
{
|
|
3824
|
+
name: "/undo",
|
|
3825
|
+
description: "Undo the most recent file edit (rollback from backup)",
|
|
3826
|
+
handler: async (args) => {
|
|
3827
|
+
const { getBackupHistory: getBackupHistory2, undoLast: undoLast2 } = await Promise.resolve().then(() => (init_file_backup(), file_backup_exports));
|
|
3828
|
+
if (args === "list") {
|
|
3829
|
+
const history = getBackupHistory2();
|
|
3830
|
+
if (history.length === 0) {
|
|
3831
|
+
console.log(chalk12.dim("\n\uB418\uB3CC\uB9B4 \uD30C\uC77C \uBCC0\uACBD \uC774\uB825\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n"));
|
|
3832
|
+
return true;
|
|
3833
|
+
}
|
|
3834
|
+
console.log(chalk12.bold(`
|
|
3835
|
+
\uD30C\uC77C \uBCC0\uACBD \uC774\uB825 (\uCD5C\uADFC ${Math.min(history.length, 20)}\uAC1C):
|
|
3836
|
+
`));
|
|
3837
|
+
const recent = history.slice(-20).reverse();
|
|
3838
|
+
for (let i = 0; i < recent.length; i++) {
|
|
3839
|
+
const entry2 = recent[i];
|
|
3840
|
+
const time = new Date(entry2.timestamp).toLocaleTimeString();
|
|
3841
|
+
const tag = entry2.wasNew ? chalk12.yellow("[\uC0C8 \uD30C\uC77C]") : chalk12.cyan("[\uC218\uC815]");
|
|
3842
|
+
console.log(` ${i + 1}. ${tag} ${entry2.originalPath} ${chalk12.dim(time)}`);
|
|
3843
|
+
}
|
|
3844
|
+
console.log("");
|
|
3845
|
+
return true;
|
|
3846
|
+
}
|
|
3847
|
+
const entry = undoLast2();
|
|
3848
|
+
if (!entry) {
|
|
3849
|
+
console.log(chalk12.yellow("\n\uB418\uB3CC\uB9B4 \uBCC0\uACBD \uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n"));
|
|
3850
|
+
return true;
|
|
3851
|
+
}
|
|
3852
|
+
const action = entry.wasNew ? "\uC0AD\uC81C\uB428 (\uC0C8\uB85C \uC0DD\uC131\uB41C \uD30C\uC77C)" : "\uC774\uC804 \uC0C1\uD0DC\uB85C \uBCF5\uC6D0\uB428";
|
|
3853
|
+
console.log(chalk12.green(`
|
|
3854
|
+
\u2713 \uB418\uB3CC\uB9AC\uAE30 \uC644\uB8CC: ${entry.originalPath}`));
|
|
3855
|
+
console.log(chalk12.dim(` ${action}`));
|
|
3856
|
+
console.log("");
|
|
3857
|
+
return true;
|
|
3858
|
+
}
|
|
3859
|
+
},
|
|
3860
|
+
{
|
|
3861
|
+
name: "/branch",
|
|
3862
|
+
description: "Create and switch to a new branch, or show current branch",
|
|
3863
|
+
handler: async (args) => {
|
|
3864
|
+
const { execSync: execSync5 } = await import("child_process");
|
|
3865
|
+
try {
|
|
3866
|
+
if (!args) {
|
|
3867
|
+
const current = execSync5("git branch --show-current", {
|
|
3868
|
+
encoding: "utf-8",
|
|
3869
|
+
cwd: process.cwd()
|
|
3870
|
+
}).trim();
|
|
3871
|
+
const branches = execSync5("git branch -a", {
|
|
3872
|
+
encoding: "utf-8",
|
|
3873
|
+
cwd: process.cwd()
|
|
3874
|
+
}).trim();
|
|
3875
|
+
console.log(chalk12.bold(`
|
|
3876
|
+
Current branch: ${chalk12.green(current || "(detached HEAD)")}
|
|
3877
|
+
`));
|
|
3878
|
+
console.log(branches);
|
|
3879
|
+
console.log("");
|
|
3880
|
+
} else {
|
|
3881
|
+
const name = args.trim();
|
|
3882
|
+
execSync5(`git checkout -b ${name}`, {
|
|
3883
|
+
encoding: "utf-8",
|
|
3884
|
+
cwd: process.cwd()
|
|
3885
|
+
});
|
|
3886
|
+
console.log(chalk12.green(`
|
|
3887
|
+
\u2713 Created and switched to branch: ${name}
|
|
3888
|
+
`));
|
|
3889
|
+
}
|
|
3890
|
+
} catch (err) {
|
|
3891
|
+
const error = err;
|
|
3892
|
+
const msg = error.stderr || error.message || "Unknown error";
|
|
3893
|
+
console.log(chalk12.red(`
|
|
3894
|
+
Branch operation failed: ${msg.trim()}
|
|
3895
|
+
`));
|
|
3896
|
+
}
|
|
3897
|
+
return true;
|
|
3898
|
+
}
|
|
3899
|
+
},
|
|
3900
|
+
{
|
|
3901
|
+
name: "/stash",
|
|
3902
|
+
description: "Git stash management (pop, list, drop, or save)",
|
|
3903
|
+
handler: async (args) => {
|
|
3904
|
+
const { execSync: execSync5 } = await import("child_process");
|
|
3905
|
+
const sub = args.trim().split(/\s+/);
|
|
3906
|
+
const action = sub[0] || "push";
|
|
3907
|
+
const allowed = ["push", "pop", "list", "drop", "show", "apply", "clear"];
|
|
3908
|
+
if (!allowed.includes(action)) {
|
|
3909
|
+
console.log(chalk12.yellow(`Usage: /stash [${allowed.join("|")}]`));
|
|
3910
|
+
return true;
|
|
3911
|
+
}
|
|
3912
|
+
try {
|
|
3913
|
+
if (action === "clear") {
|
|
3914
|
+
console.log(chalk12.yellow("\u26A0 This will drop all stashes permanently."));
|
|
3915
|
+
}
|
|
3916
|
+
const cmd = `git stash ${args.trim() || "push"}`;
|
|
3917
|
+
const output3 = execSync5(cmd, {
|
|
3918
|
+
encoding: "utf-8",
|
|
3919
|
+
cwd: process.cwd()
|
|
3920
|
+
});
|
|
3921
|
+
console.log(output3.trim() ? `
|
|
3922
|
+
${output3.trim()}
|
|
3923
|
+
` : chalk12.dim("\n(no output)\n"));
|
|
3924
|
+
} catch (err) {
|
|
3925
|
+
const error = err;
|
|
3926
|
+
const msg = error.stderr || error.stdout || error.message || "Unknown error";
|
|
3927
|
+
console.log(chalk12.red(`
|
|
3928
|
+
Stash operation failed: ${msg.trim()}
|
|
3929
|
+
`));
|
|
3930
|
+
}
|
|
3931
|
+
return true;
|
|
3932
|
+
}
|
|
3933
|
+
},
|
|
3934
|
+
{
|
|
3935
|
+
name: "/pr",
|
|
3936
|
+
description: "Generate a pull request description from current branch diff",
|
|
3937
|
+
handler: async (_args, ctx) => {
|
|
3938
|
+
const { execSync: execSync5 } = await import("child_process");
|
|
3939
|
+
try {
|
|
3940
|
+
const currentBranch = execSync5("git branch --show-current", {
|
|
3941
|
+
encoding: "utf-8",
|
|
3942
|
+
cwd: process.cwd()
|
|
3943
|
+
}).trim();
|
|
3944
|
+
if (!currentBranch) {
|
|
3945
|
+
console.log(chalk12.yellow("Not on a branch (detached HEAD)."));
|
|
3946
|
+
return true;
|
|
3947
|
+
}
|
|
3948
|
+
let baseBranch = "main";
|
|
3949
|
+
try {
|
|
3950
|
+
execSync5("git rev-parse --verify main", {
|
|
3951
|
+
encoding: "utf-8",
|
|
3952
|
+
cwd: process.cwd(),
|
|
3953
|
+
stdio: "pipe"
|
|
3954
|
+
});
|
|
3955
|
+
} catch {
|
|
3956
|
+
try {
|
|
3957
|
+
execSync5("git rev-parse --verify master", {
|
|
3958
|
+
encoding: "utf-8",
|
|
3959
|
+
cwd: process.cwd(),
|
|
3960
|
+
stdio: "pipe"
|
|
3961
|
+
});
|
|
3962
|
+
baseBranch = "master";
|
|
3963
|
+
} catch {
|
|
3964
|
+
console.log(chalk12.yellow("Cannot find base branch (main or master)."));
|
|
3965
|
+
return true;
|
|
3966
|
+
}
|
|
3967
|
+
}
|
|
3968
|
+
if (currentBranch === baseBranch) {
|
|
3969
|
+
console.log(chalk12.yellow(`Already on ${baseBranch}. Switch to a feature branch first.`));
|
|
3970
|
+
return true;
|
|
3971
|
+
}
|
|
3972
|
+
let commitLog = "";
|
|
3973
|
+
try {
|
|
3974
|
+
commitLog = execSync5(`git log ${baseBranch}..HEAD --oneline`, {
|
|
3975
|
+
encoding: "utf-8",
|
|
3976
|
+
cwd: process.cwd()
|
|
3977
|
+
}).trim();
|
|
3978
|
+
} catch {
|
|
3979
|
+
}
|
|
3980
|
+
if (!commitLog) {
|
|
3981
|
+
console.log(chalk12.yellow(`No commits ahead of ${baseBranch}.`));
|
|
3982
|
+
return true;
|
|
3983
|
+
}
|
|
3984
|
+
let diffStat = "";
|
|
3985
|
+
try {
|
|
3986
|
+
diffStat = execSync5(`git diff ${baseBranch}...HEAD --stat`, {
|
|
3987
|
+
encoding: "utf-8",
|
|
3988
|
+
cwd: process.cwd()
|
|
3989
|
+
}).trim();
|
|
3990
|
+
} catch {
|
|
3991
|
+
}
|
|
3992
|
+
let diff = "";
|
|
3993
|
+
try {
|
|
3994
|
+
diff = execSync5(`git diff ${baseBranch}...HEAD`, {
|
|
3995
|
+
encoding: "utf-8",
|
|
3996
|
+
cwd: process.cwd(),
|
|
3997
|
+
maxBuffer: 10 * 1024 * 1024
|
|
3998
|
+
});
|
|
3999
|
+
if (diff.length > 5e4) {
|
|
4000
|
+
diff = diff.slice(0, 5e4) + "\n\n... (diff truncated, too large)";
|
|
4001
|
+
}
|
|
4002
|
+
} catch {
|
|
4003
|
+
}
|
|
4004
|
+
console.log(chalk12.dim(`
|
|
4005
|
+
Analyzing ${commitLog.split("\n").length} commit(s) from ${currentBranch}...
|
|
4006
|
+
`));
|
|
4007
|
+
ctx.conversation.addUserMessage(
|
|
4008
|
+
`\uD604\uC7AC \uBE0C\uB79C\uCE58 \`${currentBranch}\`\uC5D0\uC11C \`${baseBranch}\`\uB85C \uBCF4\uB0BC Pull Request \uC124\uBA85\uC744 \uC0DD\uC131\uD574\uC918.
|
|
4009
|
+
|
|
4010
|
+
\uB2E4\uC74C \uD615\uC2DD\uC758 \uB9C8\uD06C\uB2E4\uC6B4\uC73C\uB85C \uCD9C\uB825\uD574\uC918:
|
|
4011
|
+
- **Title**: PR \uC81C\uBAA9 (70\uC790 \uC774\uB0B4, \uC601\uBB38)
|
|
4012
|
+
- **## Summary**: \uBCC0\uACBD \uC0AC\uD56D \uC694\uC57D (1-3 bullet points)
|
|
4013
|
+
- **## Changes**: \uC8FC\uC694 \uBCC0\uACBD \uD30C\uC77C \uBC0F \uB0B4\uC6A9
|
|
4014
|
+
- **## Test Plan**: \uD14C\uC2A4\uD2B8 \uACC4\uD68D \uCCB4\uD06C\uB9AC\uC2A4\uD2B8
|
|
4015
|
+
|
|
4016
|
+
### Commits:
|
|
4017
|
+
\`\`\`
|
|
4018
|
+
${commitLog}
|
|
4019
|
+
\`\`\`
|
|
4020
|
+
|
|
4021
|
+
### Diff stat:
|
|
4022
|
+
\`\`\`
|
|
4023
|
+
${diffStat}
|
|
4024
|
+
\`\`\`
|
|
4025
|
+
|
|
4026
|
+
### Full diff:
|
|
4027
|
+
\`\`\`diff
|
|
4028
|
+
${diff}
|
|
4029
|
+
\`\`\``
|
|
4030
|
+
);
|
|
4031
|
+
return false;
|
|
4032
|
+
} catch {
|
|
4033
|
+
console.log(chalk12.yellow("Not a git repository or git not available."));
|
|
4034
|
+
return true;
|
|
4035
|
+
}
|
|
4036
|
+
}
|
|
4037
|
+
},
|
|
3052
4038
|
{
|
|
3053
4039
|
name: "/mcp",
|
|
3054
4040
|
description: "Show MCP server status",
|
|
3055
4041
|
handler: async () => {
|
|
3056
4042
|
const servers = mcpManager.listServers();
|
|
3057
4043
|
if (servers.length === 0) {
|
|
3058
|
-
console.log(
|
|
3059
|
-
console.log(
|
|
4044
|
+
console.log(chalk12.dim("\nNo MCP servers connected.\n"));
|
|
4045
|
+
console.log(chalk12.dim("Add servers in .codi/mcp.json or ~/.codi/mcp.json"));
|
|
3060
4046
|
return true;
|
|
3061
4047
|
}
|
|
3062
|
-
console.log(
|
|
4048
|
+
console.log(chalk12.bold("\nMCP Servers:\n"));
|
|
3063
4049
|
for (const s of servers) {
|
|
3064
|
-
console.log(` ${
|
|
4050
|
+
console.log(` ${chalk12.green("\u25CF")} ${s.name}`);
|
|
3065
4051
|
for (const t of s.tools) {
|
|
3066
|
-
console.log(
|
|
4052
|
+
console.log(chalk12.dim(` - ${t}`));
|
|
3067
4053
|
}
|
|
3068
4054
|
}
|
|
3069
4055
|
console.log("");
|
|
@@ -3072,24 +4058,29 @@ ${errorOutput}
|
|
|
3072
4058
|
}
|
|
3073
4059
|
];
|
|
3074
4060
|
}
|
|
4061
|
+
function formatTokens(n) {
|
|
4062
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
4063
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
|
|
4064
|
+
return String(n);
|
|
4065
|
+
}
|
|
3075
4066
|
function loadCustomCommands() {
|
|
3076
4067
|
const commands = [];
|
|
3077
|
-
const home = process.env["HOME"] || process.env["USERPROFILE"] ||
|
|
4068
|
+
const home = process.env["HOME"] || process.env["USERPROFILE"] || os13.homedir();
|
|
3078
4069
|
const dirs = [
|
|
3079
|
-
|
|
3080
|
-
|
|
4070
|
+
path13.join(home, ".codi", "commands"),
|
|
4071
|
+
path13.join(process.cwd(), ".codi", "commands")
|
|
3081
4072
|
];
|
|
3082
4073
|
for (const dir of dirs) {
|
|
3083
|
-
if (!
|
|
3084
|
-
const files =
|
|
4074
|
+
if (!fs12.existsSync(dir)) continue;
|
|
4075
|
+
const files = fs12.readdirSync(dir).filter((f) => f.endsWith(".md"));
|
|
3085
4076
|
for (const file of files) {
|
|
3086
4077
|
const name = "/" + file.replace(".md", "");
|
|
3087
|
-
const filePath =
|
|
4078
|
+
const filePath = path13.join(dir, file);
|
|
3088
4079
|
commands.push({
|
|
3089
4080
|
name,
|
|
3090
|
-
description: `Custom command from ${
|
|
4081
|
+
description: `Custom command from ${path13.relative(process.cwd(), filePath)}`,
|
|
3091
4082
|
handler: async (_args, ctx) => {
|
|
3092
|
-
let content =
|
|
4083
|
+
let content = fs12.readFileSync(filePath, "utf-8");
|
|
3093
4084
|
content = content.replace(/\{\{cwd\}\}/g, process.cwd()).replace(/\{\{date\}\}/g, (/* @__PURE__ */ new Date()).toISOString().split("T")[0]).replace(/\{\{file_path\}\}/g, _args || "");
|
|
3094
4085
|
await ctx.conversation.addUserMessage(content);
|
|
3095
4086
|
return false;
|
|
@@ -3103,8 +4094,8 @@ function loadCustomCommands() {
|
|
|
3103
4094
|
// src/tools/file-read.ts
|
|
3104
4095
|
init_esm_shims();
|
|
3105
4096
|
init_tool();
|
|
3106
|
-
import * as
|
|
3107
|
-
import * as
|
|
4097
|
+
import * as fs13 from "fs";
|
|
4098
|
+
import * as path14 from "path";
|
|
3108
4099
|
var fileReadTool = {
|
|
3109
4100
|
name: "read_file",
|
|
3110
4101
|
description: `Read a file from the filesystem. Supports text files with line numbers (cat -n format), PDF files, images (returns base64 for multimodal), and Jupyter notebooks (.ipynb). Use offset/limit for large files.`,
|
|
@@ -3124,17 +4115,17 @@ var fileReadTool = {
|
|
|
3124
4115
|
const filePath = String(input3["file_path"]);
|
|
3125
4116
|
const offset = input3["offset"];
|
|
3126
4117
|
const limit = input3["limit"];
|
|
3127
|
-
const resolved =
|
|
3128
|
-
if (!
|
|
4118
|
+
const resolved = path14.resolve(filePath);
|
|
4119
|
+
if (!fs13.existsSync(resolved)) {
|
|
3129
4120
|
return makeToolError(`File not found: ${resolved}`);
|
|
3130
4121
|
}
|
|
3131
|
-
const stat =
|
|
4122
|
+
const stat = fs13.statSync(resolved);
|
|
3132
4123
|
if (stat.isDirectory()) {
|
|
3133
4124
|
return makeToolError(`Path is a directory, not a file: ${resolved}. Use list_dir instead.`);
|
|
3134
4125
|
}
|
|
3135
|
-
const ext =
|
|
4126
|
+
const ext = path14.extname(resolved).toLowerCase();
|
|
3136
4127
|
if ([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".svg"].includes(ext)) {
|
|
3137
|
-
const data =
|
|
4128
|
+
const data = fs13.readFileSync(resolved);
|
|
3138
4129
|
const base64 = data.toString("base64");
|
|
3139
4130
|
const mimeMap = {
|
|
3140
4131
|
".png": "image/png",
|
|
@@ -3145,7 +4136,7 @@ var fileReadTool = {
|
|
|
3145
4136
|
".bmp": "image/bmp",
|
|
3146
4137
|
".svg": "image/svg+xml"
|
|
3147
4138
|
};
|
|
3148
|
-
return makeToolResult(`[Image: ${
|
|
4139
|
+
return makeToolResult(`[Image: ${path14.basename(resolved)}]`, {
|
|
3149
4140
|
filePath: resolved,
|
|
3150
4141
|
isImage: true,
|
|
3151
4142
|
imageData: base64,
|
|
@@ -3156,7 +4147,7 @@ var fileReadTool = {
|
|
|
3156
4147
|
try {
|
|
3157
4148
|
const pdfModule = await import("pdf-parse");
|
|
3158
4149
|
const pdfParse = pdfModule.default || pdfModule;
|
|
3159
|
-
const buffer =
|
|
4150
|
+
const buffer = fs13.readFileSync(resolved);
|
|
3160
4151
|
const data = await pdfParse(buffer);
|
|
3161
4152
|
const pages = input3["pages"];
|
|
3162
4153
|
let text = data.text;
|
|
@@ -3174,7 +4165,7 @@ var fileReadTool = {
|
|
|
3174
4165
|
}
|
|
3175
4166
|
if (ext === ".ipynb") {
|
|
3176
4167
|
try {
|
|
3177
|
-
const content =
|
|
4168
|
+
const content = fs13.readFileSync(resolved, "utf-8");
|
|
3178
4169
|
const nb = JSON.parse(content);
|
|
3179
4170
|
const output3 = [];
|
|
3180
4171
|
for (let i = 0; i < (nb.cells || []).length; i++) {
|
|
@@ -3202,7 +4193,7 @@ var fileReadTool = {
|
|
|
3202
4193
|
}
|
|
3203
4194
|
}
|
|
3204
4195
|
try {
|
|
3205
|
-
const raw =
|
|
4196
|
+
const raw = fs13.readFileSync(resolved, "utf-8");
|
|
3206
4197
|
const content = raw.replace(/\r\n/g, "\n");
|
|
3207
4198
|
const lines = content.split("\n");
|
|
3208
4199
|
const totalLines = lines.length;
|
|
@@ -3230,8 +4221,9 @@ var fileReadTool = {
|
|
|
3230
4221
|
// src/tools/file-write.ts
|
|
3231
4222
|
init_esm_shims();
|
|
3232
4223
|
init_tool();
|
|
3233
|
-
|
|
3234
|
-
import * as
|
|
4224
|
+
init_file_backup();
|
|
4225
|
+
import * as fs14 from "fs";
|
|
4226
|
+
import * as path15 from "path";
|
|
3235
4227
|
var fileWriteTool = {
|
|
3236
4228
|
name: "write_file",
|
|
3237
4229
|
description: `Create a new file or overwrite an existing file. Creates parent directories if needed. For modifying existing files, prefer edit_file instead.`,
|
|
@@ -3248,14 +4240,15 @@ var fileWriteTool = {
|
|
|
3248
4240
|
async execute(input3) {
|
|
3249
4241
|
const filePath = String(input3["file_path"]);
|
|
3250
4242
|
const content = String(input3["content"]);
|
|
3251
|
-
const resolved =
|
|
4243
|
+
const resolved = path15.resolve(filePath);
|
|
3252
4244
|
try {
|
|
3253
|
-
const dir =
|
|
3254
|
-
if (!
|
|
3255
|
-
|
|
4245
|
+
const dir = path15.dirname(resolved);
|
|
4246
|
+
if (!fs14.existsSync(dir)) {
|
|
4247
|
+
fs14.mkdirSync(dir, { recursive: true });
|
|
3256
4248
|
}
|
|
3257
|
-
const existed =
|
|
3258
|
-
|
|
4249
|
+
const existed = fs14.existsSync(resolved);
|
|
4250
|
+
backupFile(resolved);
|
|
4251
|
+
fs14.writeFileSync(resolved, content, "utf-8");
|
|
3259
4252
|
const lines = content.split("\n").length;
|
|
3260
4253
|
const action = existed ? "Overwrote" : "Created";
|
|
3261
4254
|
return makeToolResult(`${action} ${resolved} (${lines} lines)`, {
|
|
@@ -3271,8 +4264,9 @@ var fileWriteTool = {
|
|
|
3271
4264
|
// src/tools/file-edit.ts
|
|
3272
4265
|
init_esm_shims();
|
|
3273
4266
|
init_tool();
|
|
3274
|
-
|
|
3275
|
-
import * as
|
|
4267
|
+
init_file_backup();
|
|
4268
|
+
import * as fs15 from "fs";
|
|
4269
|
+
import * as path16 from "path";
|
|
3276
4270
|
var fileEditTool = {
|
|
3277
4271
|
name: "edit_file",
|
|
3278
4272
|
description: `Perform exact string replacement in a file. The old_string must be unique in the file unless replace_all is true. Preserves indentation exactly.`,
|
|
@@ -3293,12 +4287,12 @@ var fileEditTool = {
|
|
|
3293
4287
|
const oldString = String(input3["old_string"]);
|
|
3294
4288
|
const newString = String(input3["new_string"]);
|
|
3295
4289
|
const replaceAll = input3["replace_all"] === true;
|
|
3296
|
-
const resolved =
|
|
3297
|
-
if (!
|
|
4290
|
+
const resolved = path16.resolve(filePath);
|
|
4291
|
+
if (!fs15.existsSync(resolved)) {
|
|
3298
4292
|
return makeToolError(`File not found: ${resolved}`);
|
|
3299
4293
|
}
|
|
3300
4294
|
try {
|
|
3301
|
-
const raw =
|
|
4295
|
+
const raw = fs15.readFileSync(resolved, "utf-8");
|
|
3302
4296
|
const hasCrlf = raw.includes("\r\n");
|
|
3303
4297
|
let content = hasCrlf ? raw.replace(/\r\n/g, "\n") : raw;
|
|
3304
4298
|
if (oldString === newString) {
|
|
@@ -3332,8 +4326,9 @@ Did you mean this line?
|
|
|
3332
4326
|
const idx = content.indexOf(oldString);
|
|
3333
4327
|
content = content.slice(0, idx) + newString + content.slice(idx + oldString.length);
|
|
3334
4328
|
}
|
|
4329
|
+
backupFile(resolved);
|
|
3335
4330
|
const output3 = hasCrlf ? content.replace(/\n/g, "\r\n") : content;
|
|
3336
|
-
|
|
4331
|
+
fs15.writeFileSync(resolved, output3, "utf-8");
|
|
3337
4332
|
const linesChanged = Math.max(
|
|
3338
4333
|
oldString.split("\n").length,
|
|
3339
4334
|
newString.split("\n").length
|
|
@@ -3351,8 +4346,9 @@ Did you mean this line?
|
|
|
3351
4346
|
// src/tools/file-multi-edit.ts
|
|
3352
4347
|
init_esm_shims();
|
|
3353
4348
|
init_tool();
|
|
3354
|
-
|
|
3355
|
-
import * as
|
|
4349
|
+
init_file_backup();
|
|
4350
|
+
import * as fs16 from "fs";
|
|
4351
|
+
import * as path17 from "path";
|
|
3356
4352
|
var fileMultiEditTool = {
|
|
3357
4353
|
name: "multi_edit",
|
|
3358
4354
|
description: `Apply multiple edits to a single file atomically. Each edit is an old_string \u2192 new_string replacement. All edits are validated before any are applied.`,
|
|
@@ -3380,15 +4376,15 @@ var fileMultiEditTool = {
|
|
|
3380
4376
|
async execute(input3) {
|
|
3381
4377
|
const filePath = String(input3["file_path"]);
|
|
3382
4378
|
const edits = input3["edits"];
|
|
3383
|
-
const resolved =
|
|
3384
|
-
if (!
|
|
4379
|
+
const resolved = path17.resolve(filePath);
|
|
4380
|
+
if (!fs16.existsSync(resolved)) {
|
|
3385
4381
|
return makeToolError(`File not found: ${resolved}`);
|
|
3386
4382
|
}
|
|
3387
4383
|
if (!Array.isArray(edits) || edits.length === 0) {
|
|
3388
4384
|
return makeToolError("edits must be a non-empty array of {old_string, new_string} objects");
|
|
3389
4385
|
}
|
|
3390
4386
|
try {
|
|
3391
|
-
const raw =
|
|
4387
|
+
const raw = fs16.readFileSync(resolved, "utf-8");
|
|
3392
4388
|
const hasCrlf = raw.includes("\r\n");
|
|
3393
4389
|
let content = hasCrlf ? raw.replace(/\r\n/g, "\n") : raw;
|
|
3394
4390
|
for (let i = 0; i < edits.length; i++) {
|
|
@@ -3412,8 +4408,9 @@ Searching for: ${edit2.old_string.slice(0, 100)}...`
|
|
|
3412
4408
|
edit2.new_string.split("\n").length
|
|
3413
4409
|
);
|
|
3414
4410
|
}
|
|
4411
|
+
backupFile(resolved);
|
|
3415
4412
|
const output3 = hasCrlf ? content.replace(/\n/g, "\r\n") : content;
|
|
3416
|
-
|
|
4413
|
+
fs16.writeFileSync(resolved, output3, "utf-8");
|
|
3417
4414
|
return makeToolResult(`Applied ${edits.length} edits to ${resolved}`, {
|
|
3418
4415
|
filePath: resolved,
|
|
3419
4416
|
linesChanged: totalLinesChanged
|
|
@@ -3427,8 +4424,8 @@ Searching for: ${edit2.old_string.slice(0, 100)}...`
|
|
|
3427
4424
|
// src/tools/glob.ts
|
|
3428
4425
|
init_esm_shims();
|
|
3429
4426
|
init_tool();
|
|
3430
|
-
import * as
|
|
3431
|
-
import * as
|
|
4427
|
+
import * as fs17 from "fs";
|
|
4428
|
+
import * as path18 from "path";
|
|
3432
4429
|
import { globby } from "globby";
|
|
3433
4430
|
var globTool = {
|
|
3434
4431
|
name: "glob",
|
|
@@ -3446,7 +4443,7 @@ var globTool = {
|
|
|
3446
4443
|
async execute(input3) {
|
|
3447
4444
|
const pattern = String(input3["pattern"]);
|
|
3448
4445
|
const searchPath = input3["path"] ? String(input3["path"]) : process.cwd();
|
|
3449
|
-
const resolved =
|
|
4446
|
+
const resolved = path18.resolve(searchPath);
|
|
3450
4447
|
try {
|
|
3451
4448
|
const files = await globby(pattern, {
|
|
3452
4449
|
cwd: resolved,
|
|
@@ -3457,7 +4454,7 @@ var globTool = {
|
|
|
3457
4454
|
});
|
|
3458
4455
|
const withStats = files.map((f) => {
|
|
3459
4456
|
try {
|
|
3460
|
-
const stat =
|
|
4457
|
+
const stat = fs17.statSync(f);
|
|
3461
4458
|
return { path: f, mtime: stat.mtimeMs };
|
|
3462
4459
|
} catch {
|
|
3463
4460
|
return { path: f, mtime: 0 };
|
|
@@ -3482,8 +4479,8 @@ ${result.join("\n")}`
|
|
|
3482
4479
|
init_esm_shims();
|
|
3483
4480
|
init_tool();
|
|
3484
4481
|
import { execFile } from "child_process";
|
|
3485
|
-
import * as
|
|
3486
|
-
import * as
|
|
4482
|
+
import * as fs18 from "fs";
|
|
4483
|
+
import * as path19 from "path";
|
|
3487
4484
|
var grepTool = {
|
|
3488
4485
|
name: "grep",
|
|
3489
4486
|
description: `Search file contents using regex patterns. Uses ripgrep (rg) if available, falls back to grep, then to a built-in Node.js search. Supports context lines, file type filters, and multiple output modes.`,
|
|
@@ -3513,7 +4510,7 @@ var grepTool = {
|
|
|
3513
4510
|
readOnly: true,
|
|
3514
4511
|
async execute(input3) {
|
|
3515
4512
|
const pattern = String(input3["pattern"]);
|
|
3516
|
-
const searchPath =
|
|
4513
|
+
const searchPath = path19.resolve(input3["path"] ? String(input3["path"]) : process.cwd());
|
|
3517
4514
|
const outputMode = input3["output_mode"] || "files_with_matches";
|
|
3518
4515
|
const headLimit = input3["head_limit"] || 0;
|
|
3519
4516
|
const hasRg = await hasCommand("rg");
|
|
@@ -3578,19 +4575,19 @@ var grepTool = {
|
|
|
3578
4575
|
};
|
|
3579
4576
|
function hasCommand(cmd) {
|
|
3580
4577
|
const checkCmd = process.platform === "win32" ? "where" : "which";
|
|
3581
|
-
return new Promise((
|
|
3582
|
-
execFile(checkCmd, [cmd], (err) =>
|
|
4578
|
+
return new Promise((resolve11) => {
|
|
4579
|
+
execFile(checkCmd, [cmd], (err) => resolve11(!err));
|
|
3583
4580
|
});
|
|
3584
4581
|
}
|
|
3585
4582
|
function runCommand(cmd, args) {
|
|
3586
|
-
return new Promise((
|
|
4583
|
+
return new Promise((resolve11, reject) => {
|
|
3587
4584
|
execFile(cmd, args, { maxBuffer: 10 * 1024 * 1024, timeout: 3e4 }, (err, stdout, stderr) => {
|
|
3588
4585
|
if (err) {
|
|
3589
4586
|
err.code = err.code;
|
|
3590
4587
|
reject(err);
|
|
3591
4588
|
return;
|
|
3592
4589
|
}
|
|
3593
|
-
|
|
4590
|
+
resolve11(stdout);
|
|
3594
4591
|
});
|
|
3595
4592
|
});
|
|
3596
4593
|
}
|
|
@@ -3633,7 +4630,7 @@ async function builtinSearch(pattern, searchPath, input3, outputMode, headLimit)
|
|
|
3633
4630
|
if (headLimit > 0 && entryCount >= headLimit) break;
|
|
3634
4631
|
let content;
|
|
3635
4632
|
try {
|
|
3636
|
-
content =
|
|
4633
|
+
content = fs18.readFileSync(filePath, "utf-8");
|
|
3637
4634
|
} catch {
|
|
3638
4635
|
continue;
|
|
3639
4636
|
}
|
|
@@ -3697,18 +4694,18 @@ function collectFiles(dirPath, typeFilter, globFilter) {
|
|
|
3697
4694
|
function walk(dir) {
|
|
3698
4695
|
let entries;
|
|
3699
4696
|
try {
|
|
3700
|
-
entries =
|
|
4697
|
+
entries = fs18.readdirSync(dir, { withFileTypes: true });
|
|
3701
4698
|
} catch {
|
|
3702
4699
|
return;
|
|
3703
4700
|
}
|
|
3704
4701
|
for (const entry of entries) {
|
|
3705
4702
|
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
3706
4703
|
if (entry.name.startsWith(".") && entry.name !== ".") continue;
|
|
3707
|
-
const fullPath =
|
|
4704
|
+
const fullPath = path19.join(dir, entry.name);
|
|
3708
4705
|
if (entry.isDirectory()) {
|
|
3709
4706
|
walk(fullPath);
|
|
3710
4707
|
} else if (entry.isFile()) {
|
|
3711
|
-
const ext =
|
|
4708
|
+
const ext = path19.extname(entry.name).toLowerCase();
|
|
3712
4709
|
if (BINARY_EXTENSIONS.has(ext)) continue;
|
|
3713
4710
|
if (allowedExtensions && !allowedExtensions.has(ext)) continue;
|
|
3714
4711
|
if (globRegex && !globRegex.test(entry.name)) continue;
|
|
@@ -3717,7 +4714,7 @@ function collectFiles(dirPath, typeFilter, globFilter) {
|
|
|
3717
4714
|
}
|
|
3718
4715
|
}
|
|
3719
4716
|
try {
|
|
3720
|
-
const stat =
|
|
4717
|
+
const stat = fs18.statSync(dirPath);
|
|
3721
4718
|
if (stat.isFile()) {
|
|
3722
4719
|
return [dirPath];
|
|
3723
4720
|
}
|
|
@@ -3728,116 +4725,11 @@ function collectFiles(dirPath, typeFilter, globFilter) {
|
|
|
3728
4725
|
return files;
|
|
3729
4726
|
}
|
|
3730
4727
|
|
|
3731
|
-
// src/tools/bash.ts
|
|
3732
|
-
init_esm_shims();
|
|
3733
|
-
init_tool();
|
|
3734
|
-
import { exec as exec2, spawn } from "child_process";
|
|
3735
|
-
import * as os10 from "os";
|
|
3736
|
-
function getDefaultShell2() {
|
|
3737
|
-
if (os10.platform() === "win32") {
|
|
3738
|
-
return "powershell.exe";
|
|
3739
|
-
}
|
|
3740
|
-
return process.env["SHELL"] || "/bin/bash";
|
|
3741
|
-
}
|
|
3742
|
-
var backgroundTasks = /* @__PURE__ */ new Map();
|
|
3743
|
-
var taskCounter = 0;
|
|
3744
|
-
var bashTool = {
|
|
3745
|
-
name: "bash",
|
|
3746
|
-
description: `Execute a shell command. Supports timeout (max 600s, default 120s) and background execution. The working directory persists between calls. Uses the platform default shell (bash on Unix, PowerShell on Windows).`,
|
|
3747
|
-
inputSchema: {
|
|
3748
|
-
type: "object",
|
|
3749
|
-
properties: {
|
|
3750
|
-
command: { type: "string", description: "The bash command to execute" },
|
|
3751
|
-
description: { type: "string", description: "Brief description of what the command does" },
|
|
3752
|
-
timeout: { type: "number", description: "Timeout in milliseconds (max 600000, default 120000)" },
|
|
3753
|
-
run_in_background: { type: "boolean", description: "Run in background and return a task ID" }
|
|
3754
|
-
},
|
|
3755
|
-
required: ["command"]
|
|
3756
|
-
},
|
|
3757
|
-
dangerous: true,
|
|
3758
|
-
readOnly: false,
|
|
3759
|
-
async execute(input3) {
|
|
3760
|
-
const command = String(input3["command"]);
|
|
3761
|
-
const timeout = Math.min(Number(input3["timeout"]) || 12e4, 6e5);
|
|
3762
|
-
const runInBackground = input3["run_in_background"] === true;
|
|
3763
|
-
if (!command.trim()) {
|
|
3764
|
-
return makeToolError("Command cannot be empty");
|
|
3765
|
-
}
|
|
3766
|
-
if (runInBackground) {
|
|
3767
|
-
return runBackgroundTask(command);
|
|
3768
|
-
}
|
|
3769
|
-
return new Promise((resolve10) => {
|
|
3770
|
-
const finalCommand = os10.platform() === "win32" ? `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; ${command}` : command;
|
|
3771
|
-
exec2(finalCommand, {
|
|
3772
|
-
timeout,
|
|
3773
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
3774
|
-
shell: getDefaultShell2(),
|
|
3775
|
-
cwd: process.cwd(),
|
|
3776
|
-
env: { ...process.env }
|
|
3777
|
-
}, (err, stdout, stderr) => {
|
|
3778
|
-
if (err) {
|
|
3779
|
-
const exitCode = err.code;
|
|
3780
|
-
const output4 = [
|
|
3781
|
-
stdout ? `stdout:
|
|
3782
|
-
${stdout}` : "",
|
|
3783
|
-
stderr ? `stderr:
|
|
3784
|
-
${stderr}` : "",
|
|
3785
|
-
`Exit code: ${exitCode}`
|
|
3786
|
-
].filter(Boolean).join("\n\n");
|
|
3787
|
-
if (err.killed) {
|
|
3788
|
-
resolve10(makeToolError(`Command timed out after ${timeout / 1e3}s
|
|
3789
|
-
${output4}`));
|
|
3790
|
-
} else {
|
|
3791
|
-
resolve10(makeToolResult(output4 || `Command failed with exit code ${exitCode}`));
|
|
3792
|
-
}
|
|
3793
|
-
return;
|
|
3794
|
-
}
|
|
3795
|
-
const output3 = [
|
|
3796
|
-
stdout ? stdout : "",
|
|
3797
|
-
stderr ? `stderr:
|
|
3798
|
-
${stderr}` : ""
|
|
3799
|
-
].filter(Boolean).join("\n");
|
|
3800
|
-
resolve10(makeToolResult(output3 || "(no output)"));
|
|
3801
|
-
});
|
|
3802
|
-
});
|
|
3803
|
-
}
|
|
3804
|
-
};
|
|
3805
|
-
function runBackgroundTask(command) {
|
|
3806
|
-
const taskId = `bg_${++taskCounter}`;
|
|
3807
|
-
const bgCommand = os10.platform() === "win32" ? `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; ${command}` : command;
|
|
3808
|
-
const proc = spawn(bgCommand, {
|
|
3809
|
-
shell: getDefaultShell2(),
|
|
3810
|
-
cwd: process.cwd(),
|
|
3811
|
-
env: { ...process.env },
|
|
3812
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
3813
|
-
detached: false
|
|
3814
|
-
});
|
|
3815
|
-
const task = { process: proc, output: "", status: "running", exitCode: void 0 };
|
|
3816
|
-
backgroundTasks.set(taskId, task);
|
|
3817
|
-
proc.stdout?.on("data", (data) => {
|
|
3818
|
-
task.output += data.toString();
|
|
3819
|
-
});
|
|
3820
|
-
proc.stderr?.on("data", (data) => {
|
|
3821
|
-
task.output += data.toString();
|
|
3822
|
-
});
|
|
3823
|
-
proc.on("close", (code) => {
|
|
3824
|
-
task.status = code === 0 ? "done" : "error";
|
|
3825
|
-
task.exitCode = code ?? 1;
|
|
3826
|
-
});
|
|
3827
|
-
proc.on("error", (err) => {
|
|
3828
|
-
task.status = "error";
|
|
3829
|
-
task.output += `
|
|
3830
|
-
Process error: ${err.message}`;
|
|
3831
|
-
});
|
|
3832
|
-
return makeToolResult(`Background task started with ID: ${taskId}
|
|
3833
|
-
Use task_output tool to check results.`);
|
|
3834
|
-
}
|
|
3835
|
-
|
|
3836
4728
|
// src/tools/list-dir.ts
|
|
3837
4729
|
init_esm_shims();
|
|
3838
4730
|
init_tool();
|
|
3839
|
-
import * as
|
|
3840
|
-
import * as
|
|
4731
|
+
import * as fs19 from "fs";
|
|
4732
|
+
import * as path20 from "path";
|
|
3841
4733
|
var listDirTool = {
|
|
3842
4734
|
name: "list_dir",
|
|
3843
4735
|
description: `List directory contents with file/folder distinction and basic metadata.`,
|
|
@@ -3851,16 +4743,16 @@ var listDirTool = {
|
|
|
3851
4743
|
dangerous: false,
|
|
3852
4744
|
readOnly: true,
|
|
3853
4745
|
async execute(input3) {
|
|
3854
|
-
const dirPath =
|
|
3855
|
-
if (!
|
|
4746
|
+
const dirPath = path20.resolve(input3["path"] ? String(input3["path"]) : process.cwd());
|
|
4747
|
+
if (!fs19.existsSync(dirPath)) {
|
|
3856
4748
|
return makeToolError(`Directory not found: ${dirPath}`);
|
|
3857
4749
|
}
|
|
3858
|
-
const stat =
|
|
4750
|
+
const stat = fs19.statSync(dirPath);
|
|
3859
4751
|
if (!stat.isDirectory()) {
|
|
3860
4752
|
return makeToolError(`Not a directory: ${dirPath}`);
|
|
3861
4753
|
}
|
|
3862
4754
|
try {
|
|
3863
|
-
const entries =
|
|
4755
|
+
const entries = fs19.readdirSync(dirPath, { withFileTypes: true });
|
|
3864
4756
|
const IGNORE = /* @__PURE__ */ new Set([".git", "node_modules", ".DS_Store", "__pycache__", ".next", "dist", "build"]);
|
|
3865
4757
|
const lines = [];
|
|
3866
4758
|
const dirs = [];
|
|
@@ -3871,7 +4763,7 @@ var listDirTool = {
|
|
|
3871
4763
|
dirs.push(`${entry.name}/`);
|
|
3872
4764
|
} else if (entry.isSymbolicLink()) {
|
|
3873
4765
|
try {
|
|
3874
|
-
const target =
|
|
4766
|
+
const target = fs19.readlinkSync(path20.join(dirPath, entry.name));
|
|
3875
4767
|
files.push(`${entry.name} -> ${target}`);
|
|
3876
4768
|
} catch {
|
|
3877
4769
|
files.push(`${entry.name} -> (broken link)`);
|
|
@@ -3898,9 +4790,95 @@ ${lines.join("\n")}`);
|
|
|
3898
4790
|
init_esm_shims();
|
|
3899
4791
|
init_tool();
|
|
3900
4792
|
import { execSync as execSync4 } from "child_process";
|
|
4793
|
+
function detectConflictFiles(cwd) {
|
|
4794
|
+
try {
|
|
4795
|
+
const output3 = execSync4("git diff --name-only --diff-filter=U", {
|
|
4796
|
+
encoding: "utf-8",
|
|
4797
|
+
cwd,
|
|
4798
|
+
timeout: 1e4
|
|
4799
|
+
});
|
|
4800
|
+
return output3.trim().split("\n").filter(Boolean);
|
|
4801
|
+
} catch {
|
|
4802
|
+
return [];
|
|
4803
|
+
}
|
|
4804
|
+
}
|
|
4805
|
+
function parseConflictMarkers(fileContent, filePath) {
|
|
4806
|
+
const sections = [];
|
|
4807
|
+
const lines = fileContent.split("\n");
|
|
4808
|
+
let inConflict = false;
|
|
4809
|
+
let startLine = 0;
|
|
4810
|
+
let oursLines = [];
|
|
4811
|
+
let theirsLines = [];
|
|
4812
|
+
let inTheirs = false;
|
|
4813
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4814
|
+
const line = lines[i];
|
|
4815
|
+
if (line.startsWith("<<<<<<<")) {
|
|
4816
|
+
inConflict = true;
|
|
4817
|
+
inTheirs = false;
|
|
4818
|
+
startLine = i + 1;
|
|
4819
|
+
oursLines = [];
|
|
4820
|
+
theirsLines = [];
|
|
4821
|
+
} else if (line.startsWith("=======") && inConflict) {
|
|
4822
|
+
inTheirs = true;
|
|
4823
|
+
} else if (line.startsWith(">>>>>>>") && inConflict) {
|
|
4824
|
+
sections.push({
|
|
4825
|
+
file: filePath,
|
|
4826
|
+
startLine,
|
|
4827
|
+
ours: oursLines.join("\n"),
|
|
4828
|
+
theirs: theirsLines.join("\n")
|
|
4829
|
+
});
|
|
4830
|
+
inConflict = false;
|
|
4831
|
+
inTheirs = false;
|
|
4832
|
+
} else if (inConflict) {
|
|
4833
|
+
if (inTheirs) {
|
|
4834
|
+
theirsLines.push(line);
|
|
4835
|
+
} else {
|
|
4836
|
+
oursLines.push(line);
|
|
4837
|
+
}
|
|
4838
|
+
}
|
|
4839
|
+
}
|
|
4840
|
+
return sections;
|
|
4841
|
+
}
|
|
4842
|
+
function formatConflictReport(cwd) {
|
|
4843
|
+
const conflictFiles = detectConflictFiles(cwd);
|
|
4844
|
+
if (conflictFiles.length === 0) return null;
|
|
4845
|
+
const fs22 = __require("fs");
|
|
4846
|
+
const path23 = __require("path");
|
|
4847
|
+
const lines = [
|
|
4848
|
+
`\u26A0 Merge conflicts detected in ${conflictFiles.length} file(s):`,
|
|
4849
|
+
""
|
|
4850
|
+
];
|
|
4851
|
+
for (const file of conflictFiles) {
|
|
4852
|
+
const fullPath = path23.join(cwd, file);
|
|
4853
|
+
let content;
|
|
4854
|
+
try {
|
|
4855
|
+
content = fs22.readFileSync(fullPath, "utf-8");
|
|
4856
|
+
} catch {
|
|
4857
|
+
lines.push(` - ${file} (cannot read)`);
|
|
4858
|
+
continue;
|
|
4859
|
+
}
|
|
4860
|
+
const sections = parseConflictMarkers(content, file);
|
|
4861
|
+
lines.push(` - ${file} (${sections.length} conflict(s))`);
|
|
4862
|
+
for (let i = 0; i < sections.length; i++) {
|
|
4863
|
+
const s = sections[i];
|
|
4864
|
+
lines.push(` [Conflict ${i + 1} at line ${s.startLine}]`);
|
|
4865
|
+
lines.push(` OURS:`);
|
|
4866
|
+
for (const l of s.ours.split("\n").slice(0, 5)) {
|
|
4867
|
+
lines.push(` ${l}`);
|
|
4868
|
+
}
|
|
4869
|
+
if (s.ours.split("\n").length > 5) lines.push(` ... (${s.ours.split("\n").length} lines)`);
|
|
4870
|
+
lines.push(` THEIRS:`);
|
|
4871
|
+
for (const l of s.theirs.split("\n").slice(0, 5)) {
|
|
4872
|
+
lines.push(` ${l}`);
|
|
4873
|
+
}
|
|
4874
|
+
if (s.theirs.split("\n").length > 5) lines.push(` ... (${s.theirs.split("\n").length} lines)`);
|
|
4875
|
+
}
|
|
4876
|
+
}
|
|
4877
|
+
return lines.join("\n");
|
|
4878
|
+
}
|
|
3901
4879
|
var gitTool = {
|
|
3902
4880
|
name: "git",
|
|
3903
|
-
description: `Execute git commands with safety checks. Blocks dangerous operations like force push, hard reset, and amend. Use for status, diff, log, commit, branch operations.`,
|
|
4881
|
+
description: `Execute git commands with safety checks. Blocks dangerous operations like force push, hard reset, and amend. Use for status, diff, log, commit, branch operations. When merge/pull results in conflicts, automatically detects and reports them.`,
|
|
3904
4882
|
inputSchema: {
|
|
3905
4883
|
type: "object",
|
|
3906
4884
|
properties: {
|
|
@@ -3929,7 +4907,9 @@ var gitTool = {
|
|
|
3929
4907
|
}
|
|
3930
4908
|
}
|
|
3931
4909
|
const readOnlyPrefixes = ["status", "diff", "log", "show", "branch", "tag", "remote", "stash list", "ls-files"];
|
|
3932
|
-
const
|
|
4910
|
+
const _isReadOnly = readOnlyPrefixes.some((p) => command.startsWith(p));
|
|
4911
|
+
const mergeCommands = /^(merge|pull|rebase|cherry-pick)\b/;
|
|
4912
|
+
const isMergeCommand = mergeCommands.test(command);
|
|
3933
4913
|
try {
|
|
3934
4914
|
const result = execSync4(`git ${command}`, {
|
|
3935
4915
|
encoding: "utf-8",
|
|
@@ -3937,9 +4917,26 @@ var gitTool = {
|
|
|
3937
4917
|
timeout: 3e4,
|
|
3938
4918
|
cwd: process.cwd()
|
|
3939
4919
|
});
|
|
4920
|
+
if (isMergeCommand) {
|
|
4921
|
+
const conflictReport = formatConflictReport(process.cwd());
|
|
4922
|
+
if (conflictReport) {
|
|
4923
|
+
return makeToolResult(`${result}
|
|
4924
|
+
|
|
4925
|
+
${conflictReport}`);
|
|
4926
|
+
}
|
|
4927
|
+
}
|
|
3940
4928
|
return makeToolResult(result || "(no output)");
|
|
3941
4929
|
} catch (err) {
|
|
3942
4930
|
const output3 = [err.stdout, err.stderr].filter(Boolean).join("\n");
|
|
4931
|
+
if (isMergeCommand) {
|
|
4932
|
+
const conflictReport = formatConflictReport(process.cwd());
|
|
4933
|
+
if (conflictReport) {
|
|
4934
|
+
return makeToolError(`git ${command} failed with conflicts:
|
|
4935
|
+
${output3}
|
|
4936
|
+
|
|
4937
|
+
${conflictReport}`);
|
|
4938
|
+
}
|
|
4939
|
+
}
|
|
3943
4940
|
return makeToolError(`git ${command} failed:
|
|
3944
4941
|
${output3 || err.message}`);
|
|
3945
4942
|
}
|
|
@@ -3950,15 +4947,92 @@ ${output3 || err.message}`);
|
|
|
3950
4947
|
init_esm_shims();
|
|
3951
4948
|
init_tool();
|
|
3952
4949
|
var cache = /* @__PURE__ */ new Map();
|
|
3953
|
-
var
|
|
4950
|
+
var DEFAULT_CACHE_TTL = 15 * 60 * 1e3;
|
|
4951
|
+
var MAX_RESPONSE_SIZE = 5 * 1024 * 1024;
|
|
4952
|
+
var MAX_TEXT_LEN = 5e4;
|
|
4953
|
+
var USER_AGENT = "Mozilla/5.0 (compatible; Codi/0.1; +https://github.com/gemdoq/codi)";
|
|
4954
|
+
function extractCharset(contentType) {
|
|
4955
|
+
const match = contentType.match(/charset=([^\s;]+)/i);
|
|
4956
|
+
return match && match[1] ? match[1].replace(/['"]/g, "") : null;
|
|
4957
|
+
}
|
|
4958
|
+
function parseCacheMaxAge(headers) {
|
|
4959
|
+
const cc = headers.get("cache-control");
|
|
4960
|
+
if (!cc) return null;
|
|
4961
|
+
const match = cc.match(/max-age=(\d+)/);
|
|
4962
|
+
if (!match || !match[1]) return null;
|
|
4963
|
+
const seconds = parseInt(match[1], 10);
|
|
4964
|
+
if (isNaN(seconds) || seconds <= 0) return null;
|
|
4965
|
+
const clamped = Math.max(60, Math.min(seconds, 3600));
|
|
4966
|
+
return clamped * 1e3;
|
|
4967
|
+
}
|
|
4968
|
+
function isPdf(url, contentType) {
|
|
4969
|
+
return contentType.includes("application/pdf") || /\.pdf(\?|#|$)/i.test(url);
|
|
4970
|
+
}
|
|
4971
|
+
function isJson(contentType) {
|
|
4972
|
+
return contentType.includes("application/json") || contentType.includes("+json");
|
|
4973
|
+
}
|
|
4974
|
+
async function extractHtmlText(html) {
|
|
4975
|
+
const { load } = await import("cheerio");
|
|
4976
|
+
const $ = load(html);
|
|
4977
|
+
$('script, style, nav, header, footer, iframe, noscript, svg, [role="navigation"], [role="banner"], .sidebar, .ad, .ads, .advertisement').remove();
|
|
4978
|
+
const main2 = $('main, article, .content, #content, .main, [role="main"]').first();
|
|
4979
|
+
let text = main2.length ? main2.text() : $("body").text();
|
|
4980
|
+
text = text.split("\n").map((line) => line.replace(/\s+/g, " ").trim()).filter((line) => line.length > 0).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
4981
|
+
return text;
|
|
4982
|
+
}
|
|
4983
|
+
async function extractPdfText(buffer) {
|
|
4984
|
+
const { PDFParse } = await import("pdf-parse");
|
|
4985
|
+
const parser = new PDFParse({ data: new Uint8Array(buffer) });
|
|
4986
|
+
try {
|
|
4987
|
+
const textResult = await parser.getText();
|
|
4988
|
+
const text = textResult.text?.trim() || "";
|
|
4989
|
+
const totalPages = textResult.total ?? "unknown";
|
|
4990
|
+
let infoStr = `Pages: ${totalPages}`;
|
|
4991
|
+
try {
|
|
4992
|
+
const info = await parser.getInfo();
|
|
4993
|
+
if (info.info?.Title) infoStr += ` | Title: ${info.info.Title}`;
|
|
4994
|
+
if (info.info?.Author) infoStr += ` | Author: ${info.info.Author}`;
|
|
4995
|
+
} catch {
|
|
4996
|
+
}
|
|
4997
|
+
return `[PDF] ${infoStr}
|
|
4998
|
+
|
|
4999
|
+
${text}`;
|
|
5000
|
+
} finally {
|
|
5001
|
+
await parser.destroy().catch(() => {
|
|
5002
|
+
});
|
|
5003
|
+
}
|
|
5004
|
+
}
|
|
5005
|
+
function formatJson(raw) {
|
|
5006
|
+
try {
|
|
5007
|
+
const parsed = JSON.parse(raw);
|
|
5008
|
+
return `[JSON Response]
|
|
5009
|
+
${JSON.stringify(parsed, null, 2)}`;
|
|
5010
|
+
} catch {
|
|
5011
|
+
return `[JSON Response - parse error]
|
|
5012
|
+
${raw}`;
|
|
5013
|
+
}
|
|
5014
|
+
}
|
|
5015
|
+
async function decodeResponse(response, contentType) {
|
|
5016
|
+
const charset = extractCharset(contentType);
|
|
5017
|
+
if (charset && charset.toLowerCase() !== "utf-8" && charset.toLowerCase() !== "utf8") {
|
|
5018
|
+
const buffer = await response.arrayBuffer();
|
|
5019
|
+
const decoder = new TextDecoder(charset);
|
|
5020
|
+
return decoder.decode(buffer);
|
|
5021
|
+
}
|
|
5022
|
+
return response.text();
|
|
5023
|
+
}
|
|
3954
5024
|
var webFetchTool = {
|
|
3955
5025
|
name: "web_fetch",
|
|
3956
|
-
description: `Fetch content from a URL
|
|
5026
|
+
description: `Fetch content from a URL and return extracted text. Supports HTML (cheerio), PDF (pdf-parse), and JSON. Includes caching. HTTP URLs are upgraded to HTTPS.`,
|
|
3957
5027
|
inputSchema: {
|
|
3958
5028
|
type: "object",
|
|
3959
5029
|
properties: {
|
|
3960
5030
|
url: { type: "string", description: "URL to fetch" },
|
|
3961
|
-
prompt: { type: "string", description: "What information to extract from the page" }
|
|
5031
|
+
prompt: { type: "string", description: "What information to extract from the page" },
|
|
5032
|
+
cache_ttl: {
|
|
5033
|
+
type: "number",
|
|
5034
|
+
description: "Cache TTL in seconds (default: 900, i.e. 15 minutes). Set to 0 to bypass cache."
|
|
5035
|
+
}
|
|
3962
5036
|
},
|
|
3963
5037
|
required: ["url", "prompt"]
|
|
3964
5038
|
},
|
|
@@ -3967,50 +5041,103 @@ var webFetchTool = {
|
|
|
3967
5041
|
async execute(input3) {
|
|
3968
5042
|
let url = String(input3["url"]);
|
|
3969
5043
|
const prompt = String(input3["prompt"] || "");
|
|
5044
|
+
const cacheTtlInput = input3["cache_ttl"];
|
|
5045
|
+
const requestTtl = typeof cacheTtlInput === "number" ? cacheTtlInput * 1e3 : null;
|
|
5046
|
+
const bypassCache = requestTtl === 0;
|
|
3970
5047
|
if (url.startsWith("http://")) {
|
|
3971
5048
|
url = url.replace("http://", "https://");
|
|
3972
5049
|
}
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
5050
|
+
if (!bypassCache) {
|
|
5051
|
+
const cached = cache.get(url);
|
|
5052
|
+
if (cached && Date.now() - cached.timestamp < cached.ttl) {
|
|
5053
|
+
return makeToolResult(
|
|
5054
|
+
`[Cached] ${prompt ? `Query: ${prompt}
|
|
3976
5055
|
|
|
3977
|
-
` : ""}${cached.content}`
|
|
5056
|
+
` : ""}${cached.content}`
|
|
5057
|
+
);
|
|
5058
|
+
}
|
|
3978
5059
|
}
|
|
3979
5060
|
try {
|
|
3980
5061
|
const response = await fetch(url, {
|
|
3981
5062
|
headers: {
|
|
3982
|
-
"User-Agent":
|
|
3983
|
-
|
|
5063
|
+
"User-Agent": USER_AGENT,
|
|
5064
|
+
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,application/json;q=0.8,application/pdf;q=0.7,*/*;q=0.5"
|
|
3984
5065
|
},
|
|
3985
5066
|
redirect: "follow",
|
|
3986
5067
|
signal: AbortSignal.timeout(3e4)
|
|
3987
5068
|
});
|
|
3988
5069
|
if (!response.ok) {
|
|
3989
|
-
|
|
5070
|
+
const status = response.status;
|
|
5071
|
+
const statusText = response.statusText || "Unknown";
|
|
5072
|
+
let detail = `HTTP ${status} ${statusText}`;
|
|
5073
|
+
if (status === 403 || status === 401) {
|
|
5074
|
+
detail += " - \uC811\uADFC\uC774 \uCC28\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC778\uC99D\uC774 \uD544\uC694\uD558\uAC70\uB098 \uBD07 \uCC28\uB2E8\uC77C \uC218 \uC788\uC2B5\uB2C8\uB2E4.";
|
|
5075
|
+
} else if (status === 404) {
|
|
5076
|
+
detail += " - \uD398\uC774\uC9C0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.";
|
|
5077
|
+
} else if (status === 429) {
|
|
5078
|
+
detail += " - \uC694\uCCAD\uC774 \uB108\uBB34 \uB9CE\uC2B5\uB2C8\uB2E4. \uC7A0\uC2DC \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.";
|
|
5079
|
+
} else if (status >= 500) {
|
|
5080
|
+
detail += " - \uC11C\uBC84 \uC624\uB958\uC785\uB2C8\uB2E4.";
|
|
5081
|
+
}
|
|
5082
|
+
if (response.redirected) {
|
|
5083
|
+
detail += `
|
|
5084
|
+
Redirected to: ${response.url}`;
|
|
5085
|
+
}
|
|
5086
|
+
return makeToolError(detail);
|
|
5087
|
+
}
|
|
5088
|
+
const contentLength = response.headers.get("content-length");
|
|
5089
|
+
if (contentLength && parseInt(contentLength, 10) > MAX_RESPONSE_SIZE) {
|
|
5090
|
+
return makeToolError(
|
|
5091
|
+
`\uC751\uB2F5 \uD06C\uAE30\uAC00 \uB108\uBB34 \uD07D\uB2C8\uB2E4 (${(parseInt(contentLength, 10) / 1024 / 1024).toFixed(1)}MB). \uCD5C\uB300 5MB\uAE4C\uC9C0 \uC9C0\uC6D0\uD569\uB2C8\uB2E4.`
|
|
5092
|
+
);
|
|
3990
5093
|
}
|
|
3991
5094
|
const contentType = response.headers.get("content-type") || "";
|
|
3992
5095
|
let text;
|
|
3993
|
-
if (
|
|
3994
|
-
const
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
5096
|
+
if (isPdf(url, contentType)) {
|
|
5097
|
+
const buffer = await response.arrayBuffer();
|
|
5098
|
+
if (buffer.byteLength > MAX_RESPONSE_SIZE) {
|
|
5099
|
+
return makeToolError(
|
|
5100
|
+
`PDF \uD06C\uAE30\uAC00 \uB108\uBB34 \uD07D\uB2C8\uB2E4 (${(buffer.byteLength / 1024 / 1024).toFixed(1)}MB). \uCD5C\uB300 5MB\uAE4C\uC9C0 \uC9C0\uC6D0\uD569\uB2C8\uB2E4.`
|
|
5101
|
+
);
|
|
5102
|
+
}
|
|
5103
|
+
text = await extractPdfText(buffer);
|
|
5104
|
+
} else if (isJson(contentType)) {
|
|
5105
|
+
const raw = await decodeResponse(response, contentType);
|
|
5106
|
+
text = formatJson(raw);
|
|
5107
|
+
} else if (contentType.includes("text/html") || contentType.includes("application/xhtml")) {
|
|
5108
|
+
const html = await decodeResponse(response, contentType);
|
|
5109
|
+
text = await extractHtmlText(html);
|
|
4000
5110
|
} else {
|
|
4001
|
-
text = await response
|
|
5111
|
+
text = await decodeResponse(response, contentType);
|
|
5112
|
+
}
|
|
5113
|
+
if (text.length > MAX_TEXT_LEN) {
|
|
5114
|
+
text = text.slice(0, MAX_TEXT_LEN) + "\n\n... (truncated)";
|
|
5115
|
+
}
|
|
5116
|
+
const effectiveTtl = requestTtl ?? parseCacheMaxAge(response.headers) ?? DEFAULT_CACHE_TTL;
|
|
5117
|
+
if (!bypassCache) {
|
|
5118
|
+
cache.set(url, { content: text, timestamp: Date.now(), ttl: effectiveTtl });
|
|
4002
5119
|
}
|
|
4003
|
-
|
|
4004
|
-
if (
|
|
4005
|
-
|
|
5120
|
+
let prefix = `URL: ${url}`;
|
|
5121
|
+
if (response.redirected && response.url !== url) {
|
|
5122
|
+
prefix += `
|
|
5123
|
+
Redirected to: ${response.url}`;
|
|
4006
5124
|
}
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
Query: ${prompt}
|
|
5125
|
+
if (prompt) {
|
|
5126
|
+
prefix += `
|
|
5127
|
+
Query: ${prompt}`;
|
|
5128
|
+
}
|
|
5129
|
+
return makeToolResult(`${prefix}
|
|
4010
5130
|
|
|
4011
|
-
${text}`
|
|
5131
|
+
${text}`);
|
|
4012
5132
|
} catch (err) {
|
|
4013
|
-
|
|
5133
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5134
|
+
if (message.includes("TimeoutError") || message.includes("aborted")) {
|
|
5135
|
+
return makeToolError(`\uC694\uCCAD \uC2DC\uAC04 \uCD08\uACFC (30\uCD08): ${url}`);
|
|
5136
|
+
}
|
|
5137
|
+
if (message.includes("ENOTFOUND") || message.includes("getaddrinfo")) {
|
|
5138
|
+
return makeToolError(`\uB3C4\uBA54\uC778\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${url}`);
|
|
5139
|
+
}
|
|
5140
|
+
return makeToolError(`URL \uAC00\uC838\uC624\uAE30 \uC2E4\uD328: ${message}`);
|
|
4014
5141
|
}
|
|
4015
5142
|
}
|
|
4016
5143
|
};
|
|
@@ -4018,73 +5145,181 @@ ${text}` : text);
|
|
|
4018
5145
|
// src/tools/web-search.ts
|
|
4019
5146
|
init_esm_shims();
|
|
4020
5147
|
init_tool();
|
|
5148
|
+
var searchCache = /* @__PURE__ */ new Map();
|
|
5149
|
+
var SEARCH_CACHE_TTL = 10 * 60 * 1e3;
|
|
5150
|
+
var USER_AGENT2 = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
|
|
5151
|
+
function extractRealUrl(href) {
|
|
5152
|
+
try {
|
|
5153
|
+
const urlObj = new URL(href, "https://duckduckgo.com");
|
|
5154
|
+
return urlObj.searchParams.get("uddg") || href;
|
|
5155
|
+
} catch {
|
|
5156
|
+
return href;
|
|
5157
|
+
}
|
|
5158
|
+
}
|
|
5159
|
+
function normalizeUrl(url) {
|
|
5160
|
+
try {
|
|
5161
|
+
const u = new URL(url);
|
|
5162
|
+
const trackingParams = ["utm_source", "utm_medium", "utm_campaign", "utm_content", "utm_term", "ref", "fbclid", "gclid"];
|
|
5163
|
+
for (const param of trackingParams) {
|
|
5164
|
+
u.searchParams.delete(param);
|
|
5165
|
+
}
|
|
5166
|
+
let path23 = u.pathname.replace(/\/+$/, "") || "/";
|
|
5167
|
+
return `${u.hostname}${path23}${u.search}`;
|
|
5168
|
+
} catch {
|
|
5169
|
+
return url;
|
|
5170
|
+
}
|
|
5171
|
+
}
|
|
5172
|
+
function deduplicateResults(results) {
|
|
5173
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5174
|
+
const deduped = [];
|
|
5175
|
+
for (const result of results) {
|
|
5176
|
+
const normalized = normalizeUrl(result.url);
|
|
5177
|
+
if (!seen.has(normalized) && result.title.length > 0) {
|
|
5178
|
+
seen.add(normalized);
|
|
5179
|
+
deduped.push(result);
|
|
5180
|
+
}
|
|
5181
|
+
}
|
|
5182
|
+
return deduped;
|
|
5183
|
+
}
|
|
5184
|
+
async function searchDuckDuckGo(query, maxResults) {
|
|
5185
|
+
const encoded = encodeURIComponent(query);
|
|
5186
|
+
const url = `https://html.duckduckgo.com/html/?q=${encoded}`;
|
|
5187
|
+
const response = await fetch(url, {
|
|
5188
|
+
headers: {
|
|
5189
|
+
"User-Agent": USER_AGENT2,
|
|
5190
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
5191
|
+
"Accept-Language": "en-US,en;q=0.9"
|
|
5192
|
+
},
|
|
5193
|
+
signal: AbortSignal.timeout(15e3)
|
|
5194
|
+
});
|
|
5195
|
+
if (!response.ok) {
|
|
5196
|
+
throw new Error(`DuckDuckGo HTTP ${response.status} ${response.statusText}`);
|
|
5197
|
+
}
|
|
5198
|
+
const html = await response.text();
|
|
5199
|
+
if (html.includes("If this error persists") || html.includes("blocked")) {
|
|
5200
|
+
throw new Error("DuckDuckGo\uC5D0\uC11C \uC694\uCCAD\uC774 \uCC28\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4");
|
|
5201
|
+
}
|
|
5202
|
+
const { load } = await import("cheerio");
|
|
5203
|
+
const $ = load(html);
|
|
5204
|
+
const results = [];
|
|
5205
|
+
$(".result").each((_i, el) => {
|
|
5206
|
+
const $el = $(el);
|
|
5207
|
+
if ($el.hasClass("result--ad") || $el.find(".badge--ad").length > 0) {
|
|
5208
|
+
return;
|
|
5209
|
+
}
|
|
5210
|
+
const titleEl = $el.find(".result__title a, .result__a");
|
|
5211
|
+
const title = titleEl.text().trim();
|
|
5212
|
+
const href = titleEl.attr("href") || "";
|
|
5213
|
+
const snippet = $el.find(".result__snippet").text().trim();
|
|
5214
|
+
if (title && href) {
|
|
5215
|
+
const actualUrl = extractRealUrl(href);
|
|
5216
|
+
if (actualUrl.startsWith("http://") || actualUrl.startsWith("https://")) {
|
|
5217
|
+
results.push({ title, url: actualUrl, snippet });
|
|
5218
|
+
}
|
|
5219
|
+
}
|
|
5220
|
+
});
|
|
5221
|
+
return deduplicateResults(results).slice(0, maxResults);
|
|
5222
|
+
}
|
|
5223
|
+
async function searchDuckDuckGoLite(query, maxResults) {
|
|
5224
|
+
const encoded = encodeURIComponent(query);
|
|
5225
|
+
const url = `https://lite.duckduckgo.com/lite/?q=${encoded}`;
|
|
5226
|
+
const response = await fetch(url, {
|
|
5227
|
+
headers: {
|
|
5228
|
+
"User-Agent": USER_AGENT2,
|
|
5229
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
|
|
5230
|
+
},
|
|
5231
|
+
signal: AbortSignal.timeout(15e3)
|
|
5232
|
+
});
|
|
5233
|
+
if (!response.ok) {
|
|
5234
|
+
throw new Error(`DuckDuckGo Lite HTTP ${response.status}`);
|
|
5235
|
+
}
|
|
5236
|
+
const html = await response.text();
|
|
5237
|
+
const { load } = await import("cheerio");
|
|
5238
|
+
const $ = load(html);
|
|
5239
|
+
const results = [];
|
|
5240
|
+
$("a.result-link").each((_i, el) => {
|
|
5241
|
+
const $a = $(el);
|
|
5242
|
+
const title = $a.text().trim();
|
|
5243
|
+
const href = $a.attr("href") || "";
|
|
5244
|
+
if (title && href) {
|
|
5245
|
+
const actualUrl = extractRealUrl(href);
|
|
5246
|
+
const $row = $a.closest("tr");
|
|
5247
|
+
const snippet = $row.next("tr").find(".result-snippet").text().trim() || $row.next("tr").find("td").last().text().trim();
|
|
5248
|
+
if (actualUrl.startsWith("http://") || actualUrl.startsWith("https://")) {
|
|
5249
|
+
results.push({ title, url: actualUrl, snippet });
|
|
5250
|
+
}
|
|
5251
|
+
}
|
|
5252
|
+
});
|
|
5253
|
+
return deduplicateResults(results).slice(0, maxResults);
|
|
5254
|
+
}
|
|
4021
5255
|
var webSearchTool = {
|
|
4022
5256
|
name: "web_search",
|
|
4023
|
-
description: `Search the web for information. Returns search results with titles, URLs, and snippets. Uses DuckDuckGo
|
|
5257
|
+
description: `Search the web for information. Returns search results with titles, URLs, and snippets. Uses DuckDuckGo. Falls back to DuckDuckGo Lite if the main search fails.`,
|
|
4024
5258
|
inputSchema: {
|
|
4025
5259
|
type: "object",
|
|
4026
5260
|
properties: {
|
|
4027
|
-
query: { type: "string", description: "Search query" }
|
|
5261
|
+
query: { type: "string", description: "Search query" },
|
|
5262
|
+
max_results: {
|
|
5263
|
+
type: "number",
|
|
5264
|
+
description: "Maximum number of results (default: 10, max: 20)"
|
|
5265
|
+
}
|
|
4028
5266
|
},
|
|
4029
5267
|
required: ["query"]
|
|
4030
5268
|
},
|
|
4031
5269
|
dangerous: true,
|
|
4032
5270
|
readOnly: true,
|
|
4033
5271
|
async execute(input3) {
|
|
4034
|
-
const query = String(input3["query"]);
|
|
5272
|
+
const query = String(input3["query"]).trim();
|
|
5273
|
+
if (!query) {
|
|
5274
|
+
return makeToolError("\uAC80\uC0C9\uC5B4\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4.");
|
|
5275
|
+
}
|
|
5276
|
+
const rawMax = typeof input3["max_results"] === "number" ? input3["max_results"] : 10;
|
|
5277
|
+
const maxResults = Math.max(1, Math.min(20, Math.round(rawMax)));
|
|
5278
|
+
const cacheKey = `${query}|${maxResults}`;
|
|
5279
|
+
const cached = searchCache.get(cacheKey);
|
|
5280
|
+
if (cached && Date.now() - cached.timestamp < SEARCH_CACHE_TTL) {
|
|
5281
|
+
return makeToolResult(`[Cached] ${cached.results}`);
|
|
5282
|
+
}
|
|
5283
|
+
let results = [];
|
|
5284
|
+
let fallbackUsed = false;
|
|
4035
5285
|
try {
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
const $ = load(html);
|
|
4050
|
-
const results = [];
|
|
4051
|
-
$(".result").each((i, el) => {
|
|
4052
|
-
if (i >= 10) return false;
|
|
4053
|
-
const $el = $(el);
|
|
4054
|
-
const title = $el.find(".result__title a").text().trim();
|
|
4055
|
-
const href = $el.find(".result__title a").attr("href") || "";
|
|
4056
|
-
const snippet = $el.find(".result__snippet").text().trim();
|
|
4057
|
-
if (title && href) {
|
|
4058
|
-
let actualUrl = href;
|
|
4059
|
-
try {
|
|
4060
|
-
const urlObj = new URL(href, "https://duckduckgo.com");
|
|
4061
|
-
actualUrl = urlObj.searchParams.get("uddg") || href;
|
|
4062
|
-
} catch {
|
|
4063
|
-
actualUrl = href;
|
|
4064
|
-
}
|
|
4065
|
-
results.push({ title, url: actualUrl, snippet });
|
|
4066
|
-
}
|
|
4067
|
-
});
|
|
4068
|
-
if (results.length === 0) {
|
|
4069
|
-
return makeToolResult(`No results found for: ${query}`);
|
|
5286
|
+
results = await searchDuckDuckGo(query, maxResults);
|
|
5287
|
+
} catch (primaryErr) {
|
|
5288
|
+
try {
|
|
5289
|
+
results = await searchDuckDuckGoLite(query, maxResults);
|
|
5290
|
+
fallbackUsed = true;
|
|
5291
|
+
} catch (fallbackErr) {
|
|
5292
|
+
const primaryMsg = primaryErr instanceof Error ? primaryErr.message : String(primaryErr);
|
|
5293
|
+
const fallbackMsg = fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr);
|
|
5294
|
+
return makeToolError(
|
|
5295
|
+
`\uAC80\uC0C9 \uC2E4\uD328:
|
|
5296
|
+
Primary: ${primaryMsg}
|
|
5297
|
+
Fallback: ${fallbackMsg}`
|
|
5298
|
+
);
|
|
4070
5299
|
}
|
|
4071
|
-
const formatted = results.map((r, i) => `${i + 1}. ${r.title}
|
|
4072
|
-
${r.url}
|
|
4073
|
-
${r.snippet}`).join("\n\n");
|
|
4074
|
-
return makeToolResult(`Search results for: ${query}
|
|
4075
|
-
|
|
4076
|
-
${formatted}`);
|
|
4077
|
-
} catch (err) {
|
|
4078
|
-
return makeToolError(`Search failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
4079
5300
|
}
|
|
5301
|
+
if (results.length === 0) {
|
|
5302
|
+
return makeToolResult(`"${query}"\uC5D0 \uB300\uD55C \uAC80\uC0C9 \uACB0\uACFC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.`);
|
|
5303
|
+
}
|
|
5304
|
+
const formatted = results.map(
|
|
5305
|
+
(r, i) => `${i + 1}. ${r.title}
|
|
5306
|
+
${r.url}${r.snippet ? `
|
|
5307
|
+
${r.snippet}` : ""}`
|
|
5308
|
+
).join("\n\n");
|
|
5309
|
+
const header = fallbackUsed ? `Search results for: ${query} (fallback engine used)` : `Search results for: ${query}`;
|
|
5310
|
+
const output3 = `${header}
|
|
5311
|
+
|
|
5312
|
+
${formatted}`;
|
|
5313
|
+
searchCache.set(cacheKey, { results: output3, timestamp: Date.now() });
|
|
5314
|
+
return makeToolResult(output3);
|
|
4080
5315
|
}
|
|
4081
5316
|
};
|
|
4082
5317
|
|
|
4083
5318
|
// src/tools/notebook-edit.ts
|
|
4084
5319
|
init_esm_shims();
|
|
4085
5320
|
init_tool();
|
|
4086
|
-
import * as
|
|
4087
|
-
import * as
|
|
5321
|
+
import * as fs20 from "fs";
|
|
5322
|
+
import * as path21 from "path";
|
|
4088
5323
|
var notebookEditTool = {
|
|
4089
5324
|
name: "notebook_edit",
|
|
4090
5325
|
description: `Edit Jupyter notebook (.ipynb) cells. Supports replacing, inserting, and deleting cells.`,
|
|
@@ -4102,16 +5337,16 @@ var notebookEditTool = {
|
|
|
4102
5337
|
dangerous: true,
|
|
4103
5338
|
readOnly: false,
|
|
4104
5339
|
async execute(input3) {
|
|
4105
|
-
const nbPath =
|
|
5340
|
+
const nbPath = path21.resolve(String(input3["notebook_path"]));
|
|
4106
5341
|
const cellNumber = input3["cell_number"];
|
|
4107
5342
|
const newSource = String(input3["new_source"]);
|
|
4108
5343
|
const cellType = input3["cell_type"] || "code";
|
|
4109
5344
|
const editMode = input3["edit_mode"] || "replace";
|
|
4110
|
-
if (!
|
|
5345
|
+
if (!fs20.existsSync(nbPath)) {
|
|
4111
5346
|
return makeToolError(`Notebook not found: ${nbPath}`);
|
|
4112
5347
|
}
|
|
4113
5348
|
try {
|
|
4114
|
-
const content =
|
|
5349
|
+
const content = fs20.readFileSync(nbPath, "utf-8");
|
|
4115
5350
|
const nb = JSON.parse(content);
|
|
4116
5351
|
if (!nb.cells || !Array.isArray(nb.cells)) {
|
|
4117
5352
|
return makeToolError("Invalid notebook format: no cells array");
|
|
@@ -4156,7 +5391,7 @@ var notebookEditTool = {
|
|
|
4156
5391
|
default:
|
|
4157
5392
|
return makeToolError(`Unknown edit_mode: ${editMode}`);
|
|
4158
5393
|
}
|
|
4159
|
-
|
|
5394
|
+
fs20.writeFileSync(nbPath, JSON.stringify(nb, null, 1), "utf-8");
|
|
4160
5395
|
return makeToolResult(`Notebook ${editMode}d cell in ${nbPath} (${nb.cells.length} cells total)`);
|
|
4161
5396
|
} catch (err) {
|
|
4162
5397
|
return makeToolError(`Notebook edit failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -4171,7 +5406,7 @@ init_task_tools();
|
|
|
4171
5406
|
init_esm_shims();
|
|
4172
5407
|
init_tool();
|
|
4173
5408
|
import * as readline4 from "readline/promises";
|
|
4174
|
-
import
|
|
5409
|
+
import chalk13 from "chalk";
|
|
4175
5410
|
var askUserTool = {
|
|
4176
5411
|
name: "ask_user",
|
|
4177
5412
|
description: `Ask the user a question with optional choices. Use to gather preferences, clarify requirements, or get decisions.`,
|
|
@@ -4202,21 +5437,21 @@ var askUserTool = {
|
|
|
4202
5437
|
const options = input3["options"];
|
|
4203
5438
|
const multiSelect = input3["multiSelect"] === true;
|
|
4204
5439
|
console.log("");
|
|
4205
|
-
console.log(
|
|
5440
|
+
console.log(chalk13.cyan.bold("? ") + chalk13.bold(question));
|
|
4206
5441
|
if (options && options.length > 0) {
|
|
4207
5442
|
console.log("");
|
|
4208
5443
|
for (let i = 0; i < options.length; i++) {
|
|
4209
5444
|
const opt = options[i];
|
|
4210
|
-
console.log(
|
|
5445
|
+
console.log(chalk13.cyan(` ${i + 1}.`) + ` ${opt.label}${opt.description ? chalk13.dim(` - ${opt.description}`) : ""}`);
|
|
4211
5446
|
}
|
|
4212
|
-
console.log(
|
|
5447
|
+
console.log(chalk13.dim(` ${options.length + 1}. Other (type custom response)`));
|
|
4213
5448
|
console.log("");
|
|
4214
5449
|
const rl2 = readline4.createInterface({
|
|
4215
5450
|
input: process.stdin,
|
|
4216
5451
|
output: process.stdout
|
|
4217
5452
|
});
|
|
4218
5453
|
try {
|
|
4219
|
-
const prompt = multiSelect ?
|
|
5454
|
+
const prompt = multiSelect ? chalk13.dim("Enter numbers separated by commas: ") : chalk13.dim("Enter number or type response: ");
|
|
4220
5455
|
const answer = await rl2.question(prompt);
|
|
4221
5456
|
rl2.close();
|
|
4222
5457
|
if (multiSelect) {
|
|
@@ -4242,7 +5477,7 @@ var askUserTool = {
|
|
|
4242
5477
|
output: process.stdout
|
|
4243
5478
|
});
|
|
4244
5479
|
try {
|
|
4245
|
-
const answer = await rl.question(
|
|
5480
|
+
const answer = await rl.question(chalk13.dim("> "));
|
|
4246
5481
|
rl.close();
|
|
4247
5482
|
return makeToolResult(`User response: ${answer}`);
|
|
4248
5483
|
} catch {
|
|
@@ -4252,6 +5487,133 @@ var askUserTool = {
|
|
|
4252
5487
|
}
|
|
4253
5488
|
};
|
|
4254
5489
|
|
|
5490
|
+
// src/tools/memory-tool.ts
|
|
5491
|
+
init_esm_shims();
|
|
5492
|
+
init_tool();
|
|
5493
|
+
import * as fs21 from "fs";
|
|
5494
|
+
import * as path22 from "path";
|
|
5495
|
+
function buildFrontmatter(topic, description) {
|
|
5496
|
+
return `---
|
|
5497
|
+
name: ${topic}
|
|
5498
|
+
description: ${description}
|
|
5499
|
+
type: project
|
|
5500
|
+
---
|
|
5501
|
+
`;
|
|
5502
|
+
}
|
|
5503
|
+
function parseFrontmatter(content) {
|
|
5504
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
5505
|
+
if (!match) return { body: content };
|
|
5506
|
+
const meta = {};
|
|
5507
|
+
for (const line of match[1].split("\n")) {
|
|
5508
|
+
const idx = line.indexOf(":");
|
|
5509
|
+
if (idx !== -1) {
|
|
5510
|
+
meta[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
|
5511
|
+
}
|
|
5512
|
+
}
|
|
5513
|
+
return { name: meta["name"], description: meta["description"], body: match[2] };
|
|
5514
|
+
}
|
|
5515
|
+
function updateIndex(memoryDir) {
|
|
5516
|
+
const indexPath = path22.join(memoryDir, "MEMORY.md");
|
|
5517
|
+
const files = fs21.readdirSync(memoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md").sort();
|
|
5518
|
+
const lines = ["# Project Memory", ""];
|
|
5519
|
+
if (files.length === 0) {
|
|
5520
|
+
lines.push("No topics saved yet.");
|
|
5521
|
+
} else {
|
|
5522
|
+
lines.push("| Topic | Description |");
|
|
5523
|
+
lines.push("|-------|-------------|");
|
|
5524
|
+
for (const file of files) {
|
|
5525
|
+
const content = fs21.readFileSync(path22.join(memoryDir, file), "utf-8");
|
|
5526
|
+
const parsed = parseFrontmatter(content);
|
|
5527
|
+
const topic = file.replace(".md", "");
|
|
5528
|
+
const desc = parsed.description || "";
|
|
5529
|
+
lines.push(`| [${topic}](${file}) | ${desc} |`);
|
|
5530
|
+
}
|
|
5531
|
+
}
|
|
5532
|
+
lines.push("");
|
|
5533
|
+
fs21.writeFileSync(indexPath, lines.join("\n"), "utf-8");
|
|
5534
|
+
}
|
|
5535
|
+
var updateMemoryTool = {
|
|
5536
|
+
name: "update_memory",
|
|
5537
|
+
description: `Save, delete, or list project memory topics. Use this to persist important information (architecture decisions, user preferences, patterns, etc.) across conversations.
|
|
5538
|
+
- save: Create or update a memory topic file with content
|
|
5539
|
+
- delete: Remove a memory topic file
|
|
5540
|
+
- list: List all existing memory topics with descriptions`,
|
|
5541
|
+
inputSchema: {
|
|
5542
|
+
type: "object",
|
|
5543
|
+
properties: {
|
|
5544
|
+
action: {
|
|
5545
|
+
type: "string",
|
|
5546
|
+
enum: ["save", "delete", "list"],
|
|
5547
|
+
description: "Action to perform"
|
|
5548
|
+
},
|
|
5549
|
+
topic: {
|
|
5550
|
+
type: "string",
|
|
5551
|
+
description: 'Topic name (used as filename, e.g. "architecture" -> architecture.md). Required for save/delete.'
|
|
5552
|
+
},
|
|
5553
|
+
content: {
|
|
5554
|
+
type: "string",
|
|
5555
|
+
description: "Content to save. First line is used as description. Required for save."
|
|
5556
|
+
}
|
|
5557
|
+
},
|
|
5558
|
+
required: ["action", "topic"]
|
|
5559
|
+
},
|
|
5560
|
+
dangerous: false,
|
|
5561
|
+
readOnly: false,
|
|
5562
|
+
async execute(input3) {
|
|
5563
|
+
const action = String(input3["action"]);
|
|
5564
|
+
const topic = String(input3["topic"] || "");
|
|
5565
|
+
const content = input3["content"] != null ? String(input3["content"]) : void 0;
|
|
5566
|
+
const memoryDir = memoryManager.getMemoryDir();
|
|
5567
|
+
switch (action) {
|
|
5568
|
+
case "save": {
|
|
5569
|
+
if (!topic) return makeToolError("topic is required for save action");
|
|
5570
|
+
if (!content) return makeToolError("content is required for save action");
|
|
5571
|
+
memoryManager.ensureDir();
|
|
5572
|
+
const firstLine = content.split("\n")[0].trim();
|
|
5573
|
+
const description = firstLine.length > 100 ? firstLine.slice(0, 100) + "..." : firstLine;
|
|
5574
|
+
const fileContent = buildFrontmatter(topic, description) + content;
|
|
5575
|
+
const topicPath = path22.join(memoryDir, `${topic}.md`);
|
|
5576
|
+
fs21.writeFileSync(topicPath, fileContent, "utf-8");
|
|
5577
|
+
updateIndex(memoryDir);
|
|
5578
|
+
return makeToolResult(`Memory topic "${topic}" saved to ${topicPath}`);
|
|
5579
|
+
}
|
|
5580
|
+
case "delete": {
|
|
5581
|
+
if (!topic) return makeToolError("topic is required for delete action");
|
|
5582
|
+
const topicPath = path22.join(memoryDir, `${topic}.md`);
|
|
5583
|
+
if (!fs21.existsSync(topicPath)) {
|
|
5584
|
+
return makeToolError(`Memory topic "${topic}" not found`);
|
|
5585
|
+
}
|
|
5586
|
+
fs21.unlinkSync(topicPath);
|
|
5587
|
+
memoryManager.ensureDir();
|
|
5588
|
+
updateIndex(memoryDir);
|
|
5589
|
+
return makeToolResult(`Memory topic "${topic}" deleted`);
|
|
5590
|
+
}
|
|
5591
|
+
case "list": {
|
|
5592
|
+
const topics = memoryManager.listTopics();
|
|
5593
|
+
if (topics.length === 0) {
|
|
5594
|
+
return makeToolResult("No memory topics saved yet.");
|
|
5595
|
+
}
|
|
5596
|
+
const lines = [];
|
|
5597
|
+
for (const t of topics) {
|
|
5598
|
+
const raw = memoryManager.loadTopic(t);
|
|
5599
|
+
if (raw) {
|
|
5600
|
+
const parsed = parseFrontmatter(raw);
|
|
5601
|
+
lines.push(`- ${t}: ${parsed.description || "(no description)"}`);
|
|
5602
|
+
} else {
|
|
5603
|
+
lines.push(`- ${t}: (no description)`);
|
|
5604
|
+
}
|
|
5605
|
+
}
|
|
5606
|
+
return makeToolResult(`Memory topics (${topics.length}):
|
|
5607
|
+
${lines.join("\n")}
|
|
5608
|
+
|
|
5609
|
+
Memory dir: ${memoryDir}`);
|
|
5610
|
+
}
|
|
5611
|
+
default:
|
|
5612
|
+
return makeToolError(`Unknown action: ${action}. Use save, delete, or list.`);
|
|
5613
|
+
}
|
|
5614
|
+
}
|
|
5615
|
+
};
|
|
5616
|
+
|
|
4255
5617
|
// src/llm/anthropic.ts
|
|
4256
5618
|
init_esm_shims();
|
|
4257
5619
|
import Anthropic from "@anthropic-ai/sdk";
|
|
@@ -4907,12 +6269,12 @@ function parseArgs(argv) {
|
|
|
4907
6269
|
}
|
|
4908
6270
|
function printHelp() {
|
|
4909
6271
|
console.log(`
|
|
4910
|
-
${
|
|
6272
|
+
${chalk14.cyan.bold("Codi (\uCF54\uB514)")} - AI Code Agent for Terminal
|
|
4911
6273
|
|
|
4912
|
-
${
|
|
6274
|
+
${chalk14.bold("Usage:")}
|
|
4913
6275
|
codi [options] [prompt]
|
|
4914
6276
|
|
|
4915
|
-
${
|
|
6277
|
+
${chalk14.bold("Options:")}
|
|
4916
6278
|
-m, --model <model> Set the model (default: gemini-2.5-flash)
|
|
4917
6279
|
--provider <name> Set the provider (openai, anthropic, ollama)
|
|
4918
6280
|
-p <prompt> Run a single prompt and exit
|
|
@@ -4923,12 +6285,12 @@ ${chalk13.bold("Options:")}
|
|
|
4923
6285
|
-h, --help Show this help
|
|
4924
6286
|
-v, --version Show version
|
|
4925
6287
|
|
|
4926
|
-
${
|
|
6288
|
+
${chalk14.bold("Environment:")}
|
|
4927
6289
|
GEMINI_API_KEY Google Gemini API key (default provider)
|
|
4928
6290
|
OPENAI_API_KEY OpenAI API key
|
|
4929
6291
|
ANTHROPIC_API_KEY Anthropic API key
|
|
4930
6292
|
|
|
4931
|
-
${
|
|
6293
|
+
${chalk14.bold("Examples:")}
|
|
4932
6294
|
codi # Start interactive session
|
|
4933
6295
|
codi -p "explain main.ts" # Single prompt
|
|
4934
6296
|
codi --provider anthropic # Use Anthropic Claude
|
|
@@ -4944,11 +6306,11 @@ async function main() {
|
|
|
4944
6306
|
}
|
|
4945
6307
|
if (args.version) {
|
|
4946
6308
|
try {
|
|
4947
|
-
const { readFileSync:
|
|
6309
|
+
const { readFileSync: readFileSync17 } = await import("fs");
|
|
4948
6310
|
const { fileURLToPath: fileURLToPath3 } = await import("url");
|
|
4949
6311
|
const p = await import("path");
|
|
4950
6312
|
const dir = p.dirname(fileURLToPath3(import.meta.url));
|
|
4951
|
-
const pkg = JSON.parse(
|
|
6313
|
+
const pkg = JSON.parse(readFileSync17(p.join(dir, "..", "package.json"), "utf-8"));
|
|
4952
6314
|
console.log(`codi v${pkg.version}`);
|
|
4953
6315
|
} catch {
|
|
4954
6316
|
console.log("codi v0.1.8");
|
|
@@ -5012,7 +6374,8 @@ async function main() {
|
|
|
5012
6374
|
taskUpdateTool,
|
|
5013
6375
|
taskListTool,
|
|
5014
6376
|
taskGetTool,
|
|
5015
|
-
askUserTool
|
|
6377
|
+
askUserTool,
|
|
6378
|
+
updateMemoryTool
|
|
5016
6379
|
]);
|
|
5017
6380
|
const subAgentHandler2 = createSubAgentHandler(provider, registry);
|
|
5018
6381
|
setSubAgentHandler(subAgentHandler2);
|
|
@@ -5055,10 +6418,11 @@ async function main() {
|
|
|
5055
6418
|
if (msg.role === "user") conversation.addUserMessage(msg.content);
|
|
5056
6419
|
else if (msg.role === "assistant") conversation.addAssistantMessage(msg.content);
|
|
5057
6420
|
}
|
|
5058
|
-
console.log(
|
|
6421
|
+
console.log(chalk14.dim(`Resumed session: ${id}`));
|
|
5059
6422
|
}
|
|
5060
6423
|
}
|
|
5061
6424
|
}
|
|
6425
|
+
logger.info("\uC138\uC158 \uC2DC\uC791", { provider: providerName, model: modelName, cwd: process.cwd(), planMode: getMode() === "plan", yolo: !!args.yolo });
|
|
5062
6426
|
await hookManager.runHooks("SessionStart", { cwd: process.cwd() });
|
|
5063
6427
|
if (args.prompt) {
|
|
5064
6428
|
await agentLoop(args.prompt, {
|
|
@@ -5073,6 +6437,7 @@ async function main() {
|
|
|
5073
6437
|
},
|
|
5074
6438
|
planMode: getMode() === "plan"
|
|
5075
6439
|
});
|
|
6440
|
+
logger.info("\uC138\uC158 \uC885\uB8CC (single prompt)");
|
|
5076
6441
|
await hookManager.runHooks("SessionEnd", {});
|
|
5077
6442
|
await mcpManager.disconnectAll();
|
|
5078
6443
|
process.exit(0);
|
|
@@ -5084,7 +6449,7 @@ async function main() {
|
|
|
5084
6449
|
compressor,
|
|
5085
6450
|
exitFn: async () => {
|
|
5086
6451
|
stopSpinner();
|
|
5087
|
-
console.log(
|
|
6452
|
+
console.log(chalk14.dim("\nSaving session..."));
|
|
5088
6453
|
sessionManager.save(conversation, void 0, provider.model);
|
|
5089
6454
|
await hookManager.runHooks("SessionEnd", {});
|
|
5090
6455
|
await mcpManager.disconnectAll();
|
|
@@ -5114,7 +6479,7 @@ async function main() {
|
|
|
5114
6479
|
const preview = typeof message === "string" ? message.slice(0, 50) : message.find((b) => b.type === "text")?.text?.slice(0, 50) || "image";
|
|
5115
6480
|
checkpointManager.create(conversation, preview);
|
|
5116
6481
|
if (compressor.shouldCompress(conversation)) {
|
|
5117
|
-
console.log(
|
|
6482
|
+
console.log(chalk14.dim("Auto-compacting conversation..."));
|
|
5118
6483
|
await compressor.compress(conversation, provider);
|
|
5119
6484
|
conversation.setSystemPrompt(buildPrompt());
|
|
5120
6485
|
}
|
|
@@ -5143,14 +6508,17 @@ async function main() {
|
|
|
5143
6508
|
},
|
|
5144
6509
|
onExit: async () => {
|
|
5145
6510
|
stopSpinner();
|
|
5146
|
-
|
|
6511
|
+
logger.info("\uC138\uC158 \uC885\uB8CC (REPL exit)");
|
|
6512
|
+
console.log(chalk14.dim("\nSaving session..."));
|
|
5147
6513
|
sessionManager.save(conversation, void 0, provider.model);
|
|
6514
|
+
checkpointManager.cleanup();
|
|
5148
6515
|
await hookManager.runHooks("SessionEnd", {});
|
|
5149
6516
|
await mcpManager.disconnectAll();
|
|
5150
6517
|
}
|
|
5151
6518
|
});
|
|
5152
6519
|
process.on("SIGTERM", async () => {
|
|
5153
6520
|
stopSpinner();
|
|
6521
|
+
logger.info("\uC138\uC158 \uC885\uB8CC (SIGTERM)");
|
|
5154
6522
|
sessionManager.save(conversation, void 0, provider.model);
|
|
5155
6523
|
await hookManager.runHooks("SessionEnd", {});
|
|
5156
6524
|
await mcpManager.disconnectAll();
|
|
@@ -5159,7 +6527,8 @@ async function main() {
|
|
|
5159
6527
|
await repl.start();
|
|
5160
6528
|
}
|
|
5161
6529
|
main().catch((err) => {
|
|
5162
|
-
|
|
6530
|
+
logger.error("\uCE58\uBA85\uC801 \uC624\uB958", {}, err instanceof Error ? err : new Error(String(err)));
|
|
6531
|
+
console.error(chalk14.red(`Fatal error: ${err.message}`));
|
|
5163
6532
|
console.error(err.stack);
|
|
5164
6533
|
process.exit(1);
|
|
5165
6534
|
});
|