@gemdoq/codi 0.1.9 → 0.2.2
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 +2122 -733
- 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,47 @@ 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";
|
|
886
|
+
|
|
887
|
+
// src/ui/stdin-prompt.ts
|
|
888
|
+
init_esm_shims();
|
|
889
|
+
var _handler = null;
|
|
890
|
+
function registerPromptHandler(handler) {
|
|
891
|
+
_handler = handler;
|
|
892
|
+
}
|
|
893
|
+
function unregisterPromptHandler() {
|
|
894
|
+
_handler = null;
|
|
895
|
+
}
|
|
896
|
+
async function sharedPrompt(prompt) {
|
|
897
|
+
if (_handler) {
|
|
898
|
+
return _handler(prompt);
|
|
899
|
+
}
|
|
900
|
+
const readline3 = await import("readline/promises");
|
|
901
|
+
const rl = readline3.createInterface({
|
|
902
|
+
input: process.stdin,
|
|
903
|
+
output: process.stdout
|
|
904
|
+
});
|
|
905
|
+
try {
|
|
906
|
+
const answer = await rl.question(prompt);
|
|
907
|
+
return answer;
|
|
908
|
+
} finally {
|
|
909
|
+
rl.removeAllListeners();
|
|
910
|
+
rl.terminal = false;
|
|
911
|
+
rl.close();
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// src/repl.ts
|
|
785
916
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
786
917
|
var __dirname2 = path5.dirname(__filename2);
|
|
918
|
+
var HISTORY_DIR = path5.join(os3.homedir(), ".codi");
|
|
919
|
+
var HISTORY_FILE = path5.join(HISTORY_DIR, "history");
|
|
920
|
+
var MAX_HISTORY = 1e3;
|
|
787
921
|
function getVersion() {
|
|
788
922
|
try {
|
|
789
|
-
const pkg = JSON.parse(
|
|
923
|
+
const pkg = JSON.parse(readFileSync4(path5.join(__dirname2, "..", "package.json"), "utf-8"));
|
|
790
924
|
return `v${pkg.version}`;
|
|
791
925
|
} catch {
|
|
792
926
|
return "v0.1.4";
|
|
@@ -815,15 +949,50 @@ var Repl = class {
|
|
|
815
949
|
description: "Clear screen"
|
|
816
950
|
});
|
|
817
951
|
}
|
|
952
|
+
loadHistory() {
|
|
953
|
+
try {
|
|
954
|
+
const content = fs4.readFileSync(HISTORY_FILE, "utf-8");
|
|
955
|
+
return content.split("\n").filter(Boolean).slice(-MAX_HISTORY);
|
|
956
|
+
} catch {
|
|
957
|
+
return [];
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
saveHistory() {
|
|
961
|
+
if (!this.rl) return;
|
|
962
|
+
try {
|
|
963
|
+
const rlAny = this.rl;
|
|
964
|
+
const history = rlAny.history ?? [];
|
|
965
|
+
const entries = history.slice(0, MAX_HISTORY).reverse();
|
|
966
|
+
fs4.mkdirSync(HISTORY_DIR, { recursive: true });
|
|
967
|
+
fs4.writeFileSync(HISTORY_FILE, entries.join("\n") + "\n", "utf-8");
|
|
968
|
+
} catch {
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
shouldSaveToHistory(line) {
|
|
972
|
+
const trimmed = line.trim();
|
|
973
|
+
if (!trimmed) return false;
|
|
974
|
+
if (trimmed.startsWith("/")) return false;
|
|
975
|
+
return true;
|
|
976
|
+
}
|
|
818
977
|
async start() {
|
|
819
978
|
this.running = true;
|
|
979
|
+
const loadedHistory = this.loadHistory();
|
|
820
980
|
this.rl = readline2.createInterface({
|
|
821
981
|
input: input2,
|
|
822
982
|
output: output2,
|
|
823
983
|
prompt: renderPrompt(),
|
|
824
984
|
completer: (line) => completer(line),
|
|
825
|
-
terminal: true
|
|
985
|
+
terminal: true,
|
|
986
|
+
history: loadedHistory,
|
|
987
|
+
historySize: MAX_HISTORY
|
|
826
988
|
});
|
|
989
|
+
const rlAny = this.rl;
|
|
990
|
+
if (rlAny.history && loadedHistory.length > 0) {
|
|
991
|
+
rlAny.history.length = 0;
|
|
992
|
+
for (let i = loadedHistory.length - 1; i >= 0; i--) {
|
|
993
|
+
rlAny.history.push(loadedHistory[i]);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
827
996
|
if (process.stdin.isTTY && os3.platform() !== "win32") {
|
|
828
997
|
process.stdout.write("\x1B[?2004h");
|
|
829
998
|
}
|
|
@@ -848,6 +1017,15 @@ var Repl = class {
|
|
|
848
1017
|
}
|
|
849
1018
|
});
|
|
850
1019
|
}
|
|
1020
|
+
registerPromptHandler((prompt) => {
|
|
1021
|
+
if (!this.rl) return Promise.reject(new Error("REPL not running"));
|
|
1022
|
+
process.stdout.write(prompt);
|
|
1023
|
+
return new Promise((resolve11) => {
|
|
1024
|
+
this.rl.once("line", (answer) => {
|
|
1025
|
+
resolve11(answer);
|
|
1026
|
+
});
|
|
1027
|
+
});
|
|
1028
|
+
});
|
|
851
1029
|
this.printWelcome();
|
|
852
1030
|
while (this.running) {
|
|
853
1031
|
try {
|
|
@@ -857,10 +1035,10 @@ var Repl = class {
|
|
|
857
1035
|
}
|
|
858
1036
|
this.rl.setPrompt(renderPrompt());
|
|
859
1037
|
this.rl.prompt();
|
|
860
|
-
const line = await new Promise((
|
|
1038
|
+
const line = await new Promise((resolve11, reject) => {
|
|
861
1039
|
const onLine = (data) => {
|
|
862
1040
|
cleanup();
|
|
863
|
-
|
|
1041
|
+
resolve11(data);
|
|
864
1042
|
};
|
|
865
1043
|
const onClose = () => {
|
|
866
1044
|
cleanup();
|
|
@@ -876,7 +1054,7 @@ var Repl = class {
|
|
|
876
1054
|
this.lastInterruptTime = now;
|
|
877
1055
|
this.options.onInterrupt();
|
|
878
1056
|
console.log(chalk4.dim("\n(Press Ctrl+C again to exit)"));
|
|
879
|
-
|
|
1057
|
+
resolve11("");
|
|
880
1058
|
};
|
|
881
1059
|
const cleanup = () => {
|
|
882
1060
|
this.rl.removeListener("line", onLine);
|
|
@@ -889,6 +1067,17 @@ var Repl = class {
|
|
|
889
1067
|
});
|
|
890
1068
|
const trimmed = line.trim();
|
|
891
1069
|
if (!trimmed) continue;
|
|
1070
|
+
{
|
|
1071
|
+
const rlAny2 = this.rl;
|
|
1072
|
+
const hist = rlAny2.history;
|
|
1073
|
+
if (hist && hist.length > 0) {
|
|
1074
|
+
if (!this.shouldSaveToHistory(trimmed)) {
|
|
1075
|
+
hist.shift();
|
|
1076
|
+
} else if (hist.length > 1 && hist[0] === hist[1]) {
|
|
1077
|
+
hist.shift();
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
892
1081
|
if (trimmed.endsWith("\\")) {
|
|
893
1082
|
this.multilineBuffer.push(trimmed.slice(0, -1));
|
|
894
1083
|
this.inMultiline = true;
|
|
@@ -972,7 +1161,7 @@ var Repl = class {
|
|
|
972
1161
|
try {
|
|
973
1162
|
const ext = path5.extname(filePath).toLowerCase();
|
|
974
1163
|
if (IMAGE_EXTS.has(ext)) {
|
|
975
|
-
const data =
|
|
1164
|
+
const data = readFileSync4(filePath);
|
|
976
1165
|
const base64 = data.toString("base64");
|
|
977
1166
|
const mime = MIME_MAP[ext] || "image/png";
|
|
978
1167
|
imageBlocks.push({
|
|
@@ -982,7 +1171,7 @@ var Repl = class {
|
|
|
982
1171
|
message = message.replace(match, `[\uC774\uBBF8\uC9C0: ${path5.basename(filePath)}]`);
|
|
983
1172
|
hasImages = true;
|
|
984
1173
|
} else {
|
|
985
|
-
const content =
|
|
1174
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
986
1175
|
message = message.replace(match, `
|
|
987
1176
|
[File: ${filePath}]
|
|
988
1177
|
\`\`\`
|
|
@@ -1014,12 +1203,14 @@ ${content}
|
|
|
1014
1203
|
}
|
|
1015
1204
|
stop() {
|
|
1016
1205
|
this.running = false;
|
|
1206
|
+
unregisterPromptHandler();
|
|
1017
1207
|
if (this.rl) {
|
|
1018
1208
|
this.rl.close();
|
|
1019
1209
|
this.rl = null;
|
|
1020
1210
|
}
|
|
1021
1211
|
}
|
|
1022
1212
|
async gracefulExit() {
|
|
1213
|
+
this.saveHistory();
|
|
1023
1214
|
this.stop();
|
|
1024
1215
|
if (this.options.onExit) {
|
|
1025
1216
|
await this.options.onExit();
|
|
@@ -1046,6 +1237,64 @@ init_esm_shims();
|
|
|
1046
1237
|
|
|
1047
1238
|
// src/agent/conversation.ts
|
|
1048
1239
|
init_esm_shims();
|
|
1240
|
+
|
|
1241
|
+
// src/utils/tokenizer.ts
|
|
1242
|
+
init_esm_shims();
|
|
1243
|
+
import { getEncoding, encodingForModel } from "js-tiktoken";
|
|
1244
|
+
var encoderCache = /* @__PURE__ */ new Map();
|
|
1245
|
+
function getEncoder(model) {
|
|
1246
|
+
const cacheKey = model ?? "cl100k_base";
|
|
1247
|
+
const cached = encoderCache.get(cacheKey);
|
|
1248
|
+
if (cached) return cached;
|
|
1249
|
+
try {
|
|
1250
|
+
const encoder = model ? encodingForModel(model) : getEncoding("cl100k_base");
|
|
1251
|
+
encoderCache.set(cacheKey, encoder);
|
|
1252
|
+
return encoder;
|
|
1253
|
+
} catch {
|
|
1254
|
+
const fallback = encoderCache.get("cl100k_base");
|
|
1255
|
+
if (fallback) return fallback;
|
|
1256
|
+
const encoder = getEncoding("cl100k_base");
|
|
1257
|
+
encoderCache.set("cl100k_base", encoder);
|
|
1258
|
+
return encoder;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
function countTokens(text, model) {
|
|
1262
|
+
if (!text) return 0;
|
|
1263
|
+
try {
|
|
1264
|
+
const encoder = getEncoder(model);
|
|
1265
|
+
return encoder.encode(text).length;
|
|
1266
|
+
} catch {
|
|
1267
|
+
return Math.ceil(text.length / 4);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
function countContentBlockTokens(blocks, model) {
|
|
1271
|
+
let total = 0;
|
|
1272
|
+
for (const block of blocks) {
|
|
1273
|
+
if (block.type === "text") {
|
|
1274
|
+
total += countTokens(block.text, model);
|
|
1275
|
+
} else if (block.type === "tool_use") {
|
|
1276
|
+
total += countTokens(block.name, model);
|
|
1277
|
+
total += countTokens(JSON.stringify(block.input), model);
|
|
1278
|
+
} else if (block.type === "tool_result") {
|
|
1279
|
+
if (typeof block.content === "string") {
|
|
1280
|
+
total += countTokens(block.content, model);
|
|
1281
|
+
} else {
|
|
1282
|
+
total += countTokens(JSON.stringify(block.content), model);
|
|
1283
|
+
}
|
|
1284
|
+
} else {
|
|
1285
|
+
total += countTokens(JSON.stringify(block), model);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
return total;
|
|
1289
|
+
}
|
|
1290
|
+
function countMessageTokens(content, model) {
|
|
1291
|
+
if (typeof content === "string") {
|
|
1292
|
+
return countTokens(content, model);
|
|
1293
|
+
}
|
|
1294
|
+
return countContentBlockTokens(content, model);
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
// src/agent/conversation.ts
|
|
1049
1298
|
var Conversation = class _Conversation {
|
|
1050
1299
|
messages = [];
|
|
1051
1300
|
systemPrompt = "";
|
|
@@ -1131,24 +1380,14 @@ ${summary}` },
|
|
|
1131
1380
|
return conv;
|
|
1132
1381
|
}
|
|
1133
1382
|
/**
|
|
1134
|
-
*
|
|
1383
|
+
* tiktoken을 사용하여 정확한 토큰 수를 계산한다.
|
|
1135
1384
|
*/
|
|
1136
|
-
estimateTokens() {
|
|
1137
|
-
let
|
|
1385
|
+
estimateTokens(model) {
|
|
1386
|
+
let tokens = countTokens(this.systemPrompt, model);
|
|
1138
1387
|
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
|
-
}
|
|
1388
|
+
tokens += countMessageTokens(msg.content, model);
|
|
1150
1389
|
}
|
|
1151
|
-
return
|
|
1390
|
+
return tokens;
|
|
1152
1391
|
}
|
|
1153
1392
|
};
|
|
1154
1393
|
|
|
@@ -1156,147 +1395,672 @@ ${summary}` },
|
|
|
1156
1395
|
init_esm_shims();
|
|
1157
1396
|
init_tool();
|
|
1158
1397
|
init_renderer();
|
|
1398
|
+
|
|
1399
|
+
// src/ui/spinner.ts
|
|
1400
|
+
init_esm_shims();
|
|
1401
|
+
import ora from "ora";
|
|
1159
1402
|
import chalk5 from "chalk";
|
|
1160
|
-
var
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1403
|
+
var currentSpinner = null;
|
|
1404
|
+
function startSpinner(text) {
|
|
1405
|
+
stopSpinner();
|
|
1406
|
+
currentSpinner = ora({
|
|
1407
|
+
text: chalk5.dim(text),
|
|
1408
|
+
spinner: "dots",
|
|
1409
|
+
color: "cyan"
|
|
1410
|
+
}).start();
|
|
1411
|
+
return currentSpinner;
|
|
1412
|
+
}
|
|
1413
|
+
function updateSpinner(text) {
|
|
1414
|
+
if (currentSpinner) {
|
|
1415
|
+
currentSpinner.text = chalk5.dim(text);
|
|
1164
1416
|
}
|
|
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));
|
|
1417
|
+
}
|
|
1418
|
+
function stopSpinner(symbol) {
|
|
1419
|
+
if (currentSpinner) {
|
|
1420
|
+
if (symbol) {
|
|
1421
|
+
currentSpinner.stopAndPersist({ symbol });
|
|
1422
|
+
} else {
|
|
1423
|
+
currentSpinner.stop();
|
|
1211
1424
|
}
|
|
1212
|
-
|
|
1425
|
+
currentSpinner = null;
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
// src/tools/bash.ts
|
|
1430
|
+
init_esm_shims();
|
|
1431
|
+
init_tool();
|
|
1432
|
+
import { spawn } from "child_process";
|
|
1433
|
+
import * as os5 from "os";
|
|
1434
|
+
import chalk7 from "chalk";
|
|
1435
|
+
|
|
1436
|
+
// src/security/command-validator.ts
|
|
1437
|
+
init_esm_shims();
|
|
1438
|
+
|
|
1439
|
+
// src/utils/logger.ts
|
|
1440
|
+
init_esm_shims();
|
|
1441
|
+
import * as fs5 from "fs";
|
|
1442
|
+
import * as path6 from "path";
|
|
1443
|
+
import * as os4 from "os";
|
|
1444
|
+
var LOG_LEVEL_PRIORITY = {
|
|
1445
|
+
debug: 0,
|
|
1446
|
+
info: 1,
|
|
1447
|
+
warn: 2,
|
|
1448
|
+
error: 3
|
|
1449
|
+
};
|
|
1450
|
+
var Logger = class _Logger {
|
|
1451
|
+
static instance;
|
|
1452
|
+
level;
|
|
1453
|
+
logDir;
|
|
1454
|
+
initialized = false;
|
|
1455
|
+
constructor(logDir) {
|
|
1456
|
+
const envLevel = process.env["CODI_LOG_LEVEL"]?.toLowerCase();
|
|
1457
|
+
this.level = this.isValidLevel(envLevel) ? envLevel : "info";
|
|
1458
|
+
this.logDir = logDir ?? path6.join(os4.homedir(), ".codi", "logs");
|
|
1459
|
+
}
|
|
1460
|
+
static getInstance() {
|
|
1461
|
+
if (!_Logger.instance) {
|
|
1462
|
+
_Logger.instance = new _Logger();
|
|
1463
|
+
}
|
|
1464
|
+
return _Logger.instance;
|
|
1465
|
+
}
|
|
1466
|
+
/** 테스트용: 커스텀 logDir로 새 인스턴스 생성 */
|
|
1467
|
+
static createForTest(logDir, level = "debug") {
|
|
1468
|
+
const instance = new _Logger(logDir);
|
|
1469
|
+
instance.level = level;
|
|
1470
|
+
return instance;
|
|
1471
|
+
}
|
|
1472
|
+
isValidLevel(val) {
|
|
1473
|
+
return val !== void 0 && val in LOG_LEVEL_PRIORITY;
|
|
1474
|
+
}
|
|
1475
|
+
ensureDir() {
|
|
1476
|
+
if (this.initialized) return;
|
|
1213
1477
|
try {
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
);
|
|
1219
|
-
}
|
|
1220
|
-
if (this.options.showToolCalls) {
|
|
1221
|
-
console.log(renderToolResult(toolCall.name, result.output, !result.success));
|
|
1478
|
+
fs5.mkdirSync(this.logDir, { recursive: true });
|
|
1479
|
+
this.initialized = true;
|
|
1480
|
+
this.rotateOldLogs();
|
|
1481
|
+
} catch {
|
|
1222
1482
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1483
|
+
}
|
|
1484
|
+
/** 7일 이상 된 로그 파일 삭제 */
|
|
1485
|
+
rotateOldLogs() {
|
|
1486
|
+
try {
|
|
1487
|
+
const files = fs5.readdirSync(this.logDir);
|
|
1488
|
+
const now = Date.now();
|
|
1489
|
+
const maxAge = 7 * 24 * 60 * 60 * 1e3;
|
|
1490
|
+
for (const file of files) {
|
|
1491
|
+
if (!file.startsWith("codi-") || !file.endsWith(".log")) continue;
|
|
1492
|
+
const filePath = path6.join(this.logDir, file);
|
|
1493
|
+
try {
|
|
1494
|
+
const stat = fs5.statSync(filePath);
|
|
1495
|
+
if (now - stat.mtimeMs > maxAge) {
|
|
1496
|
+
fs5.unlinkSync(filePath);
|
|
1497
|
+
}
|
|
1498
|
+
} catch {
|
|
1499
|
+
}
|
|
1227
1500
|
}
|
|
1501
|
+
} catch {
|
|
1228
1502
|
}
|
|
1229
|
-
return {
|
|
1230
|
-
toolUseId: toolCall.id,
|
|
1231
|
-
toolName: toolCall.name,
|
|
1232
|
-
result
|
|
1233
|
-
};
|
|
1234
1503
|
}
|
|
1235
|
-
|
|
1236
|
-
const
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1504
|
+
getLogFilePath() {
|
|
1505
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1506
|
+
return path6.join(this.logDir, `codi-${date}.log`);
|
|
1507
|
+
}
|
|
1508
|
+
shouldLog(level) {
|
|
1509
|
+
return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[this.level];
|
|
1510
|
+
}
|
|
1511
|
+
write(level, message, context, error) {
|
|
1512
|
+
if (!this.shouldLog(level)) return;
|
|
1513
|
+
this.ensureDir();
|
|
1514
|
+
if (!this.initialized) return;
|
|
1515
|
+
const entry = {
|
|
1516
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1517
|
+
level,
|
|
1518
|
+
message
|
|
1519
|
+
};
|
|
1520
|
+
if (context && Object.keys(context).length > 0) {
|
|
1521
|
+
entry.context = context;
|
|
1245
1522
|
}
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1523
|
+
if (error) {
|
|
1524
|
+
entry.error = {
|
|
1525
|
+
message: error.message,
|
|
1526
|
+
stack: error.stack
|
|
1527
|
+
};
|
|
1251
1528
|
}
|
|
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
|
-
}
|
|
1529
|
+
try {
|
|
1530
|
+
fs5.appendFileSync(this.getLogFilePath(), JSON.stringify(entry) + "\n");
|
|
1531
|
+
} catch {
|
|
1265
1532
|
}
|
|
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
1533
|
}
|
|
1271
|
-
|
|
1272
|
-
|
|
1534
|
+
debug(message, context) {
|
|
1535
|
+
this.write("debug", message, context);
|
|
1536
|
+
}
|
|
1537
|
+
info(message, context) {
|
|
1538
|
+
this.write("info", message, context);
|
|
1539
|
+
}
|
|
1540
|
+
warn(message, context) {
|
|
1541
|
+
this.write("warn", message, context);
|
|
1542
|
+
}
|
|
1543
|
+
error(message, context, error) {
|
|
1544
|
+
this.write("error", message, context, error);
|
|
1273
1545
|
}
|
|
1274
1546
|
};
|
|
1547
|
+
var logger = Logger.getInstance();
|
|
1275
1548
|
|
|
1276
|
-
// src/
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1549
|
+
// src/security/command-validator.ts
|
|
1550
|
+
var BLOCKED_PATTERNS = [
|
|
1551
|
+
// 광범위 삭제
|
|
1552
|
+
{ 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." },
|
|
1553
|
+
{ pattern: /\brm\s+-rf\s*$/, reason: "\uB300\uC0C1 \uC5C6\uB294 rm -rf\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1554
|
+
{ pattern: /\bRemove-Item\s+.*-Recurse.*[/\\]\s*$/, reason: "PowerShell \uAD11\uBC94\uC704 \uC0AD\uC81C\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1555
|
+
{ pattern: /\bRemove-Item\s+.*-Recurse.*(\*|~|[A-Z]:\\)/, reason: "PowerShell \uAD11\uBC94\uC704 \uC0AD\uC81C\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1556
|
+
// 디스크 연산
|
|
1557
|
+
{ pattern: /\bmkfs\b/, reason: "\uD30C\uC77C\uC2DC\uC2A4\uD15C \uD3EC\uB9F7(mkfs) \uBA85\uB839\uC5B4\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1558
|
+
{ pattern: /\bformat\s+[A-Z]:/i, reason: "\uB514\uC2A4\uD06C \uD3EC\uB9F7(format) \uBA85\uB839\uC5B4\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1559
|
+
{ pattern: /\bdd\s+if=/, reason: "dd \uB514\uC2A4\uD06C \uC4F0\uAE30 \uBA85\uB839\uC5B4\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1560
|
+
// 광범위 권한 변경
|
|
1561
|
+
{ 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." },
|
|
1562
|
+
{ 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." },
|
|
1563
|
+
// 포크 폭탄
|
|
1564
|
+
{ pattern: /:\(\)\s*\{.*\|.*&\s*\}\s*;?\s*:/, reason: "\uD3EC\uD06C \uD3ED\uD0C4\uC774 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1565
|
+
{ pattern: /\bwhile\s+true\s*;\s*do\s+fork/, reason: "\uD3EC\uD06C \uD3ED\uD0C4 \uD328\uD134\uC774 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1566
|
+
// 디바이스 리디렉션
|
|
1567
|
+
{ pattern: />\s*\/dev\/sd[a-z]/, reason: "\uB514\uBC14\uC774\uC2A4 \uC9C1\uC811 \uC4F0\uAE30\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1568
|
+
{ pattern: />\s*\/dev\/nvme/, reason: "\uB514\uBC14\uC774\uC2A4 \uC9C1\uC811 \uC4F0\uAE30\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1569
|
+
{ pattern: />\s*\\\\\.\\PhysicalDrive/, reason: "Windows \uB514\uBC14\uC774\uC2A4 \uC9C1\uC811 \uC4F0\uAE30\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1570
|
+
// 인터넷에서 받아서 바로 실행
|
|
1571
|
+
{ 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." },
|
|
1572
|
+
{ 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." },
|
|
1573
|
+
{ 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." },
|
|
1574
|
+
{ 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." },
|
|
1575
|
+
{ 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." },
|
|
1576
|
+
// 시스템 종료/재부팅
|
|
1577
|
+
{ pattern: /\bshutdown\b/, reason: "\uC2DC\uC2A4\uD15C \uC885\uB8CC(shutdown) \uBA85\uB839\uC5B4\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1578
|
+
{ pattern: /\breboot\b/, reason: "\uC2DC\uC2A4\uD15C \uC7AC\uBD80\uD305(reboot) \uBA85\uB839\uC5B4\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1579
|
+
{ pattern: /\bhalt\b/, reason: "\uC2DC\uC2A4\uD15C \uC911\uC9C0(halt) \uBA85\uB839\uC5B4\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1580
|
+
{ pattern: /\bStop-Computer\b/, reason: "PowerShell \uC2DC\uC2A4\uD15C \uC885\uB8CC\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1581
|
+
{ pattern: /\bRestart-Computer\b/, reason: "PowerShell \uC2DC\uC2A4\uD15C \uC7AC\uBD80\uD305\uC774 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1582
|
+
// 프로세스 종료
|
|
1583
|
+
{ 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." },
|
|
1584
|
+
{ pattern: /\bkillall\b/, reason: "\uC804\uCCB4 \uD504\uB85C\uC138\uC2A4 \uC885\uB8CC(killall)\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1585
|
+
{ 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." }
|
|
1586
|
+
];
|
|
1587
|
+
var WARNED_PATTERNS = [
|
|
1588
|
+
{ 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." },
|
|
1589
|
+
{ 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." },
|
|
1590
|
+
{ pattern: /\bsudo\b/, reason: "sudo \uBA85\uB839\uC5B4\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1591
|
+
{ pattern: /\bRunAs\b/i, reason: "Windows \uAD00\uB9AC\uC790 \uAD8C\uD55C \uC2E4\uD589\uC774 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1592
|
+
{ pattern: /\bnpm\s+publish\b/, reason: "npm \uD328\uD0A4\uC9C0 \uBC30\uD3EC(npm publish)\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1593
|
+
{ pattern: /\bdocker\s+push\b/, reason: "Docker \uC774\uBBF8\uC9C0 \uD478\uC2DC(docker push)\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1594
|
+
{ pattern: /\bgit\s+push\b/, reason: "git push\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." }
|
|
1595
|
+
];
|
|
1596
|
+
function validateCommand(command) {
|
|
1597
|
+
const trimmed = command.trim();
|
|
1598
|
+
if (!trimmed) {
|
|
1599
|
+
return { allowed: true, level: "allowed" };
|
|
1600
|
+
}
|
|
1601
|
+
for (const { pattern, reason } of BLOCKED_PATTERNS) {
|
|
1602
|
+
if (pattern.test(trimmed)) {
|
|
1603
|
+
logger.warn("\uBA85\uB839\uC5B4 \uCC28\uB2E8\uB428", { command: trimmed, reason });
|
|
1604
|
+
return { allowed: false, level: "blocked", reason };
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
for (const { pattern, reason } of WARNED_PATTERNS) {
|
|
1608
|
+
if (pattern.test(trimmed)) {
|
|
1609
|
+
logger.info("\uBA85\uB839\uC5B4 \uACBD\uACE0", { command: trimmed, reason });
|
|
1610
|
+
return { allowed: true, level: "warned", reason };
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
return { allowed: true, level: "allowed" };
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
// src/security/permission-manager.ts
|
|
1617
|
+
init_esm_shims();
|
|
1618
|
+
import chalk6 from "chalk";
|
|
1619
|
+
|
|
1620
|
+
// src/config/permissions.ts
|
|
1621
|
+
init_esm_shims();
|
|
1622
|
+
function parseRule(rule) {
|
|
1623
|
+
const match = rule.match(/^(\w+)(?:\((.+)\))?$/);
|
|
1624
|
+
if (match) {
|
|
1625
|
+
return { tool: match[1], pattern: match[2] };
|
|
1626
|
+
}
|
|
1627
|
+
return { tool: rule };
|
|
1628
|
+
}
|
|
1629
|
+
function matchesRule(rule, toolName, input3) {
|
|
1630
|
+
if (rule.tool !== toolName) return false;
|
|
1631
|
+
if (!rule.pattern) return true;
|
|
1632
|
+
const pattern = new RegExp(rule.pattern.replace(/\*/g, ".*"));
|
|
1633
|
+
for (const value of Object.values(input3)) {
|
|
1634
|
+
if (typeof value === "string" && pattern.test(value)) {
|
|
1635
|
+
return true;
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
return false;
|
|
1639
|
+
}
|
|
1640
|
+
function evaluatePermission(toolName, input3, rules) {
|
|
1641
|
+
for (const rule of rules.deny) {
|
|
1642
|
+
if (matchesRule(parseRule(rule), toolName, input3)) {
|
|
1643
|
+
return "deny";
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
for (const rule of rules.ask) {
|
|
1647
|
+
if (matchesRule(parseRule(rule), toolName, input3)) {
|
|
1648
|
+
return "ask";
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
for (const rule of rules.allow) {
|
|
1652
|
+
if (matchesRule(parseRule(rule), toolName, input3)) {
|
|
1653
|
+
return "allow";
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
return "ask";
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
// src/security/permission-manager.ts
|
|
1660
|
+
var sessionAllowed = /* @__PURE__ */ new Set();
|
|
1661
|
+
var sessionDenied = /* @__PURE__ */ new Set();
|
|
1662
|
+
var currentMode = "default";
|
|
1663
|
+
function setPermissionMode(mode) {
|
|
1664
|
+
currentMode = mode;
|
|
1665
|
+
}
|
|
1666
|
+
function getPermissionMode() {
|
|
1667
|
+
return currentMode;
|
|
1668
|
+
}
|
|
1669
|
+
async function checkPermission(tool, input3) {
|
|
1670
|
+
if (!tool.dangerous) return true;
|
|
1671
|
+
if (currentMode === "yolo") return true;
|
|
1672
|
+
if (currentMode === "acceptEdits" && ["write_file", "edit_file", "multi_edit"].includes(tool.name)) {
|
|
1673
|
+
return true;
|
|
1674
|
+
}
|
|
1675
|
+
if (currentMode === "plan" && !tool.readOnly) {
|
|
1676
|
+
return false;
|
|
1677
|
+
}
|
|
1678
|
+
const config = configManager.get();
|
|
1679
|
+
const decision = evaluatePermission(tool.name, input3, config.permissions);
|
|
1680
|
+
if (decision === "allow") return true;
|
|
1681
|
+
if (decision === "deny") {
|
|
1682
|
+
console.log(chalk6.red(`\u2717 Permission denied for ${tool.name} (denied by rule)`));
|
|
1683
|
+
return false;
|
|
1684
|
+
}
|
|
1685
|
+
const key = `${tool.name}:${JSON.stringify(input3)}`;
|
|
1686
|
+
if (sessionAllowed.has(tool.name)) return true;
|
|
1687
|
+
if (sessionDenied.has(tool.name)) return false;
|
|
1688
|
+
return promptUser(tool, input3);
|
|
1689
|
+
}
|
|
1690
|
+
async function promptUser(tool, input3) {
|
|
1691
|
+
console.log("");
|
|
1692
|
+
console.log(chalk6.yellow.bold(`\u26A0 Permission Required: ${tool.name}`));
|
|
1693
|
+
const relevantParams = Object.entries(input3).filter(([, v]) => v !== void 0);
|
|
1694
|
+
for (const [key, value] of relevantParams) {
|
|
1695
|
+
const displayValue = typeof value === "string" && value.length > 200 ? value.slice(0, 200) + "..." : String(value);
|
|
1696
|
+
console.log(chalk6.dim(` ${key}: `) + displayValue);
|
|
1697
|
+
}
|
|
1698
|
+
console.log("");
|
|
1699
|
+
try {
|
|
1700
|
+
const answer = await sharedPrompt(
|
|
1701
|
+
chalk6.yellow(`Allow? [${chalk6.bold("Y")}es / ${chalk6.bold("n")}o / ${chalk6.bold("a")}lways for this tool] `)
|
|
1702
|
+
);
|
|
1703
|
+
const choice = answer.trim().toLowerCase();
|
|
1704
|
+
if (choice === "a" || choice === "always") {
|
|
1705
|
+
sessionAllowed.add(tool.name);
|
|
1706
|
+
return true;
|
|
1707
|
+
}
|
|
1708
|
+
if (choice === "n" || choice === "no") {
|
|
1709
|
+
return false;
|
|
1710
|
+
}
|
|
1711
|
+
return true;
|
|
1712
|
+
} catch {
|
|
1713
|
+
return false;
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
// src/tools/bash.ts
|
|
1718
|
+
var _currentOnOutput = null;
|
|
1719
|
+
function setBashOutputCallback(cb) {
|
|
1720
|
+
_currentOnOutput = cb;
|
|
1721
|
+
}
|
|
1722
|
+
function getDefaultShell() {
|
|
1723
|
+
if (os5.platform() === "win32") {
|
|
1724
|
+
return "powershell.exe";
|
|
1725
|
+
}
|
|
1726
|
+
return process.env["SHELL"] || "/bin/bash";
|
|
1727
|
+
}
|
|
1728
|
+
var backgroundTasks = /* @__PURE__ */ new Map();
|
|
1729
|
+
var taskCounter = 0;
|
|
1730
|
+
var bashTool = {
|
|
1731
|
+
name: "bash",
|
|
1732
|
+
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).`,
|
|
1733
|
+
inputSchema: {
|
|
1734
|
+
type: "object",
|
|
1735
|
+
properties: {
|
|
1736
|
+
command: { type: "string", description: "The bash command to execute" },
|
|
1737
|
+
description: { type: "string", description: "Brief description of what the command does" },
|
|
1738
|
+
timeout: { type: "number", description: "Timeout in milliseconds (max 600000, default 120000)" },
|
|
1739
|
+
run_in_background: { type: "boolean", description: "Run in background and return a task ID" }
|
|
1740
|
+
},
|
|
1741
|
+
required: ["command"]
|
|
1742
|
+
},
|
|
1743
|
+
dangerous: true,
|
|
1744
|
+
readOnly: false,
|
|
1745
|
+
async execute(input3) {
|
|
1746
|
+
const command = String(input3["command"]);
|
|
1747
|
+
const timeout = Math.min(Number(input3["timeout"]) || 12e4, 6e5);
|
|
1748
|
+
const runInBackground = input3["run_in_background"] === true;
|
|
1749
|
+
if (!command.trim()) {
|
|
1750
|
+
return makeToolError("Command cannot be empty");
|
|
1751
|
+
}
|
|
1752
|
+
if (getPermissionMode() !== "yolo") {
|
|
1753
|
+
const validation = validateCommand(command);
|
|
1754
|
+
if (!validation.allowed) {
|
|
1755
|
+
return makeToolError(`\uBA85\uB839\uC5B4\uAC00 \uCC28\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${validation.reason}`);
|
|
1756
|
+
}
|
|
1757
|
+
if (validation.level === "warned") {
|
|
1758
|
+
console.log(chalk7.yellow(`\u26A0 \uACBD\uACE0: ${validation.reason}`));
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
if (runInBackground) {
|
|
1762
|
+
return runBackgroundTask(command);
|
|
1763
|
+
}
|
|
1764
|
+
return new Promise((resolve11) => {
|
|
1765
|
+
const finalCommand = os5.platform() === "win32" ? `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; ${command}` : command;
|
|
1766
|
+
const startTime = Date.now();
|
|
1767
|
+
let stdout = "";
|
|
1768
|
+
let stderr = "";
|
|
1769
|
+
let timedOut = false;
|
|
1770
|
+
const proc = spawn(finalCommand, {
|
|
1771
|
+
shell: getDefaultShell(),
|
|
1772
|
+
cwd: process.cwd(),
|
|
1773
|
+
env: { ...process.env },
|
|
1774
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1775
|
+
});
|
|
1776
|
+
const timer = setTimeout(() => {
|
|
1777
|
+
timedOut = true;
|
|
1778
|
+
proc.kill("SIGTERM");
|
|
1779
|
+
setTimeout(() => proc.kill("SIGKILL"), 5e3);
|
|
1780
|
+
}, timeout);
|
|
1781
|
+
const onOutput = _currentOnOutput;
|
|
1782
|
+
proc.stdout?.on("data", (data) => {
|
|
1783
|
+
const chunk = data.toString();
|
|
1784
|
+
stdout += chunk;
|
|
1785
|
+
if (onOutput) {
|
|
1786
|
+
onOutput(chunk);
|
|
1787
|
+
}
|
|
1788
|
+
});
|
|
1789
|
+
proc.stderr?.on("data", (data) => {
|
|
1790
|
+
const chunk = data.toString();
|
|
1791
|
+
stderr += chunk;
|
|
1792
|
+
if (onOutput) {
|
|
1793
|
+
onOutput(chunk);
|
|
1794
|
+
}
|
|
1795
|
+
});
|
|
1796
|
+
proc.on("close", (code) => {
|
|
1797
|
+
clearTimeout(timer);
|
|
1798
|
+
const durationMs = Date.now() - startTime;
|
|
1799
|
+
const exitCode = code ?? 1;
|
|
1800
|
+
if (timedOut) {
|
|
1801
|
+
logger.info("bash \uBA85\uB839\uC5B4 \uC2E4\uD589", { command, exitCode, durationMs, timedOut: true });
|
|
1802
|
+
const output4 = [
|
|
1803
|
+
stdout ? `stdout:
|
|
1804
|
+
${stdout}` : "",
|
|
1805
|
+
stderr ? `stderr:
|
|
1806
|
+
${stderr}` : "",
|
|
1807
|
+
`Exit code: ${exitCode}`
|
|
1808
|
+
].filter(Boolean).join("\n\n");
|
|
1809
|
+
resolve11(makeToolError(`Command timed out after ${timeout / 1e3}s
|
|
1810
|
+
${output4}`));
|
|
1811
|
+
return;
|
|
1812
|
+
}
|
|
1813
|
+
if (exitCode !== 0) {
|
|
1814
|
+
logger.info("bash \uBA85\uB839\uC5B4 \uC2E4\uD589", { command, exitCode, durationMs, timedOut: false });
|
|
1815
|
+
const output4 = [
|
|
1816
|
+
stdout ? `stdout:
|
|
1817
|
+
${stdout}` : "",
|
|
1818
|
+
stderr ? `stderr:
|
|
1819
|
+
${stderr}` : "",
|
|
1820
|
+
`Exit code: ${exitCode}`
|
|
1821
|
+
].filter(Boolean).join("\n\n");
|
|
1822
|
+
resolve11(makeToolResult(output4 || `Command failed with exit code ${exitCode}`));
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
1825
|
+
logger.info("bash \uBA85\uB839\uC5B4 \uC2E4\uD589", { command, exitCode: 0, durationMs });
|
|
1826
|
+
const output3 = [
|
|
1827
|
+
stdout ? stdout : "",
|
|
1828
|
+
stderr ? `stderr:
|
|
1829
|
+
${stderr}` : ""
|
|
1830
|
+
].filter(Boolean).join("\n");
|
|
1831
|
+
resolve11(makeToolResult(output3 || "(no output)"));
|
|
1832
|
+
});
|
|
1833
|
+
proc.on("error", (err) => {
|
|
1834
|
+
clearTimeout(timer);
|
|
1835
|
+
const durationMs = Date.now() - startTime;
|
|
1836
|
+
logger.info("bash \uBA85\uB839\uC5B4 \uC2E4\uD589 \uC2E4\uD328", { command, durationMs, error: err.message });
|
|
1837
|
+
resolve11(makeToolError(`Failed to execute command: ${err.message}`));
|
|
1838
|
+
});
|
|
1839
|
+
});
|
|
1840
|
+
}
|
|
1841
|
+
};
|
|
1842
|
+
function runBackgroundTask(command) {
|
|
1843
|
+
const taskId = `bg_${++taskCounter}`;
|
|
1844
|
+
const bgCommand = os5.platform() === "win32" ? `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; ${command}` : command;
|
|
1845
|
+
const proc = spawn(bgCommand, {
|
|
1846
|
+
shell: getDefaultShell(),
|
|
1847
|
+
cwd: process.cwd(),
|
|
1848
|
+
env: { ...process.env },
|
|
1849
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1850
|
+
detached: false
|
|
1851
|
+
});
|
|
1852
|
+
const task = { process: proc, output: "", status: "running", exitCode: void 0 };
|
|
1853
|
+
backgroundTasks.set(taskId, task);
|
|
1854
|
+
proc.stdout?.on("data", (data) => {
|
|
1855
|
+
task.output += data.toString();
|
|
1856
|
+
});
|
|
1857
|
+
proc.stderr?.on("data", (data) => {
|
|
1858
|
+
task.output += data.toString();
|
|
1859
|
+
});
|
|
1860
|
+
proc.on("close", (code) => {
|
|
1861
|
+
task.status = code === 0 ? "done" : "error";
|
|
1862
|
+
task.exitCode = code ?? 1;
|
|
1863
|
+
});
|
|
1864
|
+
proc.on("error", (err) => {
|
|
1865
|
+
task.status = "error";
|
|
1866
|
+
task.output += `
|
|
1867
|
+
Process error: ${err.message}`;
|
|
1868
|
+
});
|
|
1869
|
+
return makeToolResult(`Background task started with ID: ${taskId}
|
|
1870
|
+
Use task_output tool to check results.`);
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
// src/tools/executor.ts
|
|
1874
|
+
import chalk8 from "chalk";
|
|
1875
|
+
var ToolExecutor = class {
|
|
1876
|
+
constructor(registry, options = {}) {
|
|
1877
|
+
this.registry = registry;
|
|
1878
|
+
this.options = options;
|
|
1879
|
+
}
|
|
1880
|
+
async executeOne(toolCall) {
|
|
1881
|
+
const tool = this.registry.get(toolCall.name);
|
|
1882
|
+
if (!tool) {
|
|
1883
|
+
return {
|
|
1884
|
+
toolUseId: toolCall.id,
|
|
1885
|
+
toolName: toolCall.name,
|
|
1886
|
+
result: makeToolError(`Unknown tool: ${toolCall.name}. Available tools: ${this.registry.listNames().join(", ")}`)
|
|
1887
|
+
};
|
|
1888
|
+
}
|
|
1889
|
+
if (this.options.planMode && !tool.readOnly) {
|
|
1890
|
+
return {
|
|
1891
|
+
toolUseId: toolCall.id,
|
|
1892
|
+
toolName: toolCall.name,
|
|
1893
|
+
result: makeToolError(`Tool '${toolCall.name}' is not available in plan mode (read-only). Use only read-only tools.`)
|
|
1894
|
+
};
|
|
1895
|
+
}
|
|
1896
|
+
if (tool.dangerous && this.options.permissionCheck) {
|
|
1897
|
+
const allowed = await this.options.permissionCheck(tool, toolCall.input);
|
|
1898
|
+
if (!allowed) {
|
|
1899
|
+
return {
|
|
1900
|
+
toolUseId: toolCall.id,
|
|
1901
|
+
toolName: toolCall.name,
|
|
1902
|
+
result: makeToolError(`Permission denied for tool: ${toolCall.name}`)
|
|
1903
|
+
};
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
let input3 = toolCall.input;
|
|
1907
|
+
if (this.options.preHook) {
|
|
1908
|
+
try {
|
|
1909
|
+
const hookResult = await this.options.preHook(toolCall.name, input3);
|
|
1910
|
+
if (!hookResult.proceed) {
|
|
1911
|
+
return {
|
|
1912
|
+
toolUseId: toolCall.id,
|
|
1913
|
+
toolName: toolCall.name,
|
|
1914
|
+
result: makeToolError(`Tool execution blocked by hook for: ${toolCall.name}`)
|
|
1915
|
+
};
|
|
1916
|
+
}
|
|
1917
|
+
if (hookResult.updatedInput) {
|
|
1918
|
+
input3 = hookResult.updatedInput;
|
|
1919
|
+
}
|
|
1920
|
+
} catch (err) {
|
|
1921
|
+
console.error(chalk8.yellow(`Hook error for ${toolCall.name}: ${err}`));
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
if (this.options.showToolCalls) {
|
|
1925
|
+
console.log(renderToolCall(toolCall.name, input3));
|
|
1926
|
+
}
|
|
1927
|
+
let result;
|
|
1928
|
+
const execStart = Date.now();
|
|
1929
|
+
let elapsedTimer = null;
|
|
1930
|
+
if (this.options.showToolCalls) {
|
|
1931
|
+
elapsedTimer = setInterval(() => {
|
|
1932
|
+
const elapsed = ((Date.now() - execStart) / 1e3).toFixed(1);
|
|
1933
|
+
updateSpinner(`${toolCall.name} (${elapsed}s...)`);
|
|
1934
|
+
}, 200);
|
|
1935
|
+
startSpinner(`${toolCall.name}...`);
|
|
1936
|
+
if (toolCall.name === "bash") {
|
|
1937
|
+
setBashOutputCallback((_chunk) => {
|
|
1938
|
+
const elapsed = ((Date.now() - execStart) / 1e3).toFixed(1);
|
|
1939
|
+
updateSpinner(`${toolCall.name} (${elapsed}s...) \u25B8 \uCD9C\uB825 \uC218\uC2E0 \uC911`);
|
|
1940
|
+
});
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
try {
|
|
1944
|
+
result = await tool.execute(input3);
|
|
1945
|
+
} catch (err) {
|
|
1946
|
+
result = makeToolError(
|
|
1947
|
+
`Tool '${toolCall.name}' threw an error: ${err instanceof Error ? err.message : String(err)}`
|
|
1948
|
+
);
|
|
1949
|
+
} finally {
|
|
1950
|
+
if (elapsedTimer) {
|
|
1951
|
+
clearInterval(elapsedTimer);
|
|
1952
|
+
}
|
|
1953
|
+
if (toolCall.name === "bash") {
|
|
1954
|
+
setBashOutputCallback(null);
|
|
1955
|
+
}
|
|
1956
|
+
stopSpinner();
|
|
1957
|
+
}
|
|
1958
|
+
const execDuration = Date.now() - execStart;
|
|
1959
|
+
logger.debug("\uB3C4\uAD6C \uC2E4\uD589 \uC644\uB8CC", {
|
|
1960
|
+
tool: toolCall.name,
|
|
1961
|
+
durationMs: execDuration,
|
|
1962
|
+
success: result.success
|
|
1963
|
+
});
|
|
1964
|
+
if (this.options.showToolCalls) {
|
|
1965
|
+
console.log(renderToolResult(toolCall.name, result.output, !result.success, execDuration));
|
|
1966
|
+
}
|
|
1967
|
+
if (this.options.postHook) {
|
|
1968
|
+
try {
|
|
1969
|
+
await this.options.postHook(toolCall.name, input3, result);
|
|
1970
|
+
} catch {
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
return {
|
|
1974
|
+
toolUseId: toolCall.id,
|
|
1975
|
+
toolName: toolCall.name,
|
|
1976
|
+
result
|
|
1977
|
+
};
|
|
1978
|
+
}
|
|
1979
|
+
async executeMany(toolCalls) {
|
|
1980
|
+
const safeCalls = [];
|
|
1981
|
+
const dangerousCalls = [];
|
|
1982
|
+
for (const tc of toolCalls) {
|
|
1983
|
+
const tool = this.registry.get(tc.name);
|
|
1984
|
+
if (tool?.dangerous) {
|
|
1985
|
+
dangerousCalls.push(tc);
|
|
1986
|
+
} else {
|
|
1987
|
+
safeCalls.push(tc);
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
const safePromises = safeCalls.map((tc) => this.executeOne(tc));
|
|
1991
|
+
const dangerousResults = [];
|
|
1992
|
+
for (const tc of dangerousCalls) {
|
|
1993
|
+
const result = await this.executeOne(tc);
|
|
1994
|
+
dangerousResults.push(result);
|
|
1995
|
+
}
|
|
1996
|
+
const safeResults = await Promise.allSettled(safePromises);
|
|
1997
|
+
const results = [];
|
|
1998
|
+
for (let i = 0; i < safeResults.length; i++) {
|
|
1999
|
+
const r = safeResults[i];
|
|
2000
|
+
if (r.status === "fulfilled") {
|
|
2001
|
+
results.push(r.value);
|
|
2002
|
+
} else {
|
|
2003
|
+
results.push({
|
|
2004
|
+
toolUseId: safeCalls[i].id,
|
|
2005
|
+
toolName: safeCalls[i].name,
|
|
2006
|
+
result: makeToolError(`Tool execution failed: ${r.reason}`)
|
|
2007
|
+
});
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
results.push(...dangerousResults);
|
|
2011
|
+
const orderMap = new Map(toolCalls.map((tc, i) => [tc.id, i]));
|
|
2012
|
+
results.sort((a, b) => (orderMap.get(a.toolUseId) ?? 0) - (orderMap.get(b.toolUseId) ?? 0));
|
|
2013
|
+
return results;
|
|
2014
|
+
}
|
|
2015
|
+
setOptions(options) {
|
|
2016
|
+
Object.assign(this.options, options);
|
|
2017
|
+
}
|
|
2018
|
+
};
|
|
2019
|
+
|
|
2020
|
+
// src/agent/token-tracker.ts
|
|
2021
|
+
init_esm_shims();
|
|
2022
|
+
|
|
2023
|
+
// src/llm/types.ts
|
|
2024
|
+
init_esm_shims();
|
|
1281
2025
|
var MODEL_COSTS = {
|
|
2026
|
+
// Claude models
|
|
1282
2027
|
"claude-sonnet-4-20250514": { input: 3e-3, output: 0.015 },
|
|
1283
2028
|
"claude-opus-4-20250514": { input: 0.015, output: 0.075 },
|
|
1284
2029
|
"claude-haiku-3-5-20241022": { input: 8e-4, output: 4e-3 },
|
|
2030
|
+
// GPT models
|
|
1285
2031
|
"gpt-4o": { input: 25e-4, output: 0.01 },
|
|
1286
2032
|
"gpt-4o-mini": { input: 15e-5, output: 6e-4 },
|
|
1287
2033
|
"gpt-4.1": { input: 2e-3, output: 8e-3 },
|
|
1288
2034
|
"gpt-4.1-mini": { input: 4e-4, output: 16e-4 },
|
|
1289
|
-
"gpt-4.1-nano": { input: 1e-4, output: 4e-4 }
|
|
2035
|
+
"gpt-4.1-nano": { input: 1e-4, output: 4e-4 },
|
|
2036
|
+
"o1": { input: 0.015, output: 0.06 },
|
|
2037
|
+
"o1-mini": { input: 3e-3, output: 0.012 },
|
|
2038
|
+
"o3-mini": { input: 11e-4, output: 44e-4 },
|
|
2039
|
+
// Gemini models
|
|
2040
|
+
"gemini-2.5-flash": { input: 15e-5, output: 6e-4 },
|
|
2041
|
+
"gemini-2.5-pro": { input: 125e-5, output: 0.01 },
|
|
2042
|
+
"gemini-2.0-flash": { input: 1e-4, output: 4e-4 },
|
|
2043
|
+
"gemini-1.5-flash": { input: 75e-6, output: 3e-4 },
|
|
2044
|
+
"gemini-1.5-pro": { input: 125e-5, output: 5e-3 }
|
|
1290
2045
|
};
|
|
1291
2046
|
function getModelCost(model) {
|
|
2047
|
+
if (model.startsWith("ollama:") || model.startsWith("ollama/")) {
|
|
2048
|
+
return { input: 0, output: 0 };
|
|
2049
|
+
}
|
|
1292
2050
|
return MODEL_COSTS[model] ?? { input: 0, output: 0 };
|
|
1293
2051
|
}
|
|
1294
2052
|
|
|
1295
2053
|
// src/agent/token-tracker.ts
|
|
1296
2054
|
var TokenTracker = class {
|
|
2055
|
+
// Global (accumulated) counters
|
|
1297
2056
|
inputTokens = 0;
|
|
1298
2057
|
outputTokens = 0;
|
|
1299
2058
|
requests = 0;
|
|
2059
|
+
// Per-session counters
|
|
2060
|
+
sessionInputTokens = 0;
|
|
2061
|
+
sessionOutputTokens = 0;
|
|
2062
|
+
sessionRequests = 0;
|
|
2063
|
+
lastRequestCost = null;
|
|
1300
2064
|
model = "";
|
|
1301
2065
|
setModel(model) {
|
|
1302
2066
|
this.model = model;
|
|
@@ -1305,6 +2069,17 @@ var TokenTracker = class {
|
|
|
1305
2069
|
this.inputTokens += usage.input_tokens;
|
|
1306
2070
|
this.outputTokens += usage.output_tokens;
|
|
1307
2071
|
this.requests++;
|
|
2072
|
+
this.sessionInputTokens += usage.input_tokens;
|
|
2073
|
+
this.sessionOutputTokens += usage.output_tokens;
|
|
2074
|
+
this.sessionRequests++;
|
|
2075
|
+
const costs = getModelCost(this.model);
|
|
2076
|
+
const reqCost = usage.input_tokens / 1e3 * costs.input + usage.output_tokens / 1e3 * costs.output;
|
|
2077
|
+
this.lastRequestCost = {
|
|
2078
|
+
inputTokens: usage.input_tokens,
|
|
2079
|
+
outputTokens: usage.output_tokens,
|
|
2080
|
+
cost: reqCost,
|
|
2081
|
+
timestamp: Date.now()
|
|
2082
|
+
};
|
|
1308
2083
|
}
|
|
1309
2084
|
getStats() {
|
|
1310
2085
|
const costs = getModelCost(this.model);
|
|
@@ -1317,6 +2092,25 @@ var TokenTracker = class {
|
|
|
1317
2092
|
requests: this.requests
|
|
1318
2093
|
};
|
|
1319
2094
|
}
|
|
2095
|
+
getSessionStats() {
|
|
2096
|
+
const costs = getModelCost(this.model);
|
|
2097
|
+
const cost = this.sessionInputTokens / 1e3 * costs.input + this.sessionOutputTokens / 1e3 * costs.output;
|
|
2098
|
+
return {
|
|
2099
|
+
inputTokens: this.sessionInputTokens,
|
|
2100
|
+
outputTokens: this.sessionOutputTokens,
|
|
2101
|
+
totalTokens: this.sessionInputTokens + this.sessionOutputTokens,
|
|
2102
|
+
cost,
|
|
2103
|
+
requests: this.sessionRequests,
|
|
2104
|
+
lastRequestCost: this.lastRequestCost,
|
|
2105
|
+
avgCostPerRequest: this.sessionRequests > 0 ? cost / this.sessionRequests : 0
|
|
2106
|
+
};
|
|
2107
|
+
}
|
|
2108
|
+
resetSession() {
|
|
2109
|
+
this.sessionInputTokens = 0;
|
|
2110
|
+
this.sessionOutputTokens = 0;
|
|
2111
|
+
this.sessionRequests = 0;
|
|
2112
|
+
this.lastRequestCost = null;
|
|
2113
|
+
}
|
|
1320
2114
|
getCost() {
|
|
1321
2115
|
return this.getStats().cost;
|
|
1322
2116
|
}
|
|
@@ -1346,39 +2140,9 @@ var TokenTracker = class {
|
|
|
1346
2140
|
};
|
|
1347
2141
|
var tokenTracker = new TokenTracker();
|
|
1348
2142
|
|
|
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
2143
|
// src/agent/agent-loop.ts
|
|
1380
2144
|
init_renderer();
|
|
1381
|
-
import
|
|
2145
|
+
import chalk9 from "chalk";
|
|
1382
2146
|
var MAX_RETRIES = 3;
|
|
1383
2147
|
var RETRY_DELAYS = [1e3, 2e3, 4e3];
|
|
1384
2148
|
async function agentLoop(userMessage, options) {
|
|
@@ -1412,8 +2176,9 @@ async function agentLoop(userMessage, options) {
|
|
|
1412
2176
|
} catch (err) {
|
|
1413
2177
|
stopSpinner();
|
|
1414
2178
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
2179
|
+
logger.error("LLM \uD638\uCD9C \uC2E4\uD328", { model: provider.model }, err instanceof Error ? err : new Error(errMsg));
|
|
1415
2180
|
if (showOutput) {
|
|
1416
|
-
console.error(
|
|
2181
|
+
console.error(chalk9.red(`
|
|
1417
2182
|
LLM Error: ${errMsg}`));
|
|
1418
2183
|
}
|
|
1419
2184
|
return `Error communicating with LLM: ${errMsg}`;
|
|
@@ -1427,6 +2192,13 @@ LLM Error: ${errMsg}`));
|
|
|
1427
2192
|
outputTokens: stats.outputTokens,
|
|
1428
2193
|
cost: stats.cost
|
|
1429
2194
|
});
|
|
2195
|
+
logger.debug("LLM \uC751\uB2F5 \uC218\uC2E0", {
|
|
2196
|
+
model: provider.model,
|
|
2197
|
+
inputTokens: response.usage.input_tokens,
|
|
2198
|
+
outputTokens: response.usage.output_tokens,
|
|
2199
|
+
stopReason: response.stopReason,
|
|
2200
|
+
toolCalls: response.toolCalls?.length ?? 0
|
|
2201
|
+
});
|
|
1430
2202
|
}
|
|
1431
2203
|
conversation.addAssistantMessage(response.content);
|
|
1432
2204
|
const wasStreamed = response._streamed === true;
|
|
@@ -1447,7 +2219,7 @@ LLM Error: ${errMsg}`));
|
|
|
1447
2219
|
}
|
|
1448
2220
|
if (response.stopReason === "max_tokens") {
|
|
1449
2221
|
if (showOutput) {
|
|
1450
|
-
console.log(
|
|
2222
|
+
console.log(chalk9.yellow("\n\u26A0 Response truncated (max tokens reached)"));
|
|
1451
2223
|
}
|
|
1452
2224
|
}
|
|
1453
2225
|
if (response.toolCalls && response.toolCalls.length > 0) {
|
|
@@ -1485,7 +2257,7 @@ LLM Error: ${errMsg}`));
|
|
|
1485
2257
|
}
|
|
1486
2258
|
if (iterations >= maxIterations) {
|
|
1487
2259
|
if (showOutput) {
|
|
1488
|
-
console.log(
|
|
2260
|
+
console.log(chalk9.yellow(`
|
|
1489
2261
|
\u26A0 Agent loop reached maximum iterations (${maxIterations})`));
|
|
1490
2262
|
}
|
|
1491
2263
|
}
|
|
@@ -1539,12 +2311,12 @@ async function callLlmWithRetry(provider, conversation, registry, stream, option
|
|
|
1539
2311
|
throw lastError || new Error("Max retries exceeded");
|
|
1540
2312
|
}
|
|
1541
2313
|
function sleep(ms) {
|
|
1542
|
-
return new Promise((
|
|
2314
|
+
return new Promise((resolve11) => setTimeout(resolve11, ms));
|
|
1543
2315
|
}
|
|
1544
2316
|
|
|
1545
2317
|
// src/agent/system-prompt.ts
|
|
1546
2318
|
init_esm_shims();
|
|
1547
|
-
import * as
|
|
2319
|
+
import * as os6 from "os";
|
|
1548
2320
|
import { execSync as execSync2 } from "child_process";
|
|
1549
2321
|
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
2322
|
|
|
@@ -1604,7 +2376,8 @@ var TOOL_HIERARCHY = `# Tool Usage Rules
|
|
|
1604
2376
|
- Use grep instead of bash grep/rg for content search
|
|
1605
2377
|
- Reserve bash for system commands that have no dedicated tool
|
|
1606
2378
|
- Use sub_agent for complex multi-step exploration tasks
|
|
1607
|
-
- Call multiple tools in parallel when they are independent
|
|
2379
|
+
- Call multiple tools in parallel when they are independent
|
|
2380
|
+
- Use update_memory to persist important information (architecture, user preferences, patterns, decisions) across conversations. Proactively save useful context when you discover it.`;
|
|
1608
2381
|
var WINDOWS_RULES = `# Windows Shell Rules
|
|
1609
2382
|
You are running on Windows. The shell is PowerShell. Follow these rules:
|
|
1610
2383
|
- Use PowerShell syntax, NOT bash/sh syntax
|
|
@@ -1656,7 +2429,7 @@ function buildSystemPrompt(context) {
|
|
|
1656
2429
|
fragments.push(buildEnvironmentInfo(context));
|
|
1657
2430
|
fragments.push(CONVERSATION_RULES);
|
|
1658
2431
|
fragments.push(TOOL_HIERARCHY);
|
|
1659
|
-
if (
|
|
2432
|
+
if (os6.platform() === "win32") {
|
|
1660
2433
|
fragments.push(WINDOWS_RULES);
|
|
1661
2434
|
}
|
|
1662
2435
|
fragments.push(CODE_RULES);
|
|
@@ -1687,8 +2460,8 @@ function buildEnvironmentInfo(context) {
|
|
|
1687
2460
|
const lines = [
|
|
1688
2461
|
"# Environment",
|
|
1689
2462
|
`- Date: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`,
|
|
1690
|
-
`- OS: ${
|
|
1691
|
-
`- Shell: ${
|
|
2463
|
+
`- OS: ${os6.platform()} ${os6.release()}`,
|
|
2464
|
+
`- Shell: ${os6.platform() === "win32" ? "PowerShell" : process.env["SHELL"] || "/bin/bash"}`,
|
|
1692
2465
|
`- Working Directory: ${context.cwd}`,
|
|
1693
2466
|
`- Model: ${context.model}`,
|
|
1694
2467
|
`- Provider: ${context.provider}`
|
|
@@ -1720,7 +2493,7 @@ var ContextCompressor = class {
|
|
|
1720
2493
|
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
1721
2494
|
}
|
|
1722
2495
|
shouldCompress(conversation) {
|
|
1723
|
-
const estimatedTokens = conversation.estimateTokens();
|
|
2496
|
+
const estimatedTokens = conversation.estimateTokens(this.options.model);
|
|
1724
2497
|
return estimatedTokens > this.options.maxContextTokens * this.options.threshold;
|
|
1725
2498
|
}
|
|
1726
2499
|
async compress(conversation, provider, focusHint) {
|
|
@@ -1758,58 +2531,58 @@ ${summaryContent}`;
|
|
|
1758
2531
|
|
|
1759
2532
|
// src/agent/memory.ts
|
|
1760
2533
|
init_esm_shims();
|
|
1761
|
-
import * as
|
|
1762
|
-
import * as
|
|
1763
|
-
import * as
|
|
2534
|
+
import * as fs6 from "fs";
|
|
2535
|
+
import * as os7 from "os";
|
|
2536
|
+
import * as path7 from "path";
|
|
1764
2537
|
import * as crypto from "crypto";
|
|
1765
2538
|
var MemoryManager = class {
|
|
1766
2539
|
memoryDir;
|
|
1767
2540
|
constructor() {
|
|
1768
|
-
const home = process.env["HOME"] || process.env["USERPROFILE"] ||
|
|
2541
|
+
const home = process.env["HOME"] || process.env["USERPROFILE"] || os7.homedir();
|
|
1769
2542
|
const projectHash = crypto.createHash("md5").update(process.cwd()).digest("hex").slice(0, 8);
|
|
1770
|
-
const projectName =
|
|
1771
|
-
this.memoryDir =
|
|
2543
|
+
const projectName = path7.basename(process.cwd());
|
|
2544
|
+
this.memoryDir = path7.join(home, ".codi", "projects", `${projectName}-${projectHash}`, "memory");
|
|
1772
2545
|
}
|
|
1773
2546
|
ensureDir() {
|
|
1774
|
-
if (!
|
|
1775
|
-
|
|
2547
|
+
if (!fs6.existsSync(this.memoryDir)) {
|
|
2548
|
+
fs6.mkdirSync(this.memoryDir, { recursive: true });
|
|
1776
2549
|
}
|
|
1777
2550
|
}
|
|
1778
2551
|
getMemoryDir() {
|
|
1779
2552
|
return this.memoryDir;
|
|
1780
2553
|
}
|
|
1781
2554
|
loadIndex() {
|
|
1782
|
-
const indexPath =
|
|
1783
|
-
if (!
|
|
1784
|
-
const content =
|
|
2555
|
+
const indexPath = path7.join(this.memoryDir, "MEMORY.md");
|
|
2556
|
+
if (!fs6.existsSync(indexPath)) return "";
|
|
2557
|
+
const content = fs6.readFileSync(indexPath, "utf-8");
|
|
1785
2558
|
const lines = content.split("\n");
|
|
1786
2559
|
return lines.slice(0, 200).join("\n");
|
|
1787
2560
|
}
|
|
1788
2561
|
saveIndex(content) {
|
|
1789
2562
|
this.ensureDir();
|
|
1790
|
-
const indexPath =
|
|
1791
|
-
|
|
2563
|
+
const indexPath = path7.join(this.memoryDir, "MEMORY.md");
|
|
2564
|
+
fs6.writeFileSync(indexPath, content, "utf-8");
|
|
1792
2565
|
}
|
|
1793
2566
|
loadTopic(name) {
|
|
1794
|
-
const topicPath =
|
|
1795
|
-
if (!
|
|
1796
|
-
return
|
|
2567
|
+
const topicPath = path7.join(this.memoryDir, `${name}.md`);
|
|
2568
|
+
if (!fs6.existsSync(topicPath)) return null;
|
|
2569
|
+
return fs6.readFileSync(topicPath, "utf-8");
|
|
1797
2570
|
}
|
|
1798
2571
|
saveTopic(name, content) {
|
|
1799
2572
|
this.ensureDir();
|
|
1800
|
-
const topicPath =
|
|
1801
|
-
|
|
2573
|
+
const topicPath = path7.join(this.memoryDir, `${name}.md`);
|
|
2574
|
+
fs6.writeFileSync(topicPath, content, "utf-8");
|
|
1802
2575
|
}
|
|
1803
2576
|
listTopics() {
|
|
1804
|
-
if (!
|
|
1805
|
-
return
|
|
2577
|
+
if (!fs6.existsSync(this.memoryDir)) return [];
|
|
2578
|
+
return fs6.readdirSync(this.memoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md").map((f) => f.replace(".md", ""));
|
|
1806
2579
|
}
|
|
1807
2580
|
buildMemoryPrompt() {
|
|
1808
2581
|
const index = this.loadIndex();
|
|
1809
2582
|
if (!index) return "";
|
|
1810
2583
|
const lines = [
|
|
1811
2584
|
`You have a persistent memory directory at ${this.memoryDir}.`,
|
|
1812
|
-
"Use
|
|
2585
|
+
"Use the update_memory tool to save, delete, or list memory topics as you learn patterns.",
|
|
1813
2586
|
"",
|
|
1814
2587
|
"Current MEMORY.md:",
|
|
1815
2588
|
index
|
|
@@ -1821,25 +2594,25 @@ var memoryManager = new MemoryManager();
|
|
|
1821
2594
|
|
|
1822
2595
|
// src/agent/session.ts
|
|
1823
2596
|
init_esm_shims();
|
|
1824
|
-
import * as
|
|
1825
|
-
import * as
|
|
1826
|
-
import * as
|
|
2597
|
+
import * as fs7 from "fs";
|
|
2598
|
+
import * as os8 from "os";
|
|
2599
|
+
import * as path8 from "path";
|
|
1827
2600
|
import * as crypto2 from "crypto";
|
|
1828
2601
|
var SessionManager = class {
|
|
1829
2602
|
sessionsDir;
|
|
1830
2603
|
constructor() {
|
|
1831
|
-
const home = process.env["HOME"] || process.env["USERPROFILE"] ||
|
|
1832
|
-
this.sessionsDir =
|
|
2604
|
+
const home = process.env["HOME"] || process.env["USERPROFILE"] || os8.homedir();
|
|
2605
|
+
this.sessionsDir = path8.join(home, ".codi", "sessions");
|
|
1833
2606
|
}
|
|
1834
2607
|
ensureDir() {
|
|
1835
|
-
if (!
|
|
1836
|
-
|
|
2608
|
+
if (!fs7.existsSync(this.sessionsDir)) {
|
|
2609
|
+
fs7.mkdirSync(this.sessionsDir, { recursive: true });
|
|
1837
2610
|
}
|
|
1838
2611
|
}
|
|
1839
2612
|
save(conversation, name, model) {
|
|
1840
2613
|
this.ensureDir();
|
|
1841
2614
|
const id = name || crypto2.randomUUID().slice(0, 8);
|
|
1842
|
-
const filePath =
|
|
2615
|
+
const filePath = path8.join(this.sessionsDir, `${id}.jsonl`);
|
|
1843
2616
|
const data = conversation.serialize();
|
|
1844
2617
|
const meta = {
|
|
1845
2618
|
id,
|
|
@@ -1855,13 +2628,13 @@ var SessionManager = class {
|
|
|
1855
2628
|
JSON.stringify({ type: "system", content: data.systemPrompt }),
|
|
1856
2629
|
...data.messages.map((m) => JSON.stringify({ type: "message", ...m }))
|
|
1857
2630
|
];
|
|
1858
|
-
|
|
2631
|
+
fs7.writeFileSync(filePath, lines.join("\n") + "\n", "utf-8");
|
|
1859
2632
|
return id;
|
|
1860
2633
|
}
|
|
1861
2634
|
load(id) {
|
|
1862
|
-
const filePath =
|
|
1863
|
-
if (!
|
|
1864
|
-
const content =
|
|
2635
|
+
const filePath = path8.join(this.sessionsDir, `${id}.jsonl`);
|
|
2636
|
+
if (!fs7.existsSync(filePath)) return null;
|
|
2637
|
+
const content = fs7.readFileSync(filePath, "utf-8");
|
|
1865
2638
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
1866
2639
|
let meta = null;
|
|
1867
2640
|
let systemPrompt = "";
|
|
@@ -1886,12 +2659,12 @@ var SessionManager = class {
|
|
|
1886
2659
|
}
|
|
1887
2660
|
list() {
|
|
1888
2661
|
this.ensureDir();
|
|
1889
|
-
const files =
|
|
2662
|
+
const files = fs7.readdirSync(this.sessionsDir).filter((f) => f.endsWith(".jsonl"));
|
|
1890
2663
|
const sessions = [];
|
|
1891
2664
|
for (const file of files) {
|
|
1892
|
-
const filePath =
|
|
2665
|
+
const filePath = path8.join(this.sessionsDir, file);
|
|
1893
2666
|
try {
|
|
1894
|
-
const firstLine =
|
|
2667
|
+
const firstLine = fs7.readFileSync(filePath, "utf-8").split("\n")[0];
|
|
1895
2668
|
if (firstLine) {
|
|
1896
2669
|
const meta = JSON.parse(firstLine);
|
|
1897
2670
|
if (meta.type === "meta") {
|
|
@@ -1909,9 +2682,9 @@ var SessionManager = class {
|
|
|
1909
2682
|
return sessions[0] ?? null;
|
|
1910
2683
|
}
|
|
1911
2684
|
delete(id) {
|
|
1912
|
-
const filePath =
|
|
1913
|
-
if (
|
|
1914
|
-
|
|
2685
|
+
const filePath = path8.join(this.sessionsDir, `${id}.jsonl`);
|
|
2686
|
+
if (fs7.existsSync(filePath)) {
|
|
2687
|
+
fs7.unlinkSync(filePath);
|
|
1915
2688
|
return true;
|
|
1916
2689
|
}
|
|
1917
2690
|
return false;
|
|
@@ -1933,25 +2706,79 @@ var sessionManager = new SessionManager();
|
|
|
1933
2706
|
|
|
1934
2707
|
// src/agent/checkpoint.ts
|
|
1935
2708
|
init_esm_shims();
|
|
2709
|
+
import * as fs8 from "fs";
|
|
2710
|
+
import * as os9 from "os";
|
|
2711
|
+
import * as path9 from "path";
|
|
2712
|
+
import * as crypto3 from "crypto";
|
|
1936
2713
|
import { execSync as execSync3 } from "child_process";
|
|
2714
|
+
var MAX_CHECKPOINTS = 20;
|
|
1937
2715
|
var CheckpointManager = class {
|
|
1938
2716
|
checkpoints = [];
|
|
1939
2717
|
nextId = 1;
|
|
1940
2718
|
isGitRepo;
|
|
1941
|
-
|
|
2719
|
+
sessionId;
|
|
2720
|
+
checkpointDir;
|
|
2721
|
+
constructor(sessionId) {
|
|
2722
|
+
this.sessionId = sessionId || crypto3.randomUUID().slice(0, 8);
|
|
2723
|
+
const home = process.env["HOME"] || process.env["USERPROFILE"] || os9.homedir();
|
|
2724
|
+
this.checkpointDir = path9.join(home, ".codi", "checkpoints", this.sessionId);
|
|
1942
2725
|
try {
|
|
1943
2726
|
execSync3("git rev-parse --is-inside-work-tree", { stdio: "pipe" });
|
|
1944
2727
|
this.isGitRepo = true;
|
|
1945
2728
|
} catch {
|
|
1946
2729
|
this.isGitRepo = false;
|
|
1947
2730
|
}
|
|
2731
|
+
this.loadFromDisk();
|
|
2732
|
+
}
|
|
2733
|
+
getSessionId() {
|
|
2734
|
+
return this.sessionId;
|
|
2735
|
+
}
|
|
2736
|
+
ensureDir() {
|
|
2737
|
+
if (!fs8.existsSync(this.checkpointDir)) {
|
|
2738
|
+
fs8.mkdirSync(this.checkpointDir, { recursive: true });
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
checkpointPath(id) {
|
|
2742
|
+
return path9.join(this.checkpointDir, `checkpoint-${id}.json`);
|
|
2743
|
+
}
|
|
2744
|
+
saveToDisk(checkpoint) {
|
|
2745
|
+
this.ensureDir();
|
|
2746
|
+
const filePath = this.checkpointPath(checkpoint.id);
|
|
2747
|
+
fs8.writeFileSync(filePath, JSON.stringify(checkpoint), "utf-8");
|
|
2748
|
+
}
|
|
2749
|
+
deleteFromDisk(id) {
|
|
2750
|
+
const filePath = this.checkpointPath(id);
|
|
2751
|
+
if (fs8.existsSync(filePath)) {
|
|
2752
|
+
fs8.unlinkSync(filePath);
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
loadFromDisk() {
|
|
2756
|
+
if (!fs8.existsSync(this.checkpointDir)) return;
|
|
2757
|
+
const files = fs8.readdirSync(this.checkpointDir).filter((f) => f.startsWith("checkpoint-") && f.endsWith(".json")).sort((a, b) => {
|
|
2758
|
+
const idA = parseInt(a.replace("checkpoint-", "").replace(".json", ""), 10);
|
|
2759
|
+
const idB = parseInt(b.replace("checkpoint-", "").replace(".json", ""), 10);
|
|
2760
|
+
return idA - idB;
|
|
2761
|
+
});
|
|
2762
|
+
for (const file of files) {
|
|
2763
|
+
try {
|
|
2764
|
+
const content = fs8.readFileSync(path9.join(this.checkpointDir, file), "utf-8");
|
|
2765
|
+
const checkpoint = JSON.parse(content);
|
|
2766
|
+
this.checkpoints.push(checkpoint);
|
|
2767
|
+
if (checkpoint.id >= this.nextId) {
|
|
2768
|
+
this.nextId = checkpoint.id + 1;
|
|
2769
|
+
}
|
|
2770
|
+
} catch {
|
|
2771
|
+
continue;
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
1948
2774
|
}
|
|
1949
2775
|
create(conversation, description) {
|
|
1950
2776
|
const checkpoint = {
|
|
1951
2777
|
id: this.nextId++,
|
|
1952
2778
|
timestamp: Date.now(),
|
|
1953
2779
|
conversation: conversation.serialize(),
|
|
1954
|
-
description
|
|
2780
|
+
description,
|
|
2781
|
+
messageCount: conversation.getMessageCount()
|
|
1955
2782
|
};
|
|
1956
2783
|
if (this.isGitRepo) {
|
|
1957
2784
|
try {
|
|
@@ -1973,8 +2800,12 @@ var CheckpointManager = class {
|
|
|
1973
2800
|
}
|
|
1974
2801
|
}
|
|
1975
2802
|
this.checkpoints.push(checkpoint);
|
|
1976
|
-
|
|
1977
|
-
|
|
2803
|
+
this.saveToDisk(checkpoint);
|
|
2804
|
+
if (this.checkpoints.length > MAX_CHECKPOINTS) {
|
|
2805
|
+
const removed = this.checkpoints.splice(0, this.checkpoints.length - MAX_CHECKPOINTS);
|
|
2806
|
+
for (const cp of removed) {
|
|
2807
|
+
this.deleteFromDisk(cp.id);
|
|
2808
|
+
}
|
|
1978
2809
|
}
|
|
1979
2810
|
return checkpoint.id;
|
|
1980
2811
|
}
|
|
@@ -1994,7 +2825,10 @@ var CheckpointManager = class {
|
|
|
1994
2825
|
}
|
|
1995
2826
|
const idx = this.checkpoints.indexOf(checkpoint);
|
|
1996
2827
|
if (idx >= 0) {
|
|
1997
|
-
|
|
2828
|
+
const removed = this.checkpoints.splice(idx + 1);
|
|
2829
|
+
for (const cp of removed) {
|
|
2830
|
+
this.deleteFromDisk(cp.id);
|
|
2831
|
+
}
|
|
1998
2832
|
}
|
|
1999
2833
|
return {
|
|
2000
2834
|
conversation: Conversation.deserialize(checkpoint.conversation),
|
|
@@ -2005,54 +2839,64 @@ var CheckpointManager = class {
|
|
|
2005
2839
|
return this.checkpoints.map((cp) => ({
|
|
2006
2840
|
id: cp.id,
|
|
2007
2841
|
timestamp: cp.timestamp,
|
|
2008
|
-
description: cp.description
|
|
2842
|
+
description: cp.description,
|
|
2843
|
+
messageCount: cp.messageCount
|
|
2009
2844
|
}));
|
|
2010
2845
|
}
|
|
2846
|
+
/**
|
|
2847
|
+
* 세션 종료 시 체크포인트 파일 정리
|
|
2848
|
+
*/
|
|
2849
|
+
cleanup() {
|
|
2850
|
+
if (fs8.existsSync(this.checkpointDir)) {
|
|
2851
|
+
fs8.rmSync(this.checkpointDir, { recursive: true, force: true });
|
|
2852
|
+
}
|
|
2853
|
+
this.checkpoints = [];
|
|
2854
|
+
}
|
|
2011
2855
|
};
|
|
2012
2856
|
var checkpointManager = new CheckpointManager();
|
|
2013
2857
|
|
|
2014
2858
|
// src/agent/codi-md.ts
|
|
2015
2859
|
init_esm_shims();
|
|
2016
|
-
import * as
|
|
2017
|
-
import * as
|
|
2860
|
+
import * as fs9 from "fs";
|
|
2861
|
+
import * as path10 from "path";
|
|
2018
2862
|
function loadCodiMd() {
|
|
2019
2863
|
const fragments = [];
|
|
2020
2864
|
let dir = process.cwd();
|
|
2021
|
-
const root =
|
|
2865
|
+
const root = path10.parse(dir).root;
|
|
2022
2866
|
while (dir !== root) {
|
|
2023
2867
|
loadFromDir(dir, fragments);
|
|
2024
|
-
const parent =
|
|
2868
|
+
const parent = path10.dirname(dir);
|
|
2025
2869
|
if (parent === dir) break;
|
|
2026
2870
|
dir = parent;
|
|
2027
2871
|
}
|
|
2028
2872
|
return fragments.join("\n\n---\n\n");
|
|
2029
2873
|
}
|
|
2030
2874
|
function loadFromDir(dir, fragments) {
|
|
2031
|
-
const codiPath =
|
|
2032
|
-
if (
|
|
2875
|
+
const codiPath = path10.join(dir, "CODI.md");
|
|
2876
|
+
if (fs9.existsSync(codiPath)) {
|
|
2033
2877
|
try {
|
|
2034
|
-
let content =
|
|
2878
|
+
let content = fs9.readFileSync(codiPath, "utf-8");
|
|
2035
2879
|
content = processImports(content, dir);
|
|
2036
2880
|
fragments.push(`[CODI.md from ${dir}]
|
|
2037
2881
|
${content}`);
|
|
2038
2882
|
} catch {
|
|
2039
2883
|
}
|
|
2040
2884
|
}
|
|
2041
|
-
const localPath =
|
|
2042
|
-
if (
|
|
2885
|
+
const localPath = path10.join(dir, "CODI.local.md");
|
|
2886
|
+
if (fs9.existsSync(localPath)) {
|
|
2043
2887
|
try {
|
|
2044
|
-
const content =
|
|
2888
|
+
const content = fs9.readFileSync(localPath, "utf-8");
|
|
2045
2889
|
fragments.push(`[CODI.local.md from ${dir}]
|
|
2046
2890
|
${content}`);
|
|
2047
2891
|
} catch {
|
|
2048
2892
|
}
|
|
2049
2893
|
}
|
|
2050
|
-
const rulesDir =
|
|
2051
|
-
if (
|
|
2894
|
+
const rulesDir = path10.join(dir, ".codi", "rules");
|
|
2895
|
+
if (fs9.existsSync(rulesDir)) {
|
|
2052
2896
|
try {
|
|
2053
|
-
const files =
|
|
2897
|
+
const files = fs9.readdirSync(rulesDir).filter((f) => f.endsWith(".md")).sort();
|
|
2054
2898
|
for (const file of files) {
|
|
2055
|
-
const content =
|
|
2899
|
+
const content = fs9.readFileSync(path10.join(rulesDir, file), "utf-8");
|
|
2056
2900
|
fragments.push(`[Rule: ${file}]
|
|
2057
2901
|
${content}`);
|
|
2058
2902
|
}
|
|
@@ -2062,10 +2906,10 @@ ${content}`);
|
|
|
2062
2906
|
}
|
|
2063
2907
|
function processImports(content, baseDir) {
|
|
2064
2908
|
return content.replace(/@([\w./-]+)/g, (match, importPath) => {
|
|
2065
|
-
const resolved =
|
|
2066
|
-
if (
|
|
2909
|
+
const resolved = path10.resolve(baseDir, importPath);
|
|
2910
|
+
if (fs9.existsSync(resolved)) {
|
|
2067
2911
|
try {
|
|
2068
|
-
return
|
|
2912
|
+
return fs9.readFileSync(resolved, "utf-8");
|
|
2069
2913
|
} catch {
|
|
2070
2914
|
return match;
|
|
2071
2915
|
}
|
|
@@ -2076,12 +2920,12 @@ function processImports(content, baseDir) {
|
|
|
2076
2920
|
|
|
2077
2921
|
// src/agent/mode-manager.ts
|
|
2078
2922
|
init_esm_shims();
|
|
2079
|
-
var
|
|
2923
|
+
var currentMode2 = "execute";
|
|
2080
2924
|
function getMode() {
|
|
2081
|
-
return
|
|
2925
|
+
return currentMode2;
|
|
2082
2926
|
}
|
|
2083
2927
|
function setMode(mode) {
|
|
2084
|
-
|
|
2928
|
+
currentMode2 = mode;
|
|
2085
2929
|
}
|
|
2086
2930
|
|
|
2087
2931
|
// src/tools/registry.ts
|
|
@@ -2143,117 +2987,12 @@ var ToolRegistry = class _ToolRegistry {
|
|
|
2143
2987
|
}
|
|
2144
2988
|
};
|
|
2145
2989
|
|
|
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
2990
|
// src/hooks/hook-manager.ts
|
|
2252
2991
|
init_esm_shims();
|
|
2253
2992
|
import { exec } from "child_process";
|
|
2254
|
-
import * as
|
|
2255
|
-
function
|
|
2256
|
-
if (
|
|
2993
|
+
import * as os10 from "os";
|
|
2994
|
+
function getDefaultShell2() {
|
|
2995
|
+
if (os10.platform() === "win32") {
|
|
2257
2996
|
return "powershell.exe";
|
|
2258
2997
|
}
|
|
2259
2998
|
return void 0;
|
|
@@ -2283,41 +3022,41 @@ var HookManager = class {
|
|
|
2283
3022
|
return { proceed: true };
|
|
2284
3023
|
}
|
|
2285
3024
|
async runCommandHook(command, context, timeout) {
|
|
2286
|
-
return new Promise((
|
|
3025
|
+
return new Promise((resolve11) => {
|
|
2287
3026
|
const stdinData = JSON.stringify({
|
|
2288
3027
|
tool: context["tool"],
|
|
2289
3028
|
args: context["args"],
|
|
2290
3029
|
session_id: context["sessionId"],
|
|
2291
3030
|
cwd: context["cwd"] || process.cwd()
|
|
2292
3031
|
});
|
|
2293
|
-
const isWin =
|
|
3032
|
+
const isWin = os10.platform() === "win32";
|
|
2294
3033
|
const finalCommand = isWin ? `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; ${command}` : command;
|
|
2295
3034
|
const proc = exec(finalCommand, {
|
|
2296
3035
|
timeout: timeout || 5e3,
|
|
2297
3036
|
cwd: process.cwd(),
|
|
2298
3037
|
env: { ...process.env },
|
|
2299
|
-
shell:
|
|
3038
|
+
shell: getDefaultShell2()
|
|
2300
3039
|
}, (err, stdout, stderr) => {
|
|
2301
3040
|
if (err) {
|
|
2302
3041
|
if (err.code === 2) {
|
|
2303
|
-
|
|
3042
|
+
resolve11({
|
|
2304
3043
|
proceed: false,
|
|
2305
3044
|
reason: stderr || stdout || "Blocked by hook"
|
|
2306
3045
|
});
|
|
2307
3046
|
return;
|
|
2308
3047
|
}
|
|
2309
|
-
|
|
3048
|
+
resolve11({ proceed: true });
|
|
2310
3049
|
return;
|
|
2311
3050
|
}
|
|
2312
3051
|
try {
|
|
2313
3052
|
const output3 = JSON.parse(stdout);
|
|
2314
|
-
|
|
3053
|
+
resolve11({
|
|
2315
3054
|
proceed: output3.decision !== "block",
|
|
2316
3055
|
reason: output3.reason,
|
|
2317
3056
|
updatedInput: output3.updatedInput
|
|
2318
3057
|
});
|
|
2319
3058
|
} catch {
|
|
2320
|
-
|
|
3059
|
+
resolve11({ proceed: true });
|
|
2321
3060
|
}
|
|
2322
3061
|
});
|
|
2323
3062
|
if (proc.stdin) {
|
|
@@ -2332,12 +3071,12 @@ var hookManager = new HookManager();
|
|
|
2332
3071
|
// src/mcp/mcp-manager.ts
|
|
2333
3072
|
init_esm_shims();
|
|
2334
3073
|
init_tool();
|
|
2335
|
-
import * as
|
|
2336
|
-
import * as
|
|
2337
|
-
import * as
|
|
3074
|
+
import * as fs10 from "fs";
|
|
3075
|
+
import * as os11 from "os";
|
|
3076
|
+
import * as path11 from "path";
|
|
2338
3077
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2339
3078
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
2340
|
-
import
|
|
3079
|
+
import chalk10 from "chalk";
|
|
2341
3080
|
var McpManager = class {
|
|
2342
3081
|
servers = /* @__PURE__ */ new Map();
|
|
2343
3082
|
async initialize(registry) {
|
|
@@ -2348,21 +3087,21 @@ var McpManager = class {
|
|
|
2348
3087
|
try {
|
|
2349
3088
|
await this.connectServer(name, serverConfig, registry);
|
|
2350
3089
|
} catch (err) {
|
|
2351
|
-
console.error(
|
|
3090
|
+
console.error(chalk10.yellow(` \u26A0 Failed to connect MCP server '${name}': ${err instanceof Error ? err.message : String(err)}`));
|
|
2352
3091
|
}
|
|
2353
3092
|
}
|
|
2354
3093
|
}
|
|
2355
3094
|
loadMcpConfigs() {
|
|
2356
3095
|
const configs = {};
|
|
2357
|
-
const home = process.env["HOME"] || process.env["USERPROFILE"] ||
|
|
3096
|
+
const home = process.env["HOME"] || process.env["USERPROFILE"] || os11.homedir();
|
|
2358
3097
|
const paths = [
|
|
2359
|
-
|
|
2360
|
-
|
|
3098
|
+
path11.join(home, ".codi", "mcp.json"),
|
|
3099
|
+
path11.join(process.cwd(), ".codi", "mcp.json")
|
|
2361
3100
|
];
|
|
2362
3101
|
for (const p of paths) {
|
|
2363
3102
|
try {
|
|
2364
|
-
if (
|
|
2365
|
-
const content = JSON.parse(
|
|
3103
|
+
if (fs10.existsSync(p)) {
|
|
3104
|
+
const content = JSON.parse(fs10.readFileSync(p, "utf-8"));
|
|
2366
3105
|
if (content.mcpServers) {
|
|
2367
3106
|
Object.assign(configs, content.mcpServers);
|
|
2368
3107
|
}
|
|
@@ -2414,7 +3153,7 @@ var McpManager = class {
|
|
|
2414
3153
|
registry.register(tool);
|
|
2415
3154
|
}
|
|
2416
3155
|
this.servers.set(name, { name, client, transport, tools: toolNames });
|
|
2417
|
-
console.log(
|
|
3156
|
+
console.log(chalk10.dim(` \u2713 MCP server '${name}' connected (${toolNames.length} tools)`));
|
|
2418
3157
|
}
|
|
2419
3158
|
async disconnect(name) {
|
|
2420
3159
|
const server = this.servers.get(name);
|
|
@@ -2445,7 +3184,7 @@ var mcpManager = new McpManager();
|
|
|
2445
3184
|
// src/agent/sub-agent.ts
|
|
2446
3185
|
init_esm_shims();
|
|
2447
3186
|
init_tool();
|
|
2448
|
-
import
|
|
3187
|
+
import chalk11 from "chalk";
|
|
2449
3188
|
var AGENT_PRESETS = {
|
|
2450
3189
|
explore: {
|
|
2451
3190
|
tools: ["read_file", "glob", "grep", "list_dir"],
|
|
@@ -2484,7 +3223,7 @@ function createSubAgentHandler(provider, mainRegistry) {
|
|
|
2484
3223
|
}
|
|
2485
3224
|
const conversation = new Conversation();
|
|
2486
3225
|
const maxIterations = input3["maxIterations"] || preset.maxIterations;
|
|
2487
|
-
console.log(
|
|
3226
|
+
console.log(chalk11.dim(`
|
|
2488
3227
|
\u25B8 Launching ${type} agent: ${task.slice(0, 80)}...`));
|
|
2489
3228
|
const runAgent = async () => {
|
|
2490
3229
|
const result = await agentLoop(task, {
|
|
@@ -2513,7 +3252,7 @@ function createSubAgentHandler(provider, mainRegistry) {
|
|
|
2513
3252
|
}
|
|
2514
3253
|
try {
|
|
2515
3254
|
const result = await runAgent();
|
|
2516
|
-
console.log(
|
|
3255
|
+
console.log(chalk11.dim(` \u25B8 ${type} agent completed.`));
|
|
2517
3256
|
return makeToolResult(result);
|
|
2518
3257
|
} catch (err) {
|
|
2519
3258
|
return makeToolError(`Sub-agent failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -2557,10 +3296,10 @@ var subAgentTool = {
|
|
|
2557
3296
|
|
|
2558
3297
|
// src/config/slash-commands.ts
|
|
2559
3298
|
init_esm_shims();
|
|
2560
|
-
import * as
|
|
2561
|
-
import * as
|
|
2562
|
-
import * as
|
|
2563
|
-
import
|
|
3299
|
+
import * as fs12 from "fs";
|
|
3300
|
+
import * as os13 from "os";
|
|
3301
|
+
import * as path13 from "path";
|
|
3302
|
+
import chalk12 from "chalk";
|
|
2564
3303
|
function createBuiltinCommands() {
|
|
2565
3304
|
return [
|
|
2566
3305
|
{
|
|
@@ -2568,14 +3307,14 @@ function createBuiltinCommands() {
|
|
|
2568
3307
|
description: "Show available commands",
|
|
2569
3308
|
handler: async () => {
|
|
2570
3309
|
const commands = createBuiltinCommands();
|
|
2571
|
-
console.log(
|
|
3310
|
+
console.log(chalk12.bold("\nAvailable Commands:\n"));
|
|
2572
3311
|
for (const cmd of commands) {
|
|
2573
|
-
const aliases = cmd.aliases ?
|
|
2574
|
-
console.log(` ${
|
|
3312
|
+
const aliases = cmd.aliases ? chalk12.dim(` (${cmd.aliases.join(", ")})`) : "";
|
|
3313
|
+
console.log(` ${chalk12.cyan(cmd.name)}${aliases} - ${cmd.description}`);
|
|
2575
3314
|
}
|
|
2576
3315
|
console.log("");
|
|
2577
|
-
console.log(
|
|
2578
|
-
console.log(
|
|
3316
|
+
console.log(chalk12.dim(" Prefixes: ! (bash), @ (file reference)"));
|
|
3317
|
+
console.log(chalk12.dim(" Use \\ at end of line for multiline input"));
|
|
2579
3318
|
console.log("");
|
|
2580
3319
|
return true;
|
|
2581
3320
|
}
|
|
@@ -2588,7 +3327,7 @@ function createBuiltinCommands() {
|
|
|
2588
3327
|
if (ctx.exitFn) {
|
|
2589
3328
|
await ctx.exitFn();
|
|
2590
3329
|
}
|
|
2591
|
-
console.log(
|
|
3330
|
+
console.log(chalk12.dim("\nGoodbye!\n"));
|
|
2592
3331
|
process.exit(0);
|
|
2593
3332
|
}
|
|
2594
3333
|
},
|
|
@@ -2599,7 +3338,7 @@ function createBuiltinCommands() {
|
|
|
2599
3338
|
handler: async (_args, ctx) => {
|
|
2600
3339
|
ctx.conversation.clear();
|
|
2601
3340
|
ctx.reloadSystemPrompt();
|
|
2602
|
-
console.log(
|
|
3341
|
+
console.log(chalk12.green("\u2713 Conversation cleared"));
|
|
2603
3342
|
return true;
|
|
2604
3343
|
}
|
|
2605
3344
|
},
|
|
@@ -2609,7 +3348,7 @@ function createBuiltinCommands() {
|
|
|
2609
3348
|
handler: async (args, ctx) => {
|
|
2610
3349
|
if (!args) {
|
|
2611
3350
|
const info = statusLine.getInfo();
|
|
2612
|
-
console.log(
|
|
3351
|
+
console.log(chalk12.cyan(`Current model: ${info.model} (${info.provider})`));
|
|
2613
3352
|
return true;
|
|
2614
3353
|
}
|
|
2615
3354
|
if (args.includes(":")) {
|
|
@@ -2618,7 +3357,7 @@ function createBuiltinCommands() {
|
|
|
2618
3357
|
} else {
|
|
2619
3358
|
ctx.setProvider("", args);
|
|
2620
3359
|
}
|
|
2621
|
-
console.log(
|
|
3360
|
+
console.log(chalk12.green(`\u2713 Model switched to: ${args}`));
|
|
2622
3361
|
return true;
|
|
2623
3362
|
}
|
|
2624
3363
|
},
|
|
@@ -2626,10 +3365,10 @@ function createBuiltinCommands() {
|
|
|
2626
3365
|
name: "/compact",
|
|
2627
3366
|
description: "Compress conversation history (optional: focus hint)",
|
|
2628
3367
|
handler: async (args, ctx) => {
|
|
2629
|
-
console.log(
|
|
3368
|
+
console.log(chalk12.dim("Compressing conversation..."));
|
|
2630
3369
|
await ctx.compressor.compress(ctx.conversation, ctx.provider, args || void 0);
|
|
2631
3370
|
ctx.reloadSystemPrompt();
|
|
2632
|
-
console.log(
|
|
3371
|
+
console.log(chalk12.green("\u2713 Conversation compressed"));
|
|
2633
3372
|
return true;
|
|
2634
3373
|
}
|
|
2635
3374
|
},
|
|
@@ -2637,9 +3376,31 @@ function createBuiltinCommands() {
|
|
|
2637
3376
|
name: "/cost",
|
|
2638
3377
|
description: "Show token usage and cost",
|
|
2639
3378
|
handler: async () => {
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
3379
|
+
const session = tokenTracker.getSessionStats();
|
|
3380
|
+
const total = tokenTracker.getStats();
|
|
3381
|
+
console.log(chalk12.bold("\nToken Usage & Cost:\n"));
|
|
3382
|
+
console.log(chalk12.cyan(" [Current Session]"));
|
|
3383
|
+
console.log(` Requests: ${session.requests}`);
|
|
3384
|
+
console.log(` Input: ${formatTokens(session.inputTokens)}`);
|
|
3385
|
+
console.log(` Output: ${formatTokens(session.outputTokens)}`);
|
|
3386
|
+
console.log(` Total: ${formatTokens(session.totalTokens)}`);
|
|
3387
|
+
console.log(` Cost: $${session.cost.toFixed(4)}`);
|
|
3388
|
+
if (session.requests > 0) {
|
|
3389
|
+
console.log(` Avg/Request: $${session.avgCostPerRequest.toFixed(4)}`);
|
|
3390
|
+
}
|
|
3391
|
+
if (session.lastRequestCost) {
|
|
3392
|
+
console.log(chalk12.dim(` Last Request: $${session.lastRequestCost.cost.toFixed(4)} (${formatTokens(session.lastRequestCost.inputTokens)} in / ${formatTokens(session.lastRequestCost.outputTokens)} out)`));
|
|
3393
|
+
}
|
|
3394
|
+
if (total.requests !== session.requests) {
|
|
3395
|
+
console.log("");
|
|
3396
|
+
console.log(chalk12.cyan(" [Total Accumulated]"));
|
|
3397
|
+
console.log(` Requests: ${total.requests}`);
|
|
3398
|
+
console.log(` Input: ${formatTokens(total.inputTokens)}`);
|
|
3399
|
+
console.log(` Output: ${formatTokens(total.outputTokens)}`);
|
|
3400
|
+
console.log(` Total: ${formatTokens(total.totalTokens)}`);
|
|
3401
|
+
console.log(` Cost: $${total.cost.toFixed(4)}`);
|
|
3402
|
+
}
|
|
3403
|
+
console.log("");
|
|
2643
3404
|
return true;
|
|
2644
3405
|
}
|
|
2645
3406
|
},
|
|
@@ -2648,9 +3409,9 @@ ${tokenTracker.format()}
|
|
|
2648
3409
|
description: "Show current configuration",
|
|
2649
3410
|
handler: async () => {
|
|
2650
3411
|
const config = configManager.get();
|
|
2651
|
-
console.log(
|
|
2652
|
-
console.log(
|
|
2653
|
-
console.log(
|
|
3412
|
+
console.log(chalk12.bold("\nConfiguration:\n"));
|
|
3413
|
+
console.log(chalk12.dim(JSON.stringify(config, null, 2)));
|
|
3414
|
+
console.log(chalk12.dim(`
|
|
2654
3415
|
Config files: ${configManager.getConfigPaths().join(", ") || "(none)"}`));
|
|
2655
3416
|
console.log("");
|
|
2656
3417
|
return true;
|
|
@@ -2661,10 +3422,10 @@ Config files: ${configManager.getConfigPaths().join(", ") || "(none)"}`));
|
|
|
2661
3422
|
description: "Show permission rules",
|
|
2662
3423
|
handler: async () => {
|
|
2663
3424
|
const config = configManager.get();
|
|
2664
|
-
console.log(
|
|
2665
|
-
console.log(
|
|
2666
|
-
console.log(
|
|
2667
|
-
console.log(
|
|
3425
|
+
console.log(chalk12.bold("\nPermission Rules:\n"));
|
|
3426
|
+
console.log(chalk12.green(" Allow: ") + config.permissions.allow.join(", "));
|
|
3427
|
+
console.log(chalk12.red(" Deny: ") + (config.permissions.deny.join(", ") || "(none)"));
|
|
3428
|
+
console.log(chalk12.yellow(" Ask: ") + config.permissions.ask.join(", "));
|
|
2668
3429
|
console.log("");
|
|
2669
3430
|
return true;
|
|
2670
3431
|
}
|
|
@@ -2675,7 +3436,7 @@ Config files: ${configManager.getConfigPaths().join(", ") || "(none)"}`));
|
|
|
2675
3436
|
handler: async (args, ctx) => {
|
|
2676
3437
|
const name = args || void 0;
|
|
2677
3438
|
const id = sessionManager.save(ctx.conversation, name, statusLine.getInfo().model);
|
|
2678
|
-
console.log(
|
|
3439
|
+
console.log(chalk12.green(`\u2713 Session saved: ${id}`));
|
|
2679
3440
|
return true;
|
|
2680
3441
|
}
|
|
2681
3442
|
},
|
|
@@ -2686,12 +3447,12 @@ Config files: ${configManager.getConfigPaths().join(", ") || "(none)"}`));
|
|
|
2686
3447
|
handler: async (args, ctx) => {
|
|
2687
3448
|
const id = args || sessionManager.getLatest()?.id;
|
|
2688
3449
|
if (!id) {
|
|
2689
|
-
console.log(
|
|
3450
|
+
console.log(chalk12.yellow("No sessions found. Use /save to save a session first."));
|
|
2690
3451
|
return true;
|
|
2691
3452
|
}
|
|
2692
3453
|
const session = sessionManager.load(id);
|
|
2693
3454
|
if (!session) {
|
|
2694
|
-
console.log(
|
|
3455
|
+
console.log(chalk12.red(`Session not found: ${id}`));
|
|
2695
3456
|
return true;
|
|
2696
3457
|
}
|
|
2697
3458
|
const data = session.conversation.serialize();
|
|
@@ -2701,7 +3462,7 @@ Config files: ${configManager.getConfigPaths().join(", ") || "(none)"}`));
|
|
|
2701
3462
|
if (msg.role === "user") ctx.conversation.addUserMessage(msg.content);
|
|
2702
3463
|
else if (msg.role === "assistant") ctx.conversation.addAssistantMessage(msg.content);
|
|
2703
3464
|
}
|
|
2704
|
-
console.log(
|
|
3465
|
+
console.log(chalk12.green(`\u2713 Resumed session: ${id} (${session.meta.messageCount} messages)`));
|
|
2705
3466
|
return true;
|
|
2706
3467
|
}
|
|
2707
3468
|
},
|
|
@@ -2711,7 +3472,7 @@ Config files: ${configManager.getConfigPaths().join(", ") || "(none)"}`));
|
|
|
2711
3472
|
handler: async (args, ctx) => {
|
|
2712
3473
|
const name = args || `fork-${Date.now()}`;
|
|
2713
3474
|
const id = sessionManager.save(ctx.conversation, name, statusLine.getInfo().model);
|
|
2714
|
-
console.log(
|
|
3475
|
+
console.log(chalk12.green(`\u2713 Conversation forked: ${id}`));
|
|
2715
3476
|
return true;
|
|
2716
3477
|
}
|
|
2717
3478
|
},
|
|
@@ -2723,7 +3484,7 @@ Config files: ${configManager.getConfigPaths().join(", ") || "(none)"}`));
|
|
|
2723
3484
|
const newMode = current === "plan" ? "execute" : "plan";
|
|
2724
3485
|
setMode(newMode);
|
|
2725
3486
|
statusLine.update({ mode: newMode });
|
|
2726
|
-
console.log(
|
|
3487
|
+
console.log(chalk12.cyan(`Mode: ${newMode === "plan" ? "PLAN (read-only)" : "EXECUTE"}`));
|
|
2727
3488
|
return true;
|
|
2728
3489
|
}
|
|
2729
3490
|
},
|
|
@@ -2733,16 +3494,16 @@ Config files: ${configManager.getConfigPaths().join(", ") || "(none)"}`));
|
|
|
2733
3494
|
handler: async () => {
|
|
2734
3495
|
const index = memoryManager.loadIndex();
|
|
2735
3496
|
const topics = memoryManager.listTopics();
|
|
2736
|
-
console.log(
|
|
2737
|
-
console.log(
|
|
3497
|
+
console.log(chalk12.bold("\nAuto Memory:\n"));
|
|
3498
|
+
console.log(chalk12.dim(`Directory: ${memoryManager.getMemoryDir()}`));
|
|
2738
3499
|
if (index) {
|
|
2739
|
-
console.log(
|
|
3500
|
+
console.log(chalk12.dim("\nMEMORY.md:"));
|
|
2740
3501
|
console.log(index);
|
|
2741
3502
|
} else {
|
|
2742
|
-
console.log(
|
|
3503
|
+
console.log(chalk12.dim("\nNo memory saved yet."));
|
|
2743
3504
|
}
|
|
2744
3505
|
if (topics.length > 0) {
|
|
2745
|
-
console.log(
|
|
3506
|
+
console.log(chalk12.dim(`
|
|
2746
3507
|
Topics: ${topics.join(", ")}`));
|
|
2747
3508
|
}
|
|
2748
3509
|
console.log("");
|
|
@@ -2753,12 +3514,12 @@ Topics: ${topics.join(", ")}`));
|
|
|
2753
3514
|
name: "/init",
|
|
2754
3515
|
description: "Initialize CODI.md in the current project",
|
|
2755
3516
|
handler: async () => {
|
|
2756
|
-
const codiPath =
|
|
2757
|
-
if (
|
|
2758
|
-
console.log(
|
|
3517
|
+
const codiPath = path13.join(process.cwd(), "CODI.md");
|
|
3518
|
+
if (fs12.existsSync(codiPath)) {
|
|
3519
|
+
console.log(chalk12.yellow("CODI.md already exists"));
|
|
2759
3520
|
return true;
|
|
2760
3521
|
}
|
|
2761
|
-
const content = `# Project: ${
|
|
3522
|
+
const content = `# Project: ${path13.basename(process.cwd())}
|
|
2762
3523
|
|
|
2763
3524
|
## Overview
|
|
2764
3525
|
<!-- Describe your project here -->
|
|
@@ -2772,8 +3533,8 @@ Topics: ${topics.join(", ")}`));
|
|
|
2772
3533
|
## Conventions
|
|
2773
3534
|
<!-- Code style, naming conventions, etc. -->
|
|
2774
3535
|
`;
|
|
2775
|
-
|
|
2776
|
-
console.log(
|
|
3536
|
+
fs12.writeFileSync(codiPath, content, "utf-8");
|
|
3537
|
+
console.log(chalk12.green("\u2713 Created CODI.md"));
|
|
2777
3538
|
return true;
|
|
2778
3539
|
}
|
|
2779
3540
|
},
|
|
@@ -2801,8 +3562,8 @@ ${content}
|
|
|
2801
3562
|
|
|
2802
3563
|
`;
|
|
2803
3564
|
}
|
|
2804
|
-
|
|
2805
|
-
console.log(
|
|
3565
|
+
fs12.writeFileSync(filePath, md, "utf-8");
|
|
3566
|
+
console.log(chalk12.green(`\u2713 Exported to ${filePath}`));
|
|
2806
3567
|
return true;
|
|
2807
3568
|
}
|
|
2808
3569
|
},
|
|
@@ -2813,12 +3574,12 @@ ${content}
|
|
|
2813
3574
|
const { taskManager: taskManager2 } = await Promise.resolve().then(() => (init_task_tools(), task_tools_exports));
|
|
2814
3575
|
const tasks = taskManager2.list();
|
|
2815
3576
|
if (tasks.length === 0) {
|
|
2816
|
-
console.log(
|
|
3577
|
+
console.log(chalk12.dim("\nNo tasks.\n"));
|
|
2817
3578
|
return true;
|
|
2818
3579
|
}
|
|
2819
|
-
console.log(
|
|
3580
|
+
console.log(chalk12.bold("\nTasks:\n"));
|
|
2820
3581
|
for (const task of tasks) {
|
|
2821
|
-
const statusIcon = task.status === "completed" ?
|
|
3582
|
+
const statusIcon = task.status === "completed" ? chalk12.green("\u2713") : task.status === "in_progress" ? chalk12.yellow("\u27F3") : chalk12.dim("\u25CB");
|
|
2822
3583
|
console.log(` ${statusIcon} #${task.id} ${task.subject} [${task.status}]`);
|
|
2823
3584
|
}
|
|
2824
3585
|
console.log("");
|
|
@@ -2833,7 +3594,7 @@ ${content}
|
|
|
2833
3594
|
const info = statusLine.getInfo();
|
|
2834
3595
|
const stats = tokenTracker.getStats();
|
|
2835
3596
|
const mcpServers = mcpManager.listServers();
|
|
2836
|
-
console.log(
|
|
3597
|
+
console.log(chalk12.bold("\nCodi Status:\n"));
|
|
2837
3598
|
console.log(` Version: 0.1.0`);
|
|
2838
3599
|
console.log(` Model: ${info.model}`);
|
|
2839
3600
|
console.log(` Provider: ${config.provider}`);
|
|
@@ -2855,7 +3616,7 @@ ${content}
|
|
|
2855
3616
|
const max = 2e5;
|
|
2856
3617
|
const pct = Math.round(estimated / max * 100);
|
|
2857
3618
|
const bar = "\u2588".repeat(Math.round(pct / 5)) + "\u2591".repeat(20 - Math.round(pct / 5));
|
|
2858
|
-
console.log(
|
|
3619
|
+
console.log(chalk12.bold("\nContext Window:\n"));
|
|
2859
3620
|
console.log(` ${bar} ${pct}%`);
|
|
2860
3621
|
console.log(` ~${estimated.toLocaleString()} / ${max.toLocaleString()} tokens`);
|
|
2861
3622
|
console.log(` Messages: ${ctx.conversation.getMessageCount()}`);
|
|
@@ -2869,12 +3630,12 @@ ${content}
|
|
|
2869
3630
|
handler: async (_args, ctx) => {
|
|
2870
3631
|
const checkpoints = checkpointManager.list();
|
|
2871
3632
|
if (checkpoints.length === 0) {
|
|
2872
|
-
console.log(
|
|
3633
|
+
console.log(chalk12.yellow("No checkpoints available."));
|
|
2873
3634
|
return true;
|
|
2874
3635
|
}
|
|
2875
3636
|
const result = checkpointManager.rewind();
|
|
2876
3637
|
if (!result) {
|
|
2877
|
-
console.log(
|
|
3638
|
+
console.log(chalk12.yellow("No checkpoint to rewind to."));
|
|
2878
3639
|
return true;
|
|
2879
3640
|
}
|
|
2880
3641
|
const data = result.conversation.serialize();
|
|
@@ -2884,7 +3645,7 @@ ${content}
|
|
|
2884
3645
|
if (msg.role === "user") ctx.conversation.addUserMessage(msg.content);
|
|
2885
3646
|
else if (msg.role === "assistant") ctx.conversation.addAssistantMessage(msg.content);
|
|
2886
3647
|
}
|
|
2887
|
-
console.log(
|
|
3648
|
+
console.log(chalk12.green(`\u2713 Rewound to checkpoint${result.description ? `: ${result.description}` : ""}`));
|
|
2888
3649
|
return true;
|
|
2889
3650
|
}
|
|
2890
3651
|
},
|
|
@@ -2896,13 +3657,13 @@ ${content}
|
|
|
2896
3657
|
const { execSync: execSync5 } = await import("child_process");
|
|
2897
3658
|
const diff = execSync5("git diff", { encoding: "utf-8", cwd: process.cwd() });
|
|
2898
3659
|
if (!diff.trim()) {
|
|
2899
|
-
console.log(
|
|
3660
|
+
console.log(chalk12.dim("\nNo changes.\n"));
|
|
2900
3661
|
} else {
|
|
2901
3662
|
const { renderDiff: renderDiff2 } = await Promise.resolve().then(() => (init_renderer(), renderer_exports));
|
|
2902
3663
|
console.log("\n" + renderDiff2("", "", diff) + "\n");
|
|
2903
3664
|
}
|
|
2904
3665
|
} catch {
|
|
2905
|
-
console.log(
|
|
3666
|
+
console.log(chalk12.yellow("Not a git repository or git not available."));
|
|
2906
3667
|
}
|
|
2907
3668
|
return true;
|
|
2908
3669
|
}
|
|
@@ -2915,21 +3676,64 @@ ${content}
|
|
|
2915
3676
|
try {
|
|
2916
3677
|
const staged = execSync5("git diff --cached", { encoding: "utf-8", cwd: process.cwd() });
|
|
2917
3678
|
const unstaged = execSync5("git diff", { encoding: "utf-8", cwd: process.cwd() });
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
3679
|
+
if (!staged.trim() && unstaged.trim()) {
|
|
3680
|
+
const untracked = execSync5("git ls-files --others --exclude-standard", {
|
|
3681
|
+
encoding: "utf-8",
|
|
3682
|
+
cwd: process.cwd()
|
|
3683
|
+
}).trim();
|
|
3684
|
+
if (untracked) {
|
|
3685
|
+
console.log(chalk12.yellow(`
|
|
3686
|
+
\uC8FC\uC758: \uCD94\uC801\uB418\uC9C0 \uC54A\uB294 \uD30C\uC77C\uC774 \uC788\uC2B5\uB2C8\uB2E4 (\uC790\uB3D9 \uC2A4\uD14C\uC774\uC9D5 \uC548 \uB428):`));
|
|
3687
|
+
for (const f of untracked.split("\n").slice(0, 10)) {
|
|
3688
|
+
console.log(chalk12.dim(` ${f}`));
|
|
3689
|
+
}
|
|
3690
|
+
if (untracked.split("\n").length > 10) {
|
|
3691
|
+
console.log(chalk12.dim(` ... \uC678 ${untracked.split("\n").length - 10}\uAC1C`));
|
|
3692
|
+
}
|
|
3693
|
+
console.log("");
|
|
3694
|
+
}
|
|
3695
|
+
console.log(chalk12.dim("\uC218\uC815\uB41C \uD30C\uC77C\uC744 \uC790\uB3D9 \uC2A4\uD14C\uC774\uC9D5\uD569\uB2C8\uB2E4..."));
|
|
3696
|
+
execSync5("git add -u", { encoding: "utf-8", cwd: process.cwd() });
|
|
3697
|
+
}
|
|
3698
|
+
const finalDiff = execSync5("git diff --cached", { encoding: "utf-8", cwd: process.cwd() });
|
|
3699
|
+
if (!finalDiff.trim()) {
|
|
3700
|
+
console.log(chalk12.dim("\nNo changes to commit.\n"));
|
|
2921
3701
|
return true;
|
|
2922
3702
|
}
|
|
3703
|
+
let conventionHint = "";
|
|
3704
|
+
try {
|
|
3705
|
+
const recentLog = execSync5("git log --oneline -10", {
|
|
3706
|
+
encoding: "utf-8",
|
|
3707
|
+
cwd: process.cwd()
|
|
3708
|
+
}).trim();
|
|
3709
|
+
if (recentLog) {
|
|
3710
|
+
const conventionalPattern = /^[a-f0-9]+\s+(feat|fix|chore|docs|style|refactor|perf|test|build|ci|revert)(\(.+\))?[!]?:/;
|
|
3711
|
+
const lines = recentLog.split("\n");
|
|
3712
|
+
const conventionalCount = lines.filter((l) => conventionalPattern.test(l)).length;
|
|
3713
|
+
if (conventionalCount >= 3) {
|
|
3714
|
+
conventionHint = `
|
|
3715
|
+
|
|
3716
|
+
\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.`;
|
|
3717
|
+
}
|
|
3718
|
+
conventionHint += `
|
|
3719
|
+
|
|
3720
|
+
\uCD5C\uADFC \uCEE4\uBC0B \uCC38\uACE0:
|
|
3721
|
+
\`\`\`
|
|
3722
|
+
${recentLog}
|
|
3723
|
+
\`\`\``;
|
|
3724
|
+
}
|
|
3725
|
+
} catch {
|
|
3726
|
+
}
|
|
2923
3727
|
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 \
|
|
3728
|
+
`\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
3729
|
|
|
2926
3730
|
\`\`\`diff
|
|
2927
|
-
${
|
|
3731
|
+
${finalDiff}
|
|
2928
3732
|
\`\`\``
|
|
2929
3733
|
);
|
|
2930
3734
|
return false;
|
|
2931
3735
|
} catch {
|
|
2932
|
-
console.log(
|
|
3736
|
+
console.log(chalk12.yellow("Not a git repository or git not available."));
|
|
2933
3737
|
return true;
|
|
2934
3738
|
}
|
|
2935
3739
|
}
|
|
@@ -2944,7 +3748,7 @@ ${diff}
|
|
|
2944
3748
|
const unstaged = execSync5("git diff", { encoding: "utf-8", cwd: process.cwd() });
|
|
2945
3749
|
const diff = staged + unstaged;
|
|
2946
3750
|
if (!diff.trim()) {
|
|
2947
|
-
console.log(
|
|
3751
|
+
console.log(chalk12.dim("\nNo changes to review.\n"));
|
|
2948
3752
|
return true;
|
|
2949
3753
|
}
|
|
2950
3754
|
ctx.conversation.addUserMessage(
|
|
@@ -2956,7 +3760,7 @@ ${diff}
|
|
|
2956
3760
|
);
|
|
2957
3761
|
return false;
|
|
2958
3762
|
} catch {
|
|
2959
|
-
console.log(
|
|
3763
|
+
console.log(chalk12.yellow("Not a git repository or git not available."));
|
|
2960
3764
|
return true;
|
|
2961
3765
|
}
|
|
2962
3766
|
}
|
|
@@ -2966,27 +3770,27 @@ ${diff}
|
|
|
2966
3770
|
description: "Search past conversation sessions",
|
|
2967
3771
|
handler: async (args) => {
|
|
2968
3772
|
if (!args) {
|
|
2969
|
-
console.log(
|
|
3773
|
+
console.log(chalk12.yellow("Usage: /search <keyword>"));
|
|
2970
3774
|
return true;
|
|
2971
3775
|
}
|
|
2972
|
-
const home = process.env["HOME"] || process.env["USERPROFILE"] ||
|
|
2973
|
-
const sessionsDir =
|
|
2974
|
-
if (!
|
|
2975
|
-
console.log(
|
|
3776
|
+
const home = process.env["HOME"] || process.env["USERPROFILE"] || os13.homedir();
|
|
3777
|
+
const sessionsDir = path13.join(home, ".codi", "sessions");
|
|
3778
|
+
if (!fs12.existsSync(sessionsDir)) {
|
|
3779
|
+
console.log(chalk12.dim("\nNo sessions found.\n"));
|
|
2976
3780
|
return true;
|
|
2977
3781
|
}
|
|
2978
|
-
const files =
|
|
3782
|
+
const files = fs12.readdirSync(sessionsDir).filter((f) => f.endsWith(".jsonl"));
|
|
2979
3783
|
const results = [];
|
|
2980
3784
|
const keyword = args.toLowerCase();
|
|
2981
3785
|
for (const file of files) {
|
|
2982
3786
|
if (results.length >= 10) break;
|
|
2983
|
-
const filePath =
|
|
2984
|
-
const lines =
|
|
3787
|
+
const filePath = path13.join(sessionsDir, file);
|
|
3788
|
+
const lines = fs12.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
2985
3789
|
for (const line of lines) {
|
|
2986
3790
|
if (results.length >= 10) break;
|
|
2987
3791
|
if (line.toLowerCase().includes(keyword)) {
|
|
2988
3792
|
const sessionId = file.replace(".jsonl", "");
|
|
2989
|
-
const stat =
|
|
3793
|
+
const stat = fs12.statSync(filePath);
|
|
2990
3794
|
const date = stat.mtime.toISOString().split("T")[0];
|
|
2991
3795
|
const preview = line.length > 100 ? line.slice(0, 100) + "..." : line;
|
|
2992
3796
|
results.push({ sessionId, date, preview });
|
|
@@ -2995,16 +3799,16 @@ ${diff}
|
|
|
2995
3799
|
}
|
|
2996
3800
|
}
|
|
2997
3801
|
if (results.length === 0) {
|
|
2998
|
-
console.log(
|
|
3802
|
+
console.log(chalk12.dim(`
|
|
2999
3803
|
No results for "${args}".
|
|
3000
3804
|
`));
|
|
3001
3805
|
} else {
|
|
3002
|
-
console.log(
|
|
3806
|
+
console.log(chalk12.bold(`
|
|
3003
3807
|
Search results for "${args}":
|
|
3004
3808
|
`));
|
|
3005
3809
|
for (const r of results) {
|
|
3006
|
-
console.log(` ${
|
|
3007
|
-
console.log(` ${
|
|
3810
|
+
console.log(` ${chalk12.cyan(r.sessionId)} ${chalk12.dim(r.date)}`);
|
|
3811
|
+
console.log(` ${chalk12.dim(r.preview)}`);
|
|
3008
3812
|
}
|
|
3009
3813
|
console.log("");
|
|
3010
3814
|
}
|
|
@@ -3016,24 +3820,24 @@ Search results for "${args}":
|
|
|
3016
3820
|
description: "Run a command and auto-fix errors (e.g., /fix npm run build)",
|
|
3017
3821
|
handler: async (args, ctx) => {
|
|
3018
3822
|
if (!args) {
|
|
3019
|
-
console.log(
|
|
3823
|
+
console.log(chalk12.yellow("Usage: /fix <command>"));
|
|
3020
3824
|
return true;
|
|
3021
3825
|
}
|
|
3022
3826
|
const { execSync: execSync5 } = await import("child_process");
|
|
3023
3827
|
try {
|
|
3024
|
-
const isWin =
|
|
3828
|
+
const isWin = os13.platform() === "win32";
|
|
3025
3829
|
const shell = isWin ? "powershell.exe" : void 0;
|
|
3026
3830
|
const fixCmd = isWin ? `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; ${args}` : args;
|
|
3027
3831
|
const output3 = execSync5(fixCmd, { encoding: "utf-8", cwd: process.cwd(), stdio: "pipe", shell });
|
|
3028
|
-
console.log(
|
|
3832
|
+
console.log(chalk12.green(`
|
|
3029
3833
|
\u2713 Command succeeded. No errors to fix.
|
|
3030
3834
|
`));
|
|
3031
|
-
if (output3.trim()) console.log(
|
|
3835
|
+
if (output3.trim()) console.log(chalk12.dim(output3));
|
|
3032
3836
|
return true;
|
|
3033
3837
|
} catch (err) {
|
|
3034
3838
|
const error = err;
|
|
3035
3839
|
const errorOutput = (error.stderr || "") + (error.stdout || "");
|
|
3036
|
-
console.log(
|
|
3840
|
+
console.log(chalk12.red(`
|
|
3037
3841
|
Command failed: ${args}
|
|
3038
3842
|
`));
|
|
3039
3843
|
ctx.conversation.addUserMessage(
|
|
@@ -3049,21 +3853,236 @@ ${errorOutput}
|
|
|
3049
3853
|
}
|
|
3050
3854
|
}
|
|
3051
3855
|
},
|
|
3856
|
+
{
|
|
3857
|
+
name: "/undo",
|
|
3858
|
+
description: "Undo the most recent file edit (rollback from backup)",
|
|
3859
|
+
handler: async (args) => {
|
|
3860
|
+
const { getBackupHistory: getBackupHistory2, undoLast: undoLast2 } = await Promise.resolve().then(() => (init_file_backup(), file_backup_exports));
|
|
3861
|
+
if (args === "list") {
|
|
3862
|
+
const history = getBackupHistory2();
|
|
3863
|
+
if (history.length === 0) {
|
|
3864
|
+
console.log(chalk12.dim("\n\uB418\uB3CC\uB9B4 \uD30C\uC77C \uBCC0\uACBD \uC774\uB825\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n"));
|
|
3865
|
+
return true;
|
|
3866
|
+
}
|
|
3867
|
+
console.log(chalk12.bold(`
|
|
3868
|
+
\uD30C\uC77C \uBCC0\uACBD \uC774\uB825 (\uCD5C\uADFC ${Math.min(history.length, 20)}\uAC1C):
|
|
3869
|
+
`));
|
|
3870
|
+
const recent = history.slice(-20).reverse();
|
|
3871
|
+
for (let i = 0; i < recent.length; i++) {
|
|
3872
|
+
const entry2 = recent[i];
|
|
3873
|
+
const time = new Date(entry2.timestamp).toLocaleTimeString();
|
|
3874
|
+
const tag = entry2.wasNew ? chalk12.yellow("[\uC0C8 \uD30C\uC77C]") : chalk12.cyan("[\uC218\uC815]");
|
|
3875
|
+
console.log(` ${i + 1}. ${tag} ${entry2.originalPath} ${chalk12.dim(time)}`);
|
|
3876
|
+
}
|
|
3877
|
+
console.log("");
|
|
3878
|
+
return true;
|
|
3879
|
+
}
|
|
3880
|
+
const entry = undoLast2();
|
|
3881
|
+
if (!entry) {
|
|
3882
|
+
console.log(chalk12.yellow("\n\uB418\uB3CC\uB9B4 \uBCC0\uACBD \uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n"));
|
|
3883
|
+
return true;
|
|
3884
|
+
}
|
|
3885
|
+
const action = entry.wasNew ? "\uC0AD\uC81C\uB428 (\uC0C8\uB85C \uC0DD\uC131\uB41C \uD30C\uC77C)" : "\uC774\uC804 \uC0C1\uD0DC\uB85C \uBCF5\uC6D0\uB428";
|
|
3886
|
+
console.log(chalk12.green(`
|
|
3887
|
+
\u2713 \uB418\uB3CC\uB9AC\uAE30 \uC644\uB8CC: ${entry.originalPath}`));
|
|
3888
|
+
console.log(chalk12.dim(` ${action}`));
|
|
3889
|
+
console.log("");
|
|
3890
|
+
return true;
|
|
3891
|
+
}
|
|
3892
|
+
},
|
|
3893
|
+
{
|
|
3894
|
+
name: "/branch",
|
|
3895
|
+
description: "Create and switch to a new branch, or show current branch",
|
|
3896
|
+
handler: async (args) => {
|
|
3897
|
+
const { execSync: execSync5 } = await import("child_process");
|
|
3898
|
+
try {
|
|
3899
|
+
if (!args) {
|
|
3900
|
+
const current = execSync5("git branch --show-current", {
|
|
3901
|
+
encoding: "utf-8",
|
|
3902
|
+
cwd: process.cwd()
|
|
3903
|
+
}).trim();
|
|
3904
|
+
const branches = execSync5("git branch -a", {
|
|
3905
|
+
encoding: "utf-8",
|
|
3906
|
+
cwd: process.cwd()
|
|
3907
|
+
}).trim();
|
|
3908
|
+
console.log(chalk12.bold(`
|
|
3909
|
+
Current branch: ${chalk12.green(current || "(detached HEAD)")}
|
|
3910
|
+
`));
|
|
3911
|
+
console.log(branches);
|
|
3912
|
+
console.log("");
|
|
3913
|
+
} else {
|
|
3914
|
+
const name = args.trim();
|
|
3915
|
+
execSync5(`git checkout -b ${name}`, {
|
|
3916
|
+
encoding: "utf-8",
|
|
3917
|
+
cwd: process.cwd()
|
|
3918
|
+
});
|
|
3919
|
+
console.log(chalk12.green(`
|
|
3920
|
+
\u2713 Created and switched to branch: ${name}
|
|
3921
|
+
`));
|
|
3922
|
+
}
|
|
3923
|
+
} catch (err) {
|
|
3924
|
+
const error = err;
|
|
3925
|
+
const msg = error.stderr || error.message || "Unknown error";
|
|
3926
|
+
console.log(chalk12.red(`
|
|
3927
|
+
Branch operation failed: ${msg.trim()}
|
|
3928
|
+
`));
|
|
3929
|
+
}
|
|
3930
|
+
return true;
|
|
3931
|
+
}
|
|
3932
|
+
},
|
|
3933
|
+
{
|
|
3934
|
+
name: "/stash",
|
|
3935
|
+
description: "Git stash management (pop, list, drop, or save)",
|
|
3936
|
+
handler: async (args) => {
|
|
3937
|
+
const { execSync: execSync5 } = await import("child_process");
|
|
3938
|
+
const sub = args.trim().split(/\s+/);
|
|
3939
|
+
const action = sub[0] || "push";
|
|
3940
|
+
const allowed = ["push", "pop", "list", "drop", "show", "apply", "clear"];
|
|
3941
|
+
if (!allowed.includes(action)) {
|
|
3942
|
+
console.log(chalk12.yellow(`Usage: /stash [${allowed.join("|")}]`));
|
|
3943
|
+
return true;
|
|
3944
|
+
}
|
|
3945
|
+
try {
|
|
3946
|
+
if (action === "clear") {
|
|
3947
|
+
console.log(chalk12.yellow("\u26A0 This will drop all stashes permanently."));
|
|
3948
|
+
}
|
|
3949
|
+
const cmd = `git stash ${args.trim() || "push"}`;
|
|
3950
|
+
const output3 = execSync5(cmd, {
|
|
3951
|
+
encoding: "utf-8",
|
|
3952
|
+
cwd: process.cwd()
|
|
3953
|
+
});
|
|
3954
|
+
console.log(output3.trim() ? `
|
|
3955
|
+
${output3.trim()}
|
|
3956
|
+
` : chalk12.dim("\n(no output)\n"));
|
|
3957
|
+
} catch (err) {
|
|
3958
|
+
const error = err;
|
|
3959
|
+
const msg = error.stderr || error.stdout || error.message || "Unknown error";
|
|
3960
|
+
console.log(chalk12.red(`
|
|
3961
|
+
Stash operation failed: ${msg.trim()}
|
|
3962
|
+
`));
|
|
3963
|
+
}
|
|
3964
|
+
return true;
|
|
3965
|
+
}
|
|
3966
|
+
},
|
|
3967
|
+
{
|
|
3968
|
+
name: "/pr",
|
|
3969
|
+
description: "Generate a pull request description from current branch diff",
|
|
3970
|
+
handler: async (_args, ctx) => {
|
|
3971
|
+
const { execSync: execSync5 } = await import("child_process");
|
|
3972
|
+
try {
|
|
3973
|
+
const currentBranch = execSync5("git branch --show-current", {
|
|
3974
|
+
encoding: "utf-8",
|
|
3975
|
+
cwd: process.cwd()
|
|
3976
|
+
}).trim();
|
|
3977
|
+
if (!currentBranch) {
|
|
3978
|
+
console.log(chalk12.yellow("Not on a branch (detached HEAD)."));
|
|
3979
|
+
return true;
|
|
3980
|
+
}
|
|
3981
|
+
let baseBranch = "main";
|
|
3982
|
+
try {
|
|
3983
|
+
execSync5("git rev-parse --verify main", {
|
|
3984
|
+
encoding: "utf-8",
|
|
3985
|
+
cwd: process.cwd(),
|
|
3986
|
+
stdio: "pipe"
|
|
3987
|
+
});
|
|
3988
|
+
} catch {
|
|
3989
|
+
try {
|
|
3990
|
+
execSync5("git rev-parse --verify master", {
|
|
3991
|
+
encoding: "utf-8",
|
|
3992
|
+
cwd: process.cwd(),
|
|
3993
|
+
stdio: "pipe"
|
|
3994
|
+
});
|
|
3995
|
+
baseBranch = "master";
|
|
3996
|
+
} catch {
|
|
3997
|
+
console.log(chalk12.yellow("Cannot find base branch (main or master)."));
|
|
3998
|
+
return true;
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
4001
|
+
if (currentBranch === baseBranch) {
|
|
4002
|
+
console.log(chalk12.yellow(`Already on ${baseBranch}. Switch to a feature branch first.`));
|
|
4003
|
+
return true;
|
|
4004
|
+
}
|
|
4005
|
+
let commitLog = "";
|
|
4006
|
+
try {
|
|
4007
|
+
commitLog = execSync5(`git log ${baseBranch}..HEAD --oneline`, {
|
|
4008
|
+
encoding: "utf-8",
|
|
4009
|
+
cwd: process.cwd()
|
|
4010
|
+
}).trim();
|
|
4011
|
+
} catch {
|
|
4012
|
+
}
|
|
4013
|
+
if (!commitLog) {
|
|
4014
|
+
console.log(chalk12.yellow(`No commits ahead of ${baseBranch}.`));
|
|
4015
|
+
return true;
|
|
4016
|
+
}
|
|
4017
|
+
let diffStat = "";
|
|
4018
|
+
try {
|
|
4019
|
+
diffStat = execSync5(`git diff ${baseBranch}...HEAD --stat`, {
|
|
4020
|
+
encoding: "utf-8",
|
|
4021
|
+
cwd: process.cwd()
|
|
4022
|
+
}).trim();
|
|
4023
|
+
} catch {
|
|
4024
|
+
}
|
|
4025
|
+
let diff = "";
|
|
4026
|
+
try {
|
|
4027
|
+
diff = execSync5(`git diff ${baseBranch}...HEAD`, {
|
|
4028
|
+
encoding: "utf-8",
|
|
4029
|
+
cwd: process.cwd(),
|
|
4030
|
+
maxBuffer: 10 * 1024 * 1024
|
|
4031
|
+
});
|
|
4032
|
+
if (diff.length > 5e4) {
|
|
4033
|
+
diff = diff.slice(0, 5e4) + "\n\n... (diff truncated, too large)";
|
|
4034
|
+
}
|
|
4035
|
+
} catch {
|
|
4036
|
+
}
|
|
4037
|
+
console.log(chalk12.dim(`
|
|
4038
|
+
Analyzing ${commitLog.split("\n").length} commit(s) from ${currentBranch}...
|
|
4039
|
+
`));
|
|
4040
|
+
ctx.conversation.addUserMessage(
|
|
4041
|
+
`\uD604\uC7AC \uBE0C\uB79C\uCE58 \`${currentBranch}\`\uC5D0\uC11C \`${baseBranch}\`\uB85C \uBCF4\uB0BC Pull Request \uC124\uBA85\uC744 \uC0DD\uC131\uD574\uC918.
|
|
4042
|
+
|
|
4043
|
+
\uB2E4\uC74C \uD615\uC2DD\uC758 \uB9C8\uD06C\uB2E4\uC6B4\uC73C\uB85C \uCD9C\uB825\uD574\uC918:
|
|
4044
|
+
- **Title**: PR \uC81C\uBAA9 (70\uC790 \uC774\uB0B4, \uC601\uBB38)
|
|
4045
|
+
- **## Summary**: \uBCC0\uACBD \uC0AC\uD56D \uC694\uC57D (1-3 bullet points)
|
|
4046
|
+
- **## Changes**: \uC8FC\uC694 \uBCC0\uACBD \uD30C\uC77C \uBC0F \uB0B4\uC6A9
|
|
4047
|
+
- **## Test Plan**: \uD14C\uC2A4\uD2B8 \uACC4\uD68D \uCCB4\uD06C\uB9AC\uC2A4\uD2B8
|
|
4048
|
+
|
|
4049
|
+
### Commits:
|
|
4050
|
+
\`\`\`
|
|
4051
|
+
${commitLog}
|
|
4052
|
+
\`\`\`
|
|
4053
|
+
|
|
4054
|
+
### Diff stat:
|
|
4055
|
+
\`\`\`
|
|
4056
|
+
${diffStat}
|
|
4057
|
+
\`\`\`
|
|
4058
|
+
|
|
4059
|
+
### Full diff:
|
|
4060
|
+
\`\`\`diff
|
|
4061
|
+
${diff}
|
|
4062
|
+
\`\`\``
|
|
4063
|
+
);
|
|
4064
|
+
return false;
|
|
4065
|
+
} catch {
|
|
4066
|
+
console.log(chalk12.yellow("Not a git repository or git not available."));
|
|
4067
|
+
return true;
|
|
4068
|
+
}
|
|
4069
|
+
}
|
|
4070
|
+
},
|
|
3052
4071
|
{
|
|
3053
4072
|
name: "/mcp",
|
|
3054
4073
|
description: "Show MCP server status",
|
|
3055
4074
|
handler: async () => {
|
|
3056
4075
|
const servers = mcpManager.listServers();
|
|
3057
4076
|
if (servers.length === 0) {
|
|
3058
|
-
console.log(
|
|
3059
|
-
console.log(
|
|
4077
|
+
console.log(chalk12.dim("\nNo MCP servers connected.\n"));
|
|
4078
|
+
console.log(chalk12.dim("Add servers in .codi/mcp.json or ~/.codi/mcp.json"));
|
|
3060
4079
|
return true;
|
|
3061
4080
|
}
|
|
3062
|
-
console.log(
|
|
4081
|
+
console.log(chalk12.bold("\nMCP Servers:\n"));
|
|
3063
4082
|
for (const s of servers) {
|
|
3064
|
-
console.log(` ${
|
|
4083
|
+
console.log(` ${chalk12.green("\u25CF")} ${s.name}`);
|
|
3065
4084
|
for (const t of s.tools) {
|
|
3066
|
-
console.log(
|
|
4085
|
+
console.log(chalk12.dim(` - ${t}`));
|
|
3067
4086
|
}
|
|
3068
4087
|
}
|
|
3069
4088
|
console.log("");
|
|
@@ -3072,24 +4091,29 @@ ${errorOutput}
|
|
|
3072
4091
|
}
|
|
3073
4092
|
];
|
|
3074
4093
|
}
|
|
4094
|
+
function formatTokens(n) {
|
|
4095
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
4096
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
|
|
4097
|
+
return String(n);
|
|
4098
|
+
}
|
|
3075
4099
|
function loadCustomCommands() {
|
|
3076
4100
|
const commands = [];
|
|
3077
|
-
const home = process.env["HOME"] || process.env["USERPROFILE"] ||
|
|
4101
|
+
const home = process.env["HOME"] || process.env["USERPROFILE"] || os13.homedir();
|
|
3078
4102
|
const dirs = [
|
|
3079
|
-
|
|
3080
|
-
|
|
4103
|
+
path13.join(home, ".codi", "commands"),
|
|
4104
|
+
path13.join(process.cwd(), ".codi", "commands")
|
|
3081
4105
|
];
|
|
3082
4106
|
for (const dir of dirs) {
|
|
3083
|
-
if (!
|
|
3084
|
-
const files =
|
|
4107
|
+
if (!fs12.existsSync(dir)) continue;
|
|
4108
|
+
const files = fs12.readdirSync(dir).filter((f) => f.endsWith(".md"));
|
|
3085
4109
|
for (const file of files) {
|
|
3086
4110
|
const name = "/" + file.replace(".md", "");
|
|
3087
|
-
const filePath =
|
|
4111
|
+
const filePath = path13.join(dir, file);
|
|
3088
4112
|
commands.push({
|
|
3089
4113
|
name,
|
|
3090
|
-
description: `Custom command from ${
|
|
4114
|
+
description: `Custom command from ${path13.relative(process.cwd(), filePath)}`,
|
|
3091
4115
|
handler: async (_args, ctx) => {
|
|
3092
|
-
let content =
|
|
4116
|
+
let content = fs12.readFileSync(filePath, "utf-8");
|
|
3093
4117
|
content = content.replace(/\{\{cwd\}\}/g, process.cwd()).replace(/\{\{date\}\}/g, (/* @__PURE__ */ new Date()).toISOString().split("T")[0]).replace(/\{\{file_path\}\}/g, _args || "");
|
|
3094
4118
|
await ctx.conversation.addUserMessage(content);
|
|
3095
4119
|
return false;
|
|
@@ -3103,8 +4127,8 @@ function loadCustomCommands() {
|
|
|
3103
4127
|
// src/tools/file-read.ts
|
|
3104
4128
|
init_esm_shims();
|
|
3105
4129
|
init_tool();
|
|
3106
|
-
import * as
|
|
3107
|
-
import * as
|
|
4130
|
+
import * as fs13 from "fs";
|
|
4131
|
+
import * as path14 from "path";
|
|
3108
4132
|
var fileReadTool = {
|
|
3109
4133
|
name: "read_file",
|
|
3110
4134
|
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 +4148,17 @@ var fileReadTool = {
|
|
|
3124
4148
|
const filePath = String(input3["file_path"]);
|
|
3125
4149
|
const offset = input3["offset"];
|
|
3126
4150
|
const limit = input3["limit"];
|
|
3127
|
-
const resolved =
|
|
3128
|
-
if (!
|
|
4151
|
+
const resolved = path14.resolve(filePath);
|
|
4152
|
+
if (!fs13.existsSync(resolved)) {
|
|
3129
4153
|
return makeToolError(`File not found: ${resolved}`);
|
|
3130
4154
|
}
|
|
3131
|
-
const stat =
|
|
4155
|
+
const stat = fs13.statSync(resolved);
|
|
3132
4156
|
if (stat.isDirectory()) {
|
|
3133
4157
|
return makeToolError(`Path is a directory, not a file: ${resolved}. Use list_dir instead.`);
|
|
3134
4158
|
}
|
|
3135
|
-
const ext =
|
|
4159
|
+
const ext = path14.extname(resolved).toLowerCase();
|
|
3136
4160
|
if ([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".svg"].includes(ext)) {
|
|
3137
|
-
const data =
|
|
4161
|
+
const data = fs13.readFileSync(resolved);
|
|
3138
4162
|
const base64 = data.toString("base64");
|
|
3139
4163
|
const mimeMap = {
|
|
3140
4164
|
".png": "image/png",
|
|
@@ -3145,7 +4169,7 @@ var fileReadTool = {
|
|
|
3145
4169
|
".bmp": "image/bmp",
|
|
3146
4170
|
".svg": "image/svg+xml"
|
|
3147
4171
|
};
|
|
3148
|
-
return makeToolResult(`[Image: ${
|
|
4172
|
+
return makeToolResult(`[Image: ${path14.basename(resolved)}]`, {
|
|
3149
4173
|
filePath: resolved,
|
|
3150
4174
|
isImage: true,
|
|
3151
4175
|
imageData: base64,
|
|
@@ -3156,7 +4180,7 @@ var fileReadTool = {
|
|
|
3156
4180
|
try {
|
|
3157
4181
|
const pdfModule = await import("pdf-parse");
|
|
3158
4182
|
const pdfParse = pdfModule.default || pdfModule;
|
|
3159
|
-
const buffer =
|
|
4183
|
+
const buffer = fs13.readFileSync(resolved);
|
|
3160
4184
|
const data = await pdfParse(buffer);
|
|
3161
4185
|
const pages = input3["pages"];
|
|
3162
4186
|
let text = data.text;
|
|
@@ -3174,7 +4198,7 @@ var fileReadTool = {
|
|
|
3174
4198
|
}
|
|
3175
4199
|
if (ext === ".ipynb") {
|
|
3176
4200
|
try {
|
|
3177
|
-
const content =
|
|
4201
|
+
const content = fs13.readFileSync(resolved, "utf-8");
|
|
3178
4202
|
const nb = JSON.parse(content);
|
|
3179
4203
|
const output3 = [];
|
|
3180
4204
|
for (let i = 0; i < (nb.cells || []).length; i++) {
|
|
@@ -3202,7 +4226,7 @@ var fileReadTool = {
|
|
|
3202
4226
|
}
|
|
3203
4227
|
}
|
|
3204
4228
|
try {
|
|
3205
|
-
const raw =
|
|
4229
|
+
const raw = fs13.readFileSync(resolved, "utf-8");
|
|
3206
4230
|
const content = raw.replace(/\r\n/g, "\n");
|
|
3207
4231
|
const lines = content.split("\n");
|
|
3208
4232
|
const totalLines = lines.length;
|
|
@@ -3230,8 +4254,9 @@ var fileReadTool = {
|
|
|
3230
4254
|
// src/tools/file-write.ts
|
|
3231
4255
|
init_esm_shims();
|
|
3232
4256
|
init_tool();
|
|
3233
|
-
|
|
3234
|
-
import * as
|
|
4257
|
+
init_file_backup();
|
|
4258
|
+
import * as fs14 from "fs";
|
|
4259
|
+
import * as path15 from "path";
|
|
3235
4260
|
var fileWriteTool = {
|
|
3236
4261
|
name: "write_file",
|
|
3237
4262
|
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 +4273,15 @@ var fileWriteTool = {
|
|
|
3248
4273
|
async execute(input3) {
|
|
3249
4274
|
const filePath = String(input3["file_path"]);
|
|
3250
4275
|
const content = String(input3["content"]);
|
|
3251
|
-
const resolved =
|
|
4276
|
+
const resolved = path15.resolve(filePath);
|
|
3252
4277
|
try {
|
|
3253
|
-
const dir =
|
|
3254
|
-
if (!
|
|
3255
|
-
|
|
4278
|
+
const dir = path15.dirname(resolved);
|
|
4279
|
+
if (!fs14.existsSync(dir)) {
|
|
4280
|
+
fs14.mkdirSync(dir, { recursive: true });
|
|
3256
4281
|
}
|
|
3257
|
-
const existed =
|
|
3258
|
-
|
|
4282
|
+
const existed = fs14.existsSync(resolved);
|
|
4283
|
+
backupFile(resolved);
|
|
4284
|
+
fs14.writeFileSync(resolved, content, "utf-8");
|
|
3259
4285
|
const lines = content.split("\n").length;
|
|
3260
4286
|
const action = existed ? "Overwrote" : "Created";
|
|
3261
4287
|
return makeToolResult(`${action} ${resolved} (${lines} lines)`, {
|
|
@@ -3271,8 +4297,9 @@ var fileWriteTool = {
|
|
|
3271
4297
|
// src/tools/file-edit.ts
|
|
3272
4298
|
init_esm_shims();
|
|
3273
4299
|
init_tool();
|
|
3274
|
-
|
|
3275
|
-
import * as
|
|
4300
|
+
init_file_backup();
|
|
4301
|
+
import * as fs15 from "fs";
|
|
4302
|
+
import * as path16 from "path";
|
|
3276
4303
|
var fileEditTool = {
|
|
3277
4304
|
name: "edit_file",
|
|
3278
4305
|
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 +4320,12 @@ var fileEditTool = {
|
|
|
3293
4320
|
const oldString = String(input3["old_string"]);
|
|
3294
4321
|
const newString = String(input3["new_string"]);
|
|
3295
4322
|
const replaceAll = input3["replace_all"] === true;
|
|
3296
|
-
const resolved =
|
|
3297
|
-
if (!
|
|
4323
|
+
const resolved = path16.resolve(filePath);
|
|
4324
|
+
if (!fs15.existsSync(resolved)) {
|
|
3298
4325
|
return makeToolError(`File not found: ${resolved}`);
|
|
3299
4326
|
}
|
|
3300
4327
|
try {
|
|
3301
|
-
const raw =
|
|
4328
|
+
const raw = fs15.readFileSync(resolved, "utf-8");
|
|
3302
4329
|
const hasCrlf = raw.includes("\r\n");
|
|
3303
4330
|
let content = hasCrlf ? raw.replace(/\r\n/g, "\n") : raw;
|
|
3304
4331
|
if (oldString === newString) {
|
|
@@ -3332,8 +4359,9 @@ Did you mean this line?
|
|
|
3332
4359
|
const idx = content.indexOf(oldString);
|
|
3333
4360
|
content = content.slice(0, idx) + newString + content.slice(idx + oldString.length);
|
|
3334
4361
|
}
|
|
4362
|
+
backupFile(resolved);
|
|
3335
4363
|
const output3 = hasCrlf ? content.replace(/\n/g, "\r\n") : content;
|
|
3336
|
-
|
|
4364
|
+
fs15.writeFileSync(resolved, output3, "utf-8");
|
|
3337
4365
|
const linesChanged = Math.max(
|
|
3338
4366
|
oldString.split("\n").length,
|
|
3339
4367
|
newString.split("\n").length
|
|
@@ -3351,8 +4379,9 @@ Did you mean this line?
|
|
|
3351
4379
|
// src/tools/file-multi-edit.ts
|
|
3352
4380
|
init_esm_shims();
|
|
3353
4381
|
init_tool();
|
|
3354
|
-
|
|
3355
|
-
import * as
|
|
4382
|
+
init_file_backup();
|
|
4383
|
+
import * as fs16 from "fs";
|
|
4384
|
+
import * as path17 from "path";
|
|
3356
4385
|
var fileMultiEditTool = {
|
|
3357
4386
|
name: "multi_edit",
|
|
3358
4387
|
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 +4409,15 @@ var fileMultiEditTool = {
|
|
|
3380
4409
|
async execute(input3) {
|
|
3381
4410
|
const filePath = String(input3["file_path"]);
|
|
3382
4411
|
const edits = input3["edits"];
|
|
3383
|
-
const resolved =
|
|
3384
|
-
if (!
|
|
4412
|
+
const resolved = path17.resolve(filePath);
|
|
4413
|
+
if (!fs16.existsSync(resolved)) {
|
|
3385
4414
|
return makeToolError(`File not found: ${resolved}`);
|
|
3386
4415
|
}
|
|
3387
4416
|
if (!Array.isArray(edits) || edits.length === 0) {
|
|
3388
4417
|
return makeToolError("edits must be a non-empty array of {old_string, new_string} objects");
|
|
3389
4418
|
}
|
|
3390
4419
|
try {
|
|
3391
|
-
const raw =
|
|
4420
|
+
const raw = fs16.readFileSync(resolved, "utf-8");
|
|
3392
4421
|
const hasCrlf = raw.includes("\r\n");
|
|
3393
4422
|
let content = hasCrlf ? raw.replace(/\r\n/g, "\n") : raw;
|
|
3394
4423
|
for (let i = 0; i < edits.length; i++) {
|
|
@@ -3412,8 +4441,9 @@ Searching for: ${edit2.old_string.slice(0, 100)}...`
|
|
|
3412
4441
|
edit2.new_string.split("\n").length
|
|
3413
4442
|
);
|
|
3414
4443
|
}
|
|
4444
|
+
backupFile(resolved);
|
|
3415
4445
|
const output3 = hasCrlf ? content.replace(/\n/g, "\r\n") : content;
|
|
3416
|
-
|
|
4446
|
+
fs16.writeFileSync(resolved, output3, "utf-8");
|
|
3417
4447
|
return makeToolResult(`Applied ${edits.length} edits to ${resolved}`, {
|
|
3418
4448
|
filePath: resolved,
|
|
3419
4449
|
linesChanged: totalLinesChanged
|
|
@@ -3427,8 +4457,8 @@ Searching for: ${edit2.old_string.slice(0, 100)}...`
|
|
|
3427
4457
|
// src/tools/glob.ts
|
|
3428
4458
|
init_esm_shims();
|
|
3429
4459
|
init_tool();
|
|
3430
|
-
import * as
|
|
3431
|
-
import * as
|
|
4460
|
+
import * as fs17 from "fs";
|
|
4461
|
+
import * as path18 from "path";
|
|
3432
4462
|
import { globby } from "globby";
|
|
3433
4463
|
var globTool = {
|
|
3434
4464
|
name: "glob",
|
|
@@ -3446,7 +4476,7 @@ var globTool = {
|
|
|
3446
4476
|
async execute(input3) {
|
|
3447
4477
|
const pattern = String(input3["pattern"]);
|
|
3448
4478
|
const searchPath = input3["path"] ? String(input3["path"]) : process.cwd();
|
|
3449
|
-
const resolved =
|
|
4479
|
+
const resolved = path18.resolve(searchPath);
|
|
3450
4480
|
try {
|
|
3451
4481
|
const files = await globby(pattern, {
|
|
3452
4482
|
cwd: resolved,
|
|
@@ -3457,7 +4487,7 @@ var globTool = {
|
|
|
3457
4487
|
});
|
|
3458
4488
|
const withStats = files.map((f) => {
|
|
3459
4489
|
try {
|
|
3460
|
-
const stat =
|
|
4490
|
+
const stat = fs17.statSync(f);
|
|
3461
4491
|
return { path: f, mtime: stat.mtimeMs };
|
|
3462
4492
|
} catch {
|
|
3463
4493
|
return { path: f, mtime: 0 };
|
|
@@ -3482,8 +4512,8 @@ ${result.join("\n")}`
|
|
|
3482
4512
|
init_esm_shims();
|
|
3483
4513
|
init_tool();
|
|
3484
4514
|
import { execFile } from "child_process";
|
|
3485
|
-
import * as
|
|
3486
|
-
import * as
|
|
4515
|
+
import * as fs18 from "fs";
|
|
4516
|
+
import * as path19 from "path";
|
|
3487
4517
|
var grepTool = {
|
|
3488
4518
|
name: "grep",
|
|
3489
4519
|
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 +4543,7 @@ var grepTool = {
|
|
|
3513
4543
|
readOnly: true,
|
|
3514
4544
|
async execute(input3) {
|
|
3515
4545
|
const pattern = String(input3["pattern"]);
|
|
3516
|
-
const searchPath =
|
|
4546
|
+
const searchPath = path19.resolve(input3["path"] ? String(input3["path"]) : process.cwd());
|
|
3517
4547
|
const outputMode = input3["output_mode"] || "files_with_matches";
|
|
3518
4548
|
const headLimit = input3["head_limit"] || 0;
|
|
3519
4549
|
const hasRg = await hasCommand("rg");
|
|
@@ -3578,19 +4608,19 @@ var grepTool = {
|
|
|
3578
4608
|
};
|
|
3579
4609
|
function hasCommand(cmd) {
|
|
3580
4610
|
const checkCmd = process.platform === "win32" ? "where" : "which";
|
|
3581
|
-
return new Promise((
|
|
3582
|
-
execFile(checkCmd, [cmd], (err) =>
|
|
4611
|
+
return new Promise((resolve11) => {
|
|
4612
|
+
execFile(checkCmd, [cmd], (err) => resolve11(!err));
|
|
3583
4613
|
});
|
|
3584
4614
|
}
|
|
3585
4615
|
function runCommand(cmd, args) {
|
|
3586
|
-
return new Promise((
|
|
4616
|
+
return new Promise((resolve11, reject) => {
|
|
3587
4617
|
execFile(cmd, args, { maxBuffer: 10 * 1024 * 1024, timeout: 3e4 }, (err, stdout, stderr) => {
|
|
3588
4618
|
if (err) {
|
|
3589
4619
|
err.code = err.code;
|
|
3590
4620
|
reject(err);
|
|
3591
4621
|
return;
|
|
3592
4622
|
}
|
|
3593
|
-
|
|
4623
|
+
resolve11(stdout);
|
|
3594
4624
|
});
|
|
3595
4625
|
});
|
|
3596
4626
|
}
|
|
@@ -3633,7 +4663,7 @@ async function builtinSearch(pattern, searchPath, input3, outputMode, headLimit)
|
|
|
3633
4663
|
if (headLimit > 0 && entryCount >= headLimit) break;
|
|
3634
4664
|
let content;
|
|
3635
4665
|
try {
|
|
3636
|
-
content =
|
|
4666
|
+
content = fs18.readFileSync(filePath, "utf-8");
|
|
3637
4667
|
} catch {
|
|
3638
4668
|
continue;
|
|
3639
4669
|
}
|
|
@@ -3697,18 +4727,18 @@ function collectFiles(dirPath, typeFilter, globFilter) {
|
|
|
3697
4727
|
function walk(dir) {
|
|
3698
4728
|
let entries;
|
|
3699
4729
|
try {
|
|
3700
|
-
entries =
|
|
4730
|
+
entries = fs18.readdirSync(dir, { withFileTypes: true });
|
|
3701
4731
|
} catch {
|
|
3702
4732
|
return;
|
|
3703
4733
|
}
|
|
3704
4734
|
for (const entry of entries) {
|
|
3705
4735
|
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
3706
4736
|
if (entry.name.startsWith(".") && entry.name !== ".") continue;
|
|
3707
|
-
const fullPath =
|
|
4737
|
+
const fullPath = path19.join(dir, entry.name);
|
|
3708
4738
|
if (entry.isDirectory()) {
|
|
3709
4739
|
walk(fullPath);
|
|
3710
4740
|
} else if (entry.isFile()) {
|
|
3711
|
-
const ext =
|
|
4741
|
+
const ext = path19.extname(entry.name).toLowerCase();
|
|
3712
4742
|
if (BINARY_EXTENSIONS.has(ext)) continue;
|
|
3713
4743
|
if (allowedExtensions && !allowedExtensions.has(ext)) continue;
|
|
3714
4744
|
if (globRegex && !globRegex.test(entry.name)) continue;
|
|
@@ -3717,7 +4747,7 @@ function collectFiles(dirPath, typeFilter, globFilter) {
|
|
|
3717
4747
|
}
|
|
3718
4748
|
}
|
|
3719
4749
|
try {
|
|
3720
|
-
const stat =
|
|
4750
|
+
const stat = fs18.statSync(dirPath);
|
|
3721
4751
|
if (stat.isFile()) {
|
|
3722
4752
|
return [dirPath];
|
|
3723
4753
|
}
|
|
@@ -3728,116 +4758,11 @@ function collectFiles(dirPath, typeFilter, globFilter) {
|
|
|
3728
4758
|
return files;
|
|
3729
4759
|
}
|
|
3730
4760
|
|
|
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
4761
|
// src/tools/list-dir.ts
|
|
3837
4762
|
init_esm_shims();
|
|
3838
4763
|
init_tool();
|
|
3839
|
-
import * as
|
|
3840
|
-
import * as
|
|
4764
|
+
import * as fs19 from "fs";
|
|
4765
|
+
import * as path20 from "path";
|
|
3841
4766
|
var listDirTool = {
|
|
3842
4767
|
name: "list_dir",
|
|
3843
4768
|
description: `List directory contents with file/folder distinction and basic metadata.`,
|
|
@@ -3851,16 +4776,16 @@ var listDirTool = {
|
|
|
3851
4776
|
dangerous: false,
|
|
3852
4777
|
readOnly: true,
|
|
3853
4778
|
async execute(input3) {
|
|
3854
|
-
const dirPath =
|
|
3855
|
-
if (!
|
|
4779
|
+
const dirPath = path20.resolve(input3["path"] ? String(input3["path"]) : process.cwd());
|
|
4780
|
+
if (!fs19.existsSync(dirPath)) {
|
|
3856
4781
|
return makeToolError(`Directory not found: ${dirPath}`);
|
|
3857
4782
|
}
|
|
3858
|
-
const stat =
|
|
4783
|
+
const stat = fs19.statSync(dirPath);
|
|
3859
4784
|
if (!stat.isDirectory()) {
|
|
3860
4785
|
return makeToolError(`Not a directory: ${dirPath}`);
|
|
3861
4786
|
}
|
|
3862
4787
|
try {
|
|
3863
|
-
const entries =
|
|
4788
|
+
const entries = fs19.readdirSync(dirPath, { withFileTypes: true });
|
|
3864
4789
|
const IGNORE = /* @__PURE__ */ new Set([".git", "node_modules", ".DS_Store", "__pycache__", ".next", "dist", "build"]);
|
|
3865
4790
|
const lines = [];
|
|
3866
4791
|
const dirs = [];
|
|
@@ -3871,7 +4796,7 @@ var listDirTool = {
|
|
|
3871
4796
|
dirs.push(`${entry.name}/`);
|
|
3872
4797
|
} else if (entry.isSymbolicLink()) {
|
|
3873
4798
|
try {
|
|
3874
|
-
const target =
|
|
4799
|
+
const target = fs19.readlinkSync(path20.join(dirPath, entry.name));
|
|
3875
4800
|
files.push(`${entry.name} -> ${target}`);
|
|
3876
4801
|
} catch {
|
|
3877
4802
|
files.push(`${entry.name} -> (broken link)`);
|
|
@@ -3898,9 +4823,95 @@ ${lines.join("\n")}`);
|
|
|
3898
4823
|
init_esm_shims();
|
|
3899
4824
|
init_tool();
|
|
3900
4825
|
import { execSync as execSync4 } from "child_process";
|
|
4826
|
+
function detectConflictFiles(cwd) {
|
|
4827
|
+
try {
|
|
4828
|
+
const output3 = execSync4("git diff --name-only --diff-filter=U", {
|
|
4829
|
+
encoding: "utf-8",
|
|
4830
|
+
cwd,
|
|
4831
|
+
timeout: 1e4
|
|
4832
|
+
});
|
|
4833
|
+
return output3.trim().split("\n").filter(Boolean);
|
|
4834
|
+
} catch {
|
|
4835
|
+
return [];
|
|
4836
|
+
}
|
|
4837
|
+
}
|
|
4838
|
+
function parseConflictMarkers(fileContent, filePath) {
|
|
4839
|
+
const sections = [];
|
|
4840
|
+
const lines = fileContent.split("\n");
|
|
4841
|
+
let inConflict = false;
|
|
4842
|
+
let startLine = 0;
|
|
4843
|
+
let oursLines = [];
|
|
4844
|
+
let theirsLines = [];
|
|
4845
|
+
let inTheirs = false;
|
|
4846
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4847
|
+
const line = lines[i];
|
|
4848
|
+
if (line.startsWith("<<<<<<<")) {
|
|
4849
|
+
inConflict = true;
|
|
4850
|
+
inTheirs = false;
|
|
4851
|
+
startLine = i + 1;
|
|
4852
|
+
oursLines = [];
|
|
4853
|
+
theirsLines = [];
|
|
4854
|
+
} else if (line.startsWith("=======") && inConflict) {
|
|
4855
|
+
inTheirs = true;
|
|
4856
|
+
} else if (line.startsWith(">>>>>>>") && inConflict) {
|
|
4857
|
+
sections.push({
|
|
4858
|
+
file: filePath,
|
|
4859
|
+
startLine,
|
|
4860
|
+
ours: oursLines.join("\n"),
|
|
4861
|
+
theirs: theirsLines.join("\n")
|
|
4862
|
+
});
|
|
4863
|
+
inConflict = false;
|
|
4864
|
+
inTheirs = false;
|
|
4865
|
+
} else if (inConflict) {
|
|
4866
|
+
if (inTheirs) {
|
|
4867
|
+
theirsLines.push(line);
|
|
4868
|
+
} else {
|
|
4869
|
+
oursLines.push(line);
|
|
4870
|
+
}
|
|
4871
|
+
}
|
|
4872
|
+
}
|
|
4873
|
+
return sections;
|
|
4874
|
+
}
|
|
4875
|
+
function formatConflictReport(cwd) {
|
|
4876
|
+
const conflictFiles = detectConflictFiles(cwd);
|
|
4877
|
+
if (conflictFiles.length === 0) return null;
|
|
4878
|
+
const fs22 = __require("fs");
|
|
4879
|
+
const path23 = __require("path");
|
|
4880
|
+
const lines = [
|
|
4881
|
+
`\u26A0 Merge conflicts detected in ${conflictFiles.length} file(s):`,
|
|
4882
|
+
""
|
|
4883
|
+
];
|
|
4884
|
+
for (const file of conflictFiles) {
|
|
4885
|
+
const fullPath = path23.join(cwd, file);
|
|
4886
|
+
let content;
|
|
4887
|
+
try {
|
|
4888
|
+
content = fs22.readFileSync(fullPath, "utf-8");
|
|
4889
|
+
} catch {
|
|
4890
|
+
lines.push(` - ${file} (cannot read)`);
|
|
4891
|
+
continue;
|
|
4892
|
+
}
|
|
4893
|
+
const sections = parseConflictMarkers(content, file);
|
|
4894
|
+
lines.push(` - ${file} (${sections.length} conflict(s))`);
|
|
4895
|
+
for (let i = 0; i < sections.length; i++) {
|
|
4896
|
+
const s = sections[i];
|
|
4897
|
+
lines.push(` [Conflict ${i + 1} at line ${s.startLine}]`);
|
|
4898
|
+
lines.push(` OURS:`);
|
|
4899
|
+
for (const l of s.ours.split("\n").slice(0, 5)) {
|
|
4900
|
+
lines.push(` ${l}`);
|
|
4901
|
+
}
|
|
4902
|
+
if (s.ours.split("\n").length > 5) lines.push(` ... (${s.ours.split("\n").length} lines)`);
|
|
4903
|
+
lines.push(` THEIRS:`);
|
|
4904
|
+
for (const l of s.theirs.split("\n").slice(0, 5)) {
|
|
4905
|
+
lines.push(` ${l}`);
|
|
4906
|
+
}
|
|
4907
|
+
if (s.theirs.split("\n").length > 5) lines.push(` ... (${s.theirs.split("\n").length} lines)`);
|
|
4908
|
+
}
|
|
4909
|
+
}
|
|
4910
|
+
return lines.join("\n");
|
|
4911
|
+
}
|
|
3901
4912
|
var gitTool = {
|
|
3902
4913
|
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.`,
|
|
4914
|
+
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
4915
|
inputSchema: {
|
|
3905
4916
|
type: "object",
|
|
3906
4917
|
properties: {
|
|
@@ -3929,7 +4940,9 @@ var gitTool = {
|
|
|
3929
4940
|
}
|
|
3930
4941
|
}
|
|
3931
4942
|
const readOnlyPrefixes = ["status", "diff", "log", "show", "branch", "tag", "remote", "stash list", "ls-files"];
|
|
3932
|
-
const
|
|
4943
|
+
const _isReadOnly = readOnlyPrefixes.some((p) => command.startsWith(p));
|
|
4944
|
+
const mergeCommands = /^(merge|pull|rebase|cherry-pick)\b/;
|
|
4945
|
+
const isMergeCommand = mergeCommands.test(command);
|
|
3933
4946
|
try {
|
|
3934
4947
|
const result = execSync4(`git ${command}`, {
|
|
3935
4948
|
encoding: "utf-8",
|
|
@@ -3937,9 +4950,26 @@ var gitTool = {
|
|
|
3937
4950
|
timeout: 3e4,
|
|
3938
4951
|
cwd: process.cwd()
|
|
3939
4952
|
});
|
|
4953
|
+
if (isMergeCommand) {
|
|
4954
|
+
const conflictReport = formatConflictReport(process.cwd());
|
|
4955
|
+
if (conflictReport) {
|
|
4956
|
+
return makeToolResult(`${result}
|
|
4957
|
+
|
|
4958
|
+
${conflictReport}`);
|
|
4959
|
+
}
|
|
4960
|
+
}
|
|
3940
4961
|
return makeToolResult(result || "(no output)");
|
|
3941
4962
|
} catch (err) {
|
|
3942
4963
|
const output3 = [err.stdout, err.stderr].filter(Boolean).join("\n");
|
|
4964
|
+
if (isMergeCommand) {
|
|
4965
|
+
const conflictReport = formatConflictReport(process.cwd());
|
|
4966
|
+
if (conflictReport) {
|
|
4967
|
+
return makeToolError(`git ${command} failed with conflicts:
|
|
4968
|
+
${output3}
|
|
4969
|
+
|
|
4970
|
+
${conflictReport}`);
|
|
4971
|
+
}
|
|
4972
|
+
}
|
|
3943
4973
|
return makeToolError(`git ${command} failed:
|
|
3944
4974
|
${output3 || err.message}`);
|
|
3945
4975
|
}
|
|
@@ -3950,15 +4980,92 @@ ${output3 || err.message}`);
|
|
|
3950
4980
|
init_esm_shims();
|
|
3951
4981
|
init_tool();
|
|
3952
4982
|
var cache = /* @__PURE__ */ new Map();
|
|
3953
|
-
var
|
|
4983
|
+
var DEFAULT_CACHE_TTL = 15 * 60 * 1e3;
|
|
4984
|
+
var MAX_RESPONSE_SIZE = 5 * 1024 * 1024;
|
|
4985
|
+
var MAX_TEXT_LEN = 5e4;
|
|
4986
|
+
var USER_AGENT = "Mozilla/5.0 (compatible; Codi/0.1; +https://github.com/gemdoq/codi)";
|
|
4987
|
+
function extractCharset(contentType) {
|
|
4988
|
+
const match = contentType.match(/charset=([^\s;]+)/i);
|
|
4989
|
+
return match && match[1] ? match[1].replace(/['"]/g, "") : null;
|
|
4990
|
+
}
|
|
4991
|
+
function parseCacheMaxAge(headers) {
|
|
4992
|
+
const cc = headers.get("cache-control");
|
|
4993
|
+
if (!cc) return null;
|
|
4994
|
+
const match = cc.match(/max-age=(\d+)/);
|
|
4995
|
+
if (!match || !match[1]) return null;
|
|
4996
|
+
const seconds = parseInt(match[1], 10);
|
|
4997
|
+
if (isNaN(seconds) || seconds <= 0) return null;
|
|
4998
|
+
const clamped = Math.max(60, Math.min(seconds, 3600));
|
|
4999
|
+
return clamped * 1e3;
|
|
5000
|
+
}
|
|
5001
|
+
function isPdf(url, contentType) {
|
|
5002
|
+
return contentType.includes("application/pdf") || /\.pdf(\?|#|$)/i.test(url);
|
|
5003
|
+
}
|
|
5004
|
+
function isJson(contentType) {
|
|
5005
|
+
return contentType.includes("application/json") || contentType.includes("+json");
|
|
5006
|
+
}
|
|
5007
|
+
async function extractHtmlText(html) {
|
|
5008
|
+
const { load } = await import("cheerio");
|
|
5009
|
+
const $ = load(html);
|
|
5010
|
+
$('script, style, nav, header, footer, iframe, noscript, svg, [role="navigation"], [role="banner"], .sidebar, .ad, .ads, .advertisement').remove();
|
|
5011
|
+
const main2 = $('main, article, .content, #content, .main, [role="main"]').first();
|
|
5012
|
+
let text = main2.length ? main2.text() : $("body").text();
|
|
5013
|
+
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();
|
|
5014
|
+
return text;
|
|
5015
|
+
}
|
|
5016
|
+
async function extractPdfText(buffer) {
|
|
5017
|
+
const { PDFParse } = await import("pdf-parse");
|
|
5018
|
+
const parser = new PDFParse({ data: new Uint8Array(buffer) });
|
|
5019
|
+
try {
|
|
5020
|
+
const textResult = await parser.getText();
|
|
5021
|
+
const text = textResult.text?.trim() || "";
|
|
5022
|
+
const totalPages = textResult.total ?? "unknown";
|
|
5023
|
+
let infoStr = `Pages: ${totalPages}`;
|
|
5024
|
+
try {
|
|
5025
|
+
const info = await parser.getInfo();
|
|
5026
|
+
if (info.info?.Title) infoStr += ` | Title: ${info.info.Title}`;
|
|
5027
|
+
if (info.info?.Author) infoStr += ` | Author: ${info.info.Author}`;
|
|
5028
|
+
} catch {
|
|
5029
|
+
}
|
|
5030
|
+
return `[PDF] ${infoStr}
|
|
5031
|
+
|
|
5032
|
+
${text}`;
|
|
5033
|
+
} finally {
|
|
5034
|
+
await parser.destroy().catch(() => {
|
|
5035
|
+
});
|
|
5036
|
+
}
|
|
5037
|
+
}
|
|
5038
|
+
function formatJson(raw) {
|
|
5039
|
+
try {
|
|
5040
|
+
const parsed = JSON.parse(raw);
|
|
5041
|
+
return `[JSON Response]
|
|
5042
|
+
${JSON.stringify(parsed, null, 2)}`;
|
|
5043
|
+
} catch {
|
|
5044
|
+
return `[JSON Response - parse error]
|
|
5045
|
+
${raw}`;
|
|
5046
|
+
}
|
|
5047
|
+
}
|
|
5048
|
+
async function decodeResponse(response, contentType) {
|
|
5049
|
+
const charset = extractCharset(contentType);
|
|
5050
|
+
if (charset && charset.toLowerCase() !== "utf-8" && charset.toLowerCase() !== "utf8") {
|
|
5051
|
+
const buffer = await response.arrayBuffer();
|
|
5052
|
+
const decoder = new TextDecoder(charset);
|
|
5053
|
+
return decoder.decode(buffer);
|
|
5054
|
+
}
|
|
5055
|
+
return response.text();
|
|
5056
|
+
}
|
|
3954
5057
|
var webFetchTool = {
|
|
3955
5058
|
name: "web_fetch",
|
|
3956
|
-
description: `Fetch content from a URL
|
|
5059
|
+
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
5060
|
inputSchema: {
|
|
3958
5061
|
type: "object",
|
|
3959
5062
|
properties: {
|
|
3960
5063
|
url: { type: "string", description: "URL to fetch" },
|
|
3961
|
-
prompt: { type: "string", description: "What information to extract from the page" }
|
|
5064
|
+
prompt: { type: "string", description: "What information to extract from the page" },
|
|
5065
|
+
cache_ttl: {
|
|
5066
|
+
type: "number",
|
|
5067
|
+
description: "Cache TTL in seconds (default: 900, i.e. 15 minutes). Set to 0 to bypass cache."
|
|
5068
|
+
}
|
|
3962
5069
|
},
|
|
3963
5070
|
required: ["url", "prompt"]
|
|
3964
5071
|
},
|
|
@@ -3967,50 +5074,103 @@ var webFetchTool = {
|
|
|
3967
5074
|
async execute(input3) {
|
|
3968
5075
|
let url = String(input3["url"]);
|
|
3969
5076
|
const prompt = String(input3["prompt"] || "");
|
|
5077
|
+
const cacheTtlInput = input3["cache_ttl"];
|
|
5078
|
+
const requestTtl = typeof cacheTtlInput === "number" ? cacheTtlInput * 1e3 : null;
|
|
5079
|
+
const bypassCache = requestTtl === 0;
|
|
3970
5080
|
if (url.startsWith("http://")) {
|
|
3971
5081
|
url = url.replace("http://", "https://");
|
|
3972
5082
|
}
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
5083
|
+
if (!bypassCache) {
|
|
5084
|
+
const cached = cache.get(url);
|
|
5085
|
+
if (cached && Date.now() - cached.timestamp < cached.ttl) {
|
|
5086
|
+
return makeToolResult(
|
|
5087
|
+
`[Cached] ${prompt ? `Query: ${prompt}
|
|
3976
5088
|
|
|
3977
|
-
` : ""}${cached.content}`
|
|
5089
|
+
` : ""}${cached.content}`
|
|
5090
|
+
);
|
|
5091
|
+
}
|
|
3978
5092
|
}
|
|
3979
5093
|
try {
|
|
3980
5094
|
const response = await fetch(url, {
|
|
3981
5095
|
headers: {
|
|
3982
|
-
"User-Agent":
|
|
3983
|
-
|
|
5096
|
+
"User-Agent": USER_AGENT,
|
|
5097
|
+
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
5098
|
},
|
|
3985
5099
|
redirect: "follow",
|
|
3986
5100
|
signal: AbortSignal.timeout(3e4)
|
|
3987
5101
|
});
|
|
3988
5102
|
if (!response.ok) {
|
|
3989
|
-
|
|
5103
|
+
const status = response.status;
|
|
5104
|
+
const statusText = response.statusText || "Unknown";
|
|
5105
|
+
let detail = `HTTP ${status} ${statusText}`;
|
|
5106
|
+
if (status === 403 || status === 401) {
|
|
5107
|
+
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.";
|
|
5108
|
+
} else if (status === 404) {
|
|
5109
|
+
detail += " - \uD398\uC774\uC9C0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.";
|
|
5110
|
+
} else if (status === 429) {
|
|
5111
|
+
detail += " - \uC694\uCCAD\uC774 \uB108\uBB34 \uB9CE\uC2B5\uB2C8\uB2E4. \uC7A0\uC2DC \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.";
|
|
5112
|
+
} else if (status >= 500) {
|
|
5113
|
+
detail += " - \uC11C\uBC84 \uC624\uB958\uC785\uB2C8\uB2E4.";
|
|
5114
|
+
}
|
|
5115
|
+
if (response.redirected) {
|
|
5116
|
+
detail += `
|
|
5117
|
+
Redirected to: ${response.url}`;
|
|
5118
|
+
}
|
|
5119
|
+
return makeToolError(detail);
|
|
5120
|
+
}
|
|
5121
|
+
const contentLength = response.headers.get("content-length");
|
|
5122
|
+
if (contentLength && parseInt(contentLength, 10) > MAX_RESPONSE_SIZE) {
|
|
5123
|
+
return makeToolError(
|
|
5124
|
+
`\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.`
|
|
5125
|
+
);
|
|
3990
5126
|
}
|
|
3991
5127
|
const contentType = response.headers.get("content-type") || "";
|
|
3992
5128
|
let text;
|
|
3993
|
-
if (
|
|
3994
|
-
const
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
5129
|
+
if (isPdf(url, contentType)) {
|
|
5130
|
+
const buffer = await response.arrayBuffer();
|
|
5131
|
+
if (buffer.byteLength > MAX_RESPONSE_SIZE) {
|
|
5132
|
+
return makeToolError(
|
|
5133
|
+
`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.`
|
|
5134
|
+
);
|
|
5135
|
+
}
|
|
5136
|
+
text = await extractPdfText(buffer);
|
|
5137
|
+
} else if (isJson(contentType)) {
|
|
5138
|
+
const raw = await decodeResponse(response, contentType);
|
|
5139
|
+
text = formatJson(raw);
|
|
5140
|
+
} else if (contentType.includes("text/html") || contentType.includes("application/xhtml")) {
|
|
5141
|
+
const html = await decodeResponse(response, contentType);
|
|
5142
|
+
text = await extractHtmlText(html);
|
|
4000
5143
|
} else {
|
|
4001
|
-
text = await response
|
|
5144
|
+
text = await decodeResponse(response, contentType);
|
|
5145
|
+
}
|
|
5146
|
+
if (text.length > MAX_TEXT_LEN) {
|
|
5147
|
+
text = text.slice(0, MAX_TEXT_LEN) + "\n\n... (truncated)";
|
|
5148
|
+
}
|
|
5149
|
+
const effectiveTtl = requestTtl ?? parseCacheMaxAge(response.headers) ?? DEFAULT_CACHE_TTL;
|
|
5150
|
+
if (!bypassCache) {
|
|
5151
|
+
cache.set(url, { content: text, timestamp: Date.now(), ttl: effectiveTtl });
|
|
4002
5152
|
}
|
|
4003
|
-
|
|
4004
|
-
if (
|
|
4005
|
-
|
|
5153
|
+
let prefix = `URL: ${url}`;
|
|
5154
|
+
if (response.redirected && response.url !== url) {
|
|
5155
|
+
prefix += `
|
|
5156
|
+
Redirected to: ${response.url}`;
|
|
4006
5157
|
}
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
Query: ${prompt}
|
|
5158
|
+
if (prompt) {
|
|
5159
|
+
prefix += `
|
|
5160
|
+
Query: ${prompt}`;
|
|
5161
|
+
}
|
|
5162
|
+
return makeToolResult(`${prefix}
|
|
4010
5163
|
|
|
4011
|
-
${text}`
|
|
5164
|
+
${text}`);
|
|
4012
5165
|
} catch (err) {
|
|
4013
|
-
|
|
5166
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5167
|
+
if (message.includes("TimeoutError") || message.includes("aborted")) {
|
|
5168
|
+
return makeToolError(`\uC694\uCCAD \uC2DC\uAC04 \uCD08\uACFC (30\uCD08): ${url}`);
|
|
5169
|
+
}
|
|
5170
|
+
if (message.includes("ENOTFOUND") || message.includes("getaddrinfo")) {
|
|
5171
|
+
return makeToolError(`\uB3C4\uBA54\uC778\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${url}`);
|
|
5172
|
+
}
|
|
5173
|
+
return makeToolError(`URL \uAC00\uC838\uC624\uAE30 \uC2E4\uD328: ${message}`);
|
|
4014
5174
|
}
|
|
4015
5175
|
}
|
|
4016
5176
|
};
|
|
@@ -4018,73 +5178,181 @@ ${text}` : text);
|
|
|
4018
5178
|
// src/tools/web-search.ts
|
|
4019
5179
|
init_esm_shims();
|
|
4020
5180
|
init_tool();
|
|
5181
|
+
var searchCache = /* @__PURE__ */ new Map();
|
|
5182
|
+
var SEARCH_CACHE_TTL = 10 * 60 * 1e3;
|
|
5183
|
+
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";
|
|
5184
|
+
function extractRealUrl(href) {
|
|
5185
|
+
try {
|
|
5186
|
+
const urlObj = new URL(href, "https://duckduckgo.com");
|
|
5187
|
+
return urlObj.searchParams.get("uddg") || href;
|
|
5188
|
+
} catch {
|
|
5189
|
+
return href;
|
|
5190
|
+
}
|
|
5191
|
+
}
|
|
5192
|
+
function normalizeUrl(url) {
|
|
5193
|
+
try {
|
|
5194
|
+
const u = new URL(url);
|
|
5195
|
+
const trackingParams = ["utm_source", "utm_medium", "utm_campaign", "utm_content", "utm_term", "ref", "fbclid", "gclid"];
|
|
5196
|
+
for (const param of trackingParams) {
|
|
5197
|
+
u.searchParams.delete(param);
|
|
5198
|
+
}
|
|
5199
|
+
let path23 = u.pathname.replace(/\/+$/, "") || "/";
|
|
5200
|
+
return `${u.hostname}${path23}${u.search}`;
|
|
5201
|
+
} catch {
|
|
5202
|
+
return url;
|
|
5203
|
+
}
|
|
5204
|
+
}
|
|
5205
|
+
function deduplicateResults(results) {
|
|
5206
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5207
|
+
const deduped = [];
|
|
5208
|
+
for (const result of results) {
|
|
5209
|
+
const normalized = normalizeUrl(result.url);
|
|
5210
|
+
if (!seen.has(normalized) && result.title.length > 0) {
|
|
5211
|
+
seen.add(normalized);
|
|
5212
|
+
deduped.push(result);
|
|
5213
|
+
}
|
|
5214
|
+
}
|
|
5215
|
+
return deduped;
|
|
5216
|
+
}
|
|
5217
|
+
async function searchDuckDuckGo(query, maxResults) {
|
|
5218
|
+
const encoded = encodeURIComponent(query);
|
|
5219
|
+
const url = `https://html.duckduckgo.com/html/?q=${encoded}`;
|
|
5220
|
+
const response = await fetch(url, {
|
|
5221
|
+
headers: {
|
|
5222
|
+
"User-Agent": USER_AGENT2,
|
|
5223
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
5224
|
+
"Accept-Language": "en-US,en;q=0.9"
|
|
5225
|
+
},
|
|
5226
|
+
signal: AbortSignal.timeout(15e3)
|
|
5227
|
+
});
|
|
5228
|
+
if (!response.ok) {
|
|
5229
|
+
throw new Error(`DuckDuckGo HTTP ${response.status} ${response.statusText}`);
|
|
5230
|
+
}
|
|
5231
|
+
const html = await response.text();
|
|
5232
|
+
if (html.includes("If this error persists") || html.includes("blocked")) {
|
|
5233
|
+
throw new Error("DuckDuckGo\uC5D0\uC11C \uC694\uCCAD\uC774 \uCC28\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4");
|
|
5234
|
+
}
|
|
5235
|
+
const { load } = await import("cheerio");
|
|
5236
|
+
const $ = load(html);
|
|
5237
|
+
const results = [];
|
|
5238
|
+
$(".result").each((_i, el) => {
|
|
5239
|
+
const $el = $(el);
|
|
5240
|
+
if ($el.hasClass("result--ad") || $el.find(".badge--ad").length > 0) {
|
|
5241
|
+
return;
|
|
5242
|
+
}
|
|
5243
|
+
const titleEl = $el.find(".result__title a, .result__a");
|
|
5244
|
+
const title = titleEl.text().trim();
|
|
5245
|
+
const href = titleEl.attr("href") || "";
|
|
5246
|
+
const snippet = $el.find(".result__snippet").text().trim();
|
|
5247
|
+
if (title && href) {
|
|
5248
|
+
const actualUrl = extractRealUrl(href);
|
|
5249
|
+
if (actualUrl.startsWith("http://") || actualUrl.startsWith("https://")) {
|
|
5250
|
+
results.push({ title, url: actualUrl, snippet });
|
|
5251
|
+
}
|
|
5252
|
+
}
|
|
5253
|
+
});
|
|
5254
|
+
return deduplicateResults(results).slice(0, maxResults);
|
|
5255
|
+
}
|
|
5256
|
+
async function searchDuckDuckGoLite(query, maxResults) {
|
|
5257
|
+
const encoded = encodeURIComponent(query);
|
|
5258
|
+
const url = `https://lite.duckduckgo.com/lite/?q=${encoded}`;
|
|
5259
|
+
const response = await fetch(url, {
|
|
5260
|
+
headers: {
|
|
5261
|
+
"User-Agent": USER_AGENT2,
|
|
5262
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
|
|
5263
|
+
},
|
|
5264
|
+
signal: AbortSignal.timeout(15e3)
|
|
5265
|
+
});
|
|
5266
|
+
if (!response.ok) {
|
|
5267
|
+
throw new Error(`DuckDuckGo Lite HTTP ${response.status}`);
|
|
5268
|
+
}
|
|
5269
|
+
const html = await response.text();
|
|
5270
|
+
const { load } = await import("cheerio");
|
|
5271
|
+
const $ = load(html);
|
|
5272
|
+
const results = [];
|
|
5273
|
+
$("a.result-link").each((_i, el) => {
|
|
5274
|
+
const $a = $(el);
|
|
5275
|
+
const title = $a.text().trim();
|
|
5276
|
+
const href = $a.attr("href") || "";
|
|
5277
|
+
if (title && href) {
|
|
5278
|
+
const actualUrl = extractRealUrl(href);
|
|
5279
|
+
const $row = $a.closest("tr");
|
|
5280
|
+
const snippet = $row.next("tr").find(".result-snippet").text().trim() || $row.next("tr").find("td").last().text().trim();
|
|
5281
|
+
if (actualUrl.startsWith("http://") || actualUrl.startsWith("https://")) {
|
|
5282
|
+
results.push({ title, url: actualUrl, snippet });
|
|
5283
|
+
}
|
|
5284
|
+
}
|
|
5285
|
+
});
|
|
5286
|
+
return deduplicateResults(results).slice(0, maxResults);
|
|
5287
|
+
}
|
|
4021
5288
|
var webSearchTool = {
|
|
4022
5289
|
name: "web_search",
|
|
4023
|
-
description: `Search the web for information. Returns search results with titles, URLs, and snippets. Uses DuckDuckGo
|
|
5290
|
+
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
5291
|
inputSchema: {
|
|
4025
5292
|
type: "object",
|
|
4026
5293
|
properties: {
|
|
4027
|
-
query: { type: "string", description: "Search query" }
|
|
5294
|
+
query: { type: "string", description: "Search query" },
|
|
5295
|
+
max_results: {
|
|
5296
|
+
type: "number",
|
|
5297
|
+
description: "Maximum number of results (default: 10, max: 20)"
|
|
5298
|
+
}
|
|
4028
5299
|
},
|
|
4029
5300
|
required: ["query"]
|
|
4030
5301
|
},
|
|
4031
5302
|
dangerous: true,
|
|
4032
5303
|
readOnly: true,
|
|
4033
5304
|
async execute(input3) {
|
|
4034
|
-
const query = String(input3["query"]);
|
|
5305
|
+
const query = String(input3["query"]).trim();
|
|
5306
|
+
if (!query) {
|
|
5307
|
+
return makeToolError("\uAC80\uC0C9\uC5B4\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4.");
|
|
5308
|
+
}
|
|
5309
|
+
const rawMax = typeof input3["max_results"] === "number" ? input3["max_results"] : 10;
|
|
5310
|
+
const maxResults = Math.max(1, Math.min(20, Math.round(rawMax)));
|
|
5311
|
+
const cacheKey = `${query}|${maxResults}`;
|
|
5312
|
+
const cached = searchCache.get(cacheKey);
|
|
5313
|
+
if (cached && Date.now() - cached.timestamp < SEARCH_CACHE_TTL) {
|
|
5314
|
+
return makeToolResult(`[Cached] ${cached.results}`);
|
|
5315
|
+
}
|
|
5316
|
+
let results = [];
|
|
5317
|
+
let fallbackUsed = false;
|
|
4035
5318
|
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}`);
|
|
5319
|
+
results = await searchDuckDuckGo(query, maxResults);
|
|
5320
|
+
} catch (primaryErr) {
|
|
5321
|
+
try {
|
|
5322
|
+
results = await searchDuckDuckGoLite(query, maxResults);
|
|
5323
|
+
fallbackUsed = true;
|
|
5324
|
+
} catch (fallbackErr) {
|
|
5325
|
+
const primaryMsg = primaryErr instanceof Error ? primaryErr.message : String(primaryErr);
|
|
5326
|
+
const fallbackMsg = fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr);
|
|
5327
|
+
return makeToolError(
|
|
5328
|
+
`\uAC80\uC0C9 \uC2E4\uD328:
|
|
5329
|
+
Primary: ${primaryMsg}
|
|
5330
|
+
Fallback: ${fallbackMsg}`
|
|
5331
|
+
);
|
|
4070
5332
|
}
|
|
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
5333
|
}
|
|
5334
|
+
if (results.length === 0) {
|
|
5335
|
+
return makeToolResult(`"${query}"\uC5D0 \uB300\uD55C \uAC80\uC0C9 \uACB0\uACFC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.`);
|
|
5336
|
+
}
|
|
5337
|
+
const formatted = results.map(
|
|
5338
|
+
(r, i) => `${i + 1}. ${r.title}
|
|
5339
|
+
${r.url}${r.snippet ? `
|
|
5340
|
+
${r.snippet}` : ""}`
|
|
5341
|
+
).join("\n\n");
|
|
5342
|
+
const header = fallbackUsed ? `Search results for: ${query} (fallback engine used)` : `Search results for: ${query}`;
|
|
5343
|
+
const output3 = `${header}
|
|
5344
|
+
|
|
5345
|
+
${formatted}`;
|
|
5346
|
+
searchCache.set(cacheKey, { results: output3, timestamp: Date.now() });
|
|
5347
|
+
return makeToolResult(output3);
|
|
4080
5348
|
}
|
|
4081
5349
|
};
|
|
4082
5350
|
|
|
4083
5351
|
// src/tools/notebook-edit.ts
|
|
4084
5352
|
init_esm_shims();
|
|
4085
5353
|
init_tool();
|
|
4086
|
-
import * as
|
|
4087
|
-
import * as
|
|
5354
|
+
import * as fs20 from "fs";
|
|
5355
|
+
import * as path21 from "path";
|
|
4088
5356
|
var notebookEditTool = {
|
|
4089
5357
|
name: "notebook_edit",
|
|
4090
5358
|
description: `Edit Jupyter notebook (.ipynb) cells. Supports replacing, inserting, and deleting cells.`,
|
|
@@ -4102,16 +5370,16 @@ var notebookEditTool = {
|
|
|
4102
5370
|
dangerous: true,
|
|
4103
5371
|
readOnly: false,
|
|
4104
5372
|
async execute(input3) {
|
|
4105
|
-
const nbPath =
|
|
5373
|
+
const nbPath = path21.resolve(String(input3["notebook_path"]));
|
|
4106
5374
|
const cellNumber = input3["cell_number"];
|
|
4107
5375
|
const newSource = String(input3["new_source"]);
|
|
4108
5376
|
const cellType = input3["cell_type"] || "code";
|
|
4109
5377
|
const editMode = input3["edit_mode"] || "replace";
|
|
4110
|
-
if (!
|
|
5378
|
+
if (!fs20.existsSync(nbPath)) {
|
|
4111
5379
|
return makeToolError(`Notebook not found: ${nbPath}`);
|
|
4112
5380
|
}
|
|
4113
5381
|
try {
|
|
4114
|
-
const content =
|
|
5382
|
+
const content = fs20.readFileSync(nbPath, "utf-8");
|
|
4115
5383
|
const nb = JSON.parse(content);
|
|
4116
5384
|
if (!nb.cells || !Array.isArray(nb.cells)) {
|
|
4117
5385
|
return makeToolError("Invalid notebook format: no cells array");
|
|
@@ -4156,7 +5424,7 @@ var notebookEditTool = {
|
|
|
4156
5424
|
default:
|
|
4157
5425
|
return makeToolError(`Unknown edit_mode: ${editMode}`);
|
|
4158
5426
|
}
|
|
4159
|
-
|
|
5427
|
+
fs20.writeFileSync(nbPath, JSON.stringify(nb, null, 1), "utf-8");
|
|
4160
5428
|
return makeToolResult(`Notebook ${editMode}d cell in ${nbPath} (${nb.cells.length} cells total)`);
|
|
4161
5429
|
} catch (err) {
|
|
4162
5430
|
return makeToolError(`Notebook edit failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -4170,8 +5438,7 @@ init_task_tools();
|
|
|
4170
5438
|
// src/tools/ask-user.ts
|
|
4171
5439
|
init_esm_shims();
|
|
4172
5440
|
init_tool();
|
|
4173
|
-
import
|
|
4174
|
-
import chalk12 from "chalk";
|
|
5441
|
+
import chalk13 from "chalk";
|
|
4175
5442
|
var askUserTool = {
|
|
4176
5443
|
name: "ask_user",
|
|
4177
5444
|
description: `Ask the user a question with optional choices. Use to gather preferences, clarify requirements, or get decisions.`,
|
|
@@ -4202,23 +5469,18 @@ var askUserTool = {
|
|
|
4202
5469
|
const options = input3["options"];
|
|
4203
5470
|
const multiSelect = input3["multiSelect"] === true;
|
|
4204
5471
|
console.log("");
|
|
4205
|
-
console.log(
|
|
5472
|
+
console.log(chalk13.cyan.bold("? ") + chalk13.bold(question));
|
|
4206
5473
|
if (options && options.length > 0) {
|
|
4207
5474
|
console.log("");
|
|
4208
5475
|
for (let i = 0; i < options.length; i++) {
|
|
4209
5476
|
const opt = options[i];
|
|
4210
|
-
console.log(
|
|
5477
|
+
console.log(chalk13.cyan(` ${i + 1}.`) + ` ${opt.label}${opt.description ? chalk13.dim(` - ${opt.description}`) : ""}`);
|
|
4211
5478
|
}
|
|
4212
|
-
console.log(
|
|
5479
|
+
console.log(chalk13.dim(` ${options.length + 1}. Other (type custom response)`));
|
|
4213
5480
|
console.log("");
|
|
4214
|
-
const rl2 = readline4.createInterface({
|
|
4215
|
-
input: process.stdin,
|
|
4216
|
-
output: process.stdout
|
|
4217
|
-
});
|
|
4218
5481
|
try {
|
|
4219
|
-
const prompt = multiSelect ?
|
|
4220
|
-
const answer = await
|
|
4221
|
-
rl2.close();
|
|
5482
|
+
const prompt = multiSelect ? chalk13.dim("Enter numbers separated by commas: ") : chalk13.dim("Enter number or type response: ");
|
|
5483
|
+
const answer = await sharedPrompt(prompt);
|
|
4222
5484
|
if (multiSelect) {
|
|
4223
5485
|
const indices = answer.split(",").map((s) => parseInt(s.trim()) - 1);
|
|
4224
5486
|
const selected = indices.filter((i) => i >= 0 && i < options.length).map((i) => options[i].label);
|
|
@@ -4233,25 +5495,145 @@ var askUserTool = {
|
|
|
4233
5495
|
}
|
|
4234
5496
|
return makeToolResult(`User response: ${answer}`);
|
|
4235
5497
|
} catch {
|
|
4236
|
-
rl2.close();
|
|
4237
5498
|
return makeToolError("Failed to get user input");
|
|
4238
5499
|
}
|
|
4239
5500
|
}
|
|
4240
|
-
const rl = readline4.createInterface({
|
|
4241
|
-
input: process.stdin,
|
|
4242
|
-
output: process.stdout
|
|
4243
|
-
});
|
|
4244
5501
|
try {
|
|
4245
|
-
const answer = await
|
|
4246
|
-
rl.close();
|
|
5502
|
+
const answer = await sharedPrompt(chalk13.dim("> "));
|
|
4247
5503
|
return makeToolResult(`User response: ${answer}`);
|
|
4248
5504
|
} catch {
|
|
4249
|
-
rl.close();
|
|
4250
5505
|
return makeToolError("Failed to get user input");
|
|
4251
5506
|
}
|
|
4252
5507
|
}
|
|
4253
5508
|
};
|
|
4254
5509
|
|
|
5510
|
+
// src/tools/memory-tool.ts
|
|
5511
|
+
init_esm_shims();
|
|
5512
|
+
init_tool();
|
|
5513
|
+
import * as fs21 from "fs";
|
|
5514
|
+
import * as path22 from "path";
|
|
5515
|
+
function buildFrontmatter(topic, description) {
|
|
5516
|
+
return `---
|
|
5517
|
+
name: ${topic}
|
|
5518
|
+
description: ${description}
|
|
5519
|
+
type: project
|
|
5520
|
+
---
|
|
5521
|
+
`;
|
|
5522
|
+
}
|
|
5523
|
+
function parseFrontmatter(content) {
|
|
5524
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
5525
|
+
if (!match) return { body: content };
|
|
5526
|
+
const meta = {};
|
|
5527
|
+
for (const line of match[1].split("\n")) {
|
|
5528
|
+
const idx = line.indexOf(":");
|
|
5529
|
+
if (idx !== -1) {
|
|
5530
|
+
meta[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
|
5531
|
+
}
|
|
5532
|
+
}
|
|
5533
|
+
return { name: meta["name"], description: meta["description"], body: match[2] };
|
|
5534
|
+
}
|
|
5535
|
+
function updateIndex(memoryDir) {
|
|
5536
|
+
const indexPath = path22.join(memoryDir, "MEMORY.md");
|
|
5537
|
+
const files = fs21.readdirSync(memoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md").sort();
|
|
5538
|
+
const lines = ["# Project Memory", ""];
|
|
5539
|
+
if (files.length === 0) {
|
|
5540
|
+
lines.push("No topics saved yet.");
|
|
5541
|
+
} else {
|
|
5542
|
+
lines.push("| Topic | Description |");
|
|
5543
|
+
lines.push("|-------|-------------|");
|
|
5544
|
+
for (const file of files) {
|
|
5545
|
+
const content = fs21.readFileSync(path22.join(memoryDir, file), "utf-8");
|
|
5546
|
+
const parsed = parseFrontmatter(content);
|
|
5547
|
+
const topic = file.replace(".md", "");
|
|
5548
|
+
const desc = parsed.description || "";
|
|
5549
|
+
lines.push(`| [${topic}](${file}) | ${desc} |`);
|
|
5550
|
+
}
|
|
5551
|
+
}
|
|
5552
|
+
lines.push("");
|
|
5553
|
+
fs21.writeFileSync(indexPath, lines.join("\n"), "utf-8");
|
|
5554
|
+
}
|
|
5555
|
+
var updateMemoryTool = {
|
|
5556
|
+
name: "update_memory",
|
|
5557
|
+
description: `Save, delete, or list project memory topics. Use this to persist important information (architecture decisions, user preferences, patterns, etc.) across conversations.
|
|
5558
|
+
- save: Create or update a memory topic file with content
|
|
5559
|
+
- delete: Remove a memory topic file
|
|
5560
|
+
- list: List all existing memory topics with descriptions`,
|
|
5561
|
+
inputSchema: {
|
|
5562
|
+
type: "object",
|
|
5563
|
+
properties: {
|
|
5564
|
+
action: {
|
|
5565
|
+
type: "string",
|
|
5566
|
+
enum: ["save", "delete", "list"],
|
|
5567
|
+
description: "Action to perform"
|
|
5568
|
+
},
|
|
5569
|
+
topic: {
|
|
5570
|
+
type: "string",
|
|
5571
|
+
description: 'Topic name (used as filename, e.g. "architecture" -> architecture.md). Required for save/delete.'
|
|
5572
|
+
},
|
|
5573
|
+
content: {
|
|
5574
|
+
type: "string",
|
|
5575
|
+
description: "Content to save. First line is used as description. Required for save."
|
|
5576
|
+
}
|
|
5577
|
+
},
|
|
5578
|
+
required: ["action", "topic"]
|
|
5579
|
+
},
|
|
5580
|
+
dangerous: false,
|
|
5581
|
+
readOnly: false,
|
|
5582
|
+
async execute(input3) {
|
|
5583
|
+
const action = String(input3["action"]);
|
|
5584
|
+
const topic = String(input3["topic"] || "");
|
|
5585
|
+
const content = input3["content"] != null ? String(input3["content"]) : void 0;
|
|
5586
|
+
const memoryDir = memoryManager.getMemoryDir();
|
|
5587
|
+
switch (action) {
|
|
5588
|
+
case "save": {
|
|
5589
|
+
if (!topic) return makeToolError("topic is required for save action");
|
|
5590
|
+
if (!content) return makeToolError("content is required for save action");
|
|
5591
|
+
memoryManager.ensureDir();
|
|
5592
|
+
const firstLine = content.split("\n")[0].trim();
|
|
5593
|
+
const description = firstLine.length > 100 ? firstLine.slice(0, 100) + "..." : firstLine;
|
|
5594
|
+
const fileContent = buildFrontmatter(topic, description) + content;
|
|
5595
|
+
const topicPath = path22.join(memoryDir, `${topic}.md`);
|
|
5596
|
+
fs21.writeFileSync(topicPath, fileContent, "utf-8");
|
|
5597
|
+
updateIndex(memoryDir);
|
|
5598
|
+
return makeToolResult(`Memory topic "${topic}" saved to ${topicPath}`);
|
|
5599
|
+
}
|
|
5600
|
+
case "delete": {
|
|
5601
|
+
if (!topic) return makeToolError("topic is required for delete action");
|
|
5602
|
+
const topicPath = path22.join(memoryDir, `${topic}.md`);
|
|
5603
|
+
if (!fs21.existsSync(topicPath)) {
|
|
5604
|
+
return makeToolError(`Memory topic "${topic}" not found`);
|
|
5605
|
+
}
|
|
5606
|
+
fs21.unlinkSync(topicPath);
|
|
5607
|
+
memoryManager.ensureDir();
|
|
5608
|
+
updateIndex(memoryDir);
|
|
5609
|
+
return makeToolResult(`Memory topic "${topic}" deleted`);
|
|
5610
|
+
}
|
|
5611
|
+
case "list": {
|
|
5612
|
+
const topics = memoryManager.listTopics();
|
|
5613
|
+
if (topics.length === 0) {
|
|
5614
|
+
return makeToolResult("No memory topics saved yet.");
|
|
5615
|
+
}
|
|
5616
|
+
const lines = [];
|
|
5617
|
+
for (const t of topics) {
|
|
5618
|
+
const raw = memoryManager.loadTopic(t);
|
|
5619
|
+
if (raw) {
|
|
5620
|
+
const parsed = parseFrontmatter(raw);
|
|
5621
|
+
lines.push(`- ${t}: ${parsed.description || "(no description)"}`);
|
|
5622
|
+
} else {
|
|
5623
|
+
lines.push(`- ${t}: (no description)`);
|
|
5624
|
+
}
|
|
5625
|
+
}
|
|
5626
|
+
return makeToolResult(`Memory topics (${topics.length}):
|
|
5627
|
+
${lines.join("\n")}
|
|
5628
|
+
|
|
5629
|
+
Memory dir: ${memoryDir}`);
|
|
5630
|
+
}
|
|
5631
|
+
default:
|
|
5632
|
+
return makeToolError(`Unknown action: ${action}. Use save, delete, or list.`);
|
|
5633
|
+
}
|
|
5634
|
+
}
|
|
5635
|
+
};
|
|
5636
|
+
|
|
4255
5637
|
// src/llm/anthropic.ts
|
|
4256
5638
|
init_esm_shims();
|
|
4257
5639
|
import Anthropic from "@anthropic-ai/sdk";
|
|
@@ -4907,12 +6289,12 @@ function parseArgs(argv) {
|
|
|
4907
6289
|
}
|
|
4908
6290
|
function printHelp() {
|
|
4909
6291
|
console.log(`
|
|
4910
|
-
${
|
|
6292
|
+
${chalk14.cyan.bold("Codi (\uCF54\uB514)")} - AI Code Agent for Terminal
|
|
4911
6293
|
|
|
4912
|
-
${
|
|
6294
|
+
${chalk14.bold("Usage:")}
|
|
4913
6295
|
codi [options] [prompt]
|
|
4914
6296
|
|
|
4915
|
-
${
|
|
6297
|
+
${chalk14.bold("Options:")}
|
|
4916
6298
|
-m, --model <model> Set the model (default: gemini-2.5-flash)
|
|
4917
6299
|
--provider <name> Set the provider (openai, anthropic, ollama)
|
|
4918
6300
|
-p <prompt> Run a single prompt and exit
|
|
@@ -4923,12 +6305,12 @@ ${chalk13.bold("Options:")}
|
|
|
4923
6305
|
-h, --help Show this help
|
|
4924
6306
|
-v, --version Show version
|
|
4925
6307
|
|
|
4926
|
-
${
|
|
6308
|
+
${chalk14.bold("Environment:")}
|
|
4927
6309
|
GEMINI_API_KEY Google Gemini API key (default provider)
|
|
4928
6310
|
OPENAI_API_KEY OpenAI API key
|
|
4929
6311
|
ANTHROPIC_API_KEY Anthropic API key
|
|
4930
6312
|
|
|
4931
|
-
${
|
|
6313
|
+
${chalk14.bold("Examples:")}
|
|
4932
6314
|
codi # Start interactive session
|
|
4933
6315
|
codi -p "explain main.ts" # Single prompt
|
|
4934
6316
|
codi --provider anthropic # Use Anthropic Claude
|
|
@@ -4944,11 +6326,11 @@ async function main() {
|
|
|
4944
6326
|
}
|
|
4945
6327
|
if (args.version) {
|
|
4946
6328
|
try {
|
|
4947
|
-
const { readFileSync:
|
|
6329
|
+
const { readFileSync: readFileSync17 } = await import("fs");
|
|
4948
6330
|
const { fileURLToPath: fileURLToPath3 } = await import("url");
|
|
4949
6331
|
const p = await import("path");
|
|
4950
6332
|
const dir = p.dirname(fileURLToPath3(import.meta.url));
|
|
4951
|
-
const pkg = JSON.parse(
|
|
6333
|
+
const pkg = JSON.parse(readFileSync17(p.join(dir, "..", "package.json"), "utf-8"));
|
|
4952
6334
|
console.log(`codi v${pkg.version}`);
|
|
4953
6335
|
} catch {
|
|
4954
6336
|
console.log("codi v0.1.8");
|
|
@@ -5012,7 +6394,8 @@ async function main() {
|
|
|
5012
6394
|
taskUpdateTool,
|
|
5013
6395
|
taskListTool,
|
|
5014
6396
|
taskGetTool,
|
|
5015
|
-
askUserTool
|
|
6397
|
+
askUserTool,
|
|
6398
|
+
updateMemoryTool
|
|
5016
6399
|
]);
|
|
5017
6400
|
const subAgentHandler2 = createSubAgentHandler(provider, registry);
|
|
5018
6401
|
setSubAgentHandler(subAgentHandler2);
|
|
@@ -5055,10 +6438,11 @@ async function main() {
|
|
|
5055
6438
|
if (msg.role === "user") conversation.addUserMessage(msg.content);
|
|
5056
6439
|
else if (msg.role === "assistant") conversation.addAssistantMessage(msg.content);
|
|
5057
6440
|
}
|
|
5058
|
-
console.log(
|
|
6441
|
+
console.log(chalk14.dim(`Resumed session: ${id}`));
|
|
5059
6442
|
}
|
|
5060
6443
|
}
|
|
5061
6444
|
}
|
|
6445
|
+
logger.info("\uC138\uC158 \uC2DC\uC791", { provider: providerName, model: modelName, cwd: process.cwd(), planMode: getMode() === "plan", yolo: !!args.yolo });
|
|
5062
6446
|
await hookManager.runHooks("SessionStart", { cwd: process.cwd() });
|
|
5063
6447
|
if (args.prompt) {
|
|
5064
6448
|
await agentLoop(args.prompt, {
|
|
@@ -5073,6 +6457,7 @@ async function main() {
|
|
|
5073
6457
|
},
|
|
5074
6458
|
planMode: getMode() === "plan"
|
|
5075
6459
|
});
|
|
6460
|
+
logger.info("\uC138\uC158 \uC885\uB8CC (single prompt)");
|
|
5076
6461
|
await hookManager.runHooks("SessionEnd", {});
|
|
5077
6462
|
await mcpManager.disconnectAll();
|
|
5078
6463
|
process.exit(0);
|
|
@@ -5084,7 +6469,7 @@ async function main() {
|
|
|
5084
6469
|
compressor,
|
|
5085
6470
|
exitFn: async () => {
|
|
5086
6471
|
stopSpinner();
|
|
5087
|
-
console.log(
|
|
6472
|
+
console.log(chalk14.dim("\nSaving session..."));
|
|
5088
6473
|
sessionManager.save(conversation, void 0, provider.model);
|
|
5089
6474
|
await hookManager.runHooks("SessionEnd", {});
|
|
5090
6475
|
await mcpManager.disconnectAll();
|
|
@@ -5114,7 +6499,7 @@ async function main() {
|
|
|
5114
6499
|
const preview = typeof message === "string" ? message.slice(0, 50) : message.find((b) => b.type === "text")?.text?.slice(0, 50) || "image";
|
|
5115
6500
|
checkpointManager.create(conversation, preview);
|
|
5116
6501
|
if (compressor.shouldCompress(conversation)) {
|
|
5117
|
-
console.log(
|
|
6502
|
+
console.log(chalk14.dim("Auto-compacting conversation..."));
|
|
5118
6503
|
await compressor.compress(conversation, provider);
|
|
5119
6504
|
conversation.setSystemPrompt(buildPrompt());
|
|
5120
6505
|
}
|
|
@@ -5143,14 +6528,17 @@ async function main() {
|
|
|
5143
6528
|
},
|
|
5144
6529
|
onExit: async () => {
|
|
5145
6530
|
stopSpinner();
|
|
5146
|
-
|
|
6531
|
+
logger.info("\uC138\uC158 \uC885\uB8CC (REPL exit)");
|
|
6532
|
+
console.log(chalk14.dim("\nSaving session..."));
|
|
5147
6533
|
sessionManager.save(conversation, void 0, provider.model);
|
|
6534
|
+
checkpointManager.cleanup();
|
|
5148
6535
|
await hookManager.runHooks("SessionEnd", {});
|
|
5149
6536
|
await mcpManager.disconnectAll();
|
|
5150
6537
|
}
|
|
5151
6538
|
});
|
|
5152
6539
|
process.on("SIGTERM", async () => {
|
|
5153
6540
|
stopSpinner();
|
|
6541
|
+
logger.info("\uC138\uC158 \uC885\uB8CC (SIGTERM)");
|
|
5154
6542
|
sessionManager.save(conversation, void 0, provider.model);
|
|
5155
6543
|
await hookManager.runHooks("SessionEnd", {});
|
|
5156
6544
|
await mcpManager.disconnectAll();
|
|
@@ -5159,7 +6547,8 @@ async function main() {
|
|
|
5159
6547
|
await repl.start();
|
|
5160
6548
|
}
|
|
5161
6549
|
main().catch((err) => {
|
|
5162
|
-
|
|
6550
|
+
logger.error("\uCE58\uBA85\uC801 \uC624\uB958", {}, err instanceof Error ? err : new Error(String(err)));
|
|
6551
|
+
console.error(chalk14.red(`Fatal error: ${err.message}`));
|
|
5163
6552
|
console.error(err.stack);
|
|
5164
6553
|
process.exit(1);
|
|
5165
6554
|
});
|