@takimcizgisi/ajan 0.2.5
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/LICENSE +674 -0
- package/README.md +366 -0
- package/config/models.json +54 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +741 -0
- package/dist/cli/tui.d.ts +145 -0
- package/dist/cli/tui.js +1003 -0
- package/dist/config/configManager.d.ts +18 -0
- package/dist/config/configManager.js +204 -0
- package/dist/config/types.d.ts +98 -0
- package/dist/config/types.js +2 -0
- package/dist/core/agent.d.ts +88 -0
- package/dist/core/agent.js +222 -0
- package/dist/electron/main.d.ts +1 -0
- package/dist/electron/main.js +364 -0
- package/dist/electron/preload.d.ts +1 -0
- package/dist/electron/preload.js +67 -0
- package/dist/engine/llama.d.ts +39 -0
- package/dist/engine/llama.js +146 -0
- package/dist/engine/modelManager.d.ts +18 -0
- package/dist/engine/modelManager.js +73 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/service.d.ts +90 -0
- package/dist/service.js +268 -0
- package/dist/tools/data.d.ts +3 -0
- package/dist/tools/data.js +111 -0
- package/dist/tools/edit.d.ts +2 -0
- package/dist/tools/edit.js +48 -0
- package/dist/tools/file.d.ts +6 -0
- package/dist/tools/file.js +274 -0
- package/dist/tools/filesys.d.ts +3 -0
- package/dist/tools/filesys.js +74 -0
- package/dist/tools/memory.d.ts +3 -0
- package/dist/tools/memory.js +69 -0
- package/dist/tools/patch.d.ts +15 -0
- package/dist/tools/patch.js +129 -0
- package/dist/tools/registry.d.ts +10 -0
- package/dist/tools/registry.js +64 -0
- package/dist/tools/taskComplete.d.ts +2 -0
- package/dist/tools/taskComplete.js +19 -0
- package/dist/tools/terminal.d.ts +2 -0
- package/dist/tools/terminal.js +130 -0
- package/dist/tools/utils.d.ts +7 -0
- package/dist/tools/utils.js +63 -0
- package/dist/tools/web.d.ts +8 -0
- package/dist/tools/web.js +181 -0
- package/dist/utils/clipboard.d.ts +2 -0
- package/dist/utils/clipboard.js +40 -0
- package/dist/utils/lock.d.ts +12 -0
- package/dist/utils/lock.js +68 -0
- package/dist/utils/paths.d.ts +13 -0
- package/dist/utils/paths.js +58 -0
- package/dist/utils/projectContext.d.ts +2 -0
- package/dist/utils/projectContext.js +24 -0
- package/dist/utils/sacGuard.d.ts +4 -0
- package/dist/utils/sacGuard.js +47 -0
- package/dist/utils/sessions.d.ts +29 -0
- package/dist/utils/sessions.js +64 -0
- package/dist/utils/spinner.d.ts +10 -0
- package/dist/utils/spinner.js +32 -0
- package/package.json +106 -0
- package/src/gui/assets/image/AJAN_LOGO.svg +1 -0
- package/src/gui/assets/image/ajan.png +0 -0
- package/src/gui/assets/image/logo.svg +1 -0
- package/src/gui/assets/image/opencode.svg +18 -0
- package/src/gui/css/style.css +430 -0
- package/src/gui/html/chat.html +34 -0
- package/src/gui/html/downloads.html +11 -0
- package/src/gui/html/hakkinda.html +26 -0
- package/src/gui/html/index.html +82 -0
- package/src/gui/html/projects.html +12 -0
- package/src/gui/html/settings.html +4 -0
- package/src/gui/js/app.js +197 -0
- package/src/gui/js/chat.js +434 -0
- package/src/gui/js/downloads.js +172 -0
- package/src/gui/js/hakkinda.js +16 -0
- package/src/gui/js/projects.js +113 -0
- package/src/gui/js/settings.js +177 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, readdirSync, statSync, existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { join, dirname, relative } from "node:path";
|
|
3
|
+
import { limitOutput, resolveUserPath, shouldSkipDir } from "./utils.js";
|
|
4
|
+
function resolvePath(cwd, input) {
|
|
5
|
+
return resolveUserPath(cwd, input);
|
|
6
|
+
}
|
|
7
|
+
function globToRegExp(glob) {
|
|
8
|
+
const escaped = glob
|
|
9
|
+
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
10
|
+
.replace(/\*\*/g, "@@DOUBLE_STAR@@")
|
|
11
|
+
.replace(/\*/g, "[^/]*")
|
|
12
|
+
.replace(/\?/g, "[^/]")
|
|
13
|
+
.replace(/@@DOUBLE_STAR@@/g, ".*");
|
|
14
|
+
return new RegExp(`^${escaped}$`);
|
|
15
|
+
}
|
|
16
|
+
function normalizeLines(text) {
|
|
17
|
+
return text.replace(/\r\n/g, "\n");
|
|
18
|
+
}
|
|
19
|
+
export const readFileTool = {
|
|
20
|
+
name: "read_file",
|
|
21
|
+
description: "Bir dosyanın içeriğini okur. Büyük dosyalar için offset/limit ile kısmi okuma yapılabilir.",
|
|
22
|
+
parameters: {
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: {
|
|
25
|
+
path: { type: "string", description: "Okunacak dosyanın yolu (cwd'ye göre göreli veya mutlak)" },
|
|
26
|
+
offset: { type: "integer", description: "Başlangıç satır numarası (1 tabanlı)" },
|
|
27
|
+
limit: { type: "integer", description: "Okunacak maksimum satır sayısı" }
|
|
28
|
+
},
|
|
29
|
+
required: ["path"]
|
|
30
|
+
},
|
|
31
|
+
handler: async (params, ctx) => {
|
|
32
|
+
const filePath = resolvePath(ctx.cwd, params.path);
|
|
33
|
+
if (!existsSync(filePath)) {
|
|
34
|
+
return { ok: false, output: `Dosya bulunamadı: ${params.path}` };
|
|
35
|
+
}
|
|
36
|
+
const stat = statSync(filePath);
|
|
37
|
+
if (!stat.isFile()) {
|
|
38
|
+
return { ok: false, output: `Bu bir dosya değil: ${params.path}` };
|
|
39
|
+
}
|
|
40
|
+
if (stat.size > 2 * 1024 * 1024 && !params.offset && !params.limit) {
|
|
41
|
+
return {
|
|
42
|
+
ok: false,
|
|
43
|
+
output: `Dosya çok büyük (${stat.size} bayt). offset/limit parametreleriyle kısmi okuma yapın.`
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const content = normalizeLines(readFileSync(filePath, "utf8"));
|
|
47
|
+
const lines = content.split("\n");
|
|
48
|
+
const totalLines = lines.length;
|
|
49
|
+
const start = params.offset ? Math.max(1, params.offset) : 1;
|
|
50
|
+
const end = params.limit ? Math.min(totalLines, start + params.limit - 1) : totalLines;
|
|
51
|
+
const slice = lines.slice(start - 1, end);
|
|
52
|
+
const numbered = slice.map((line, i) => `${String(start + i).padStart(6)} | ${line}`).join("\n");
|
|
53
|
+
const header = `Dosya: ${filePath}\nSatırlar: ${start}-${end} / ${totalLines}\n`;
|
|
54
|
+
return { ok: true, output: header + numbered };
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
export const writeFileTool = {
|
|
58
|
+
name: "write_file",
|
|
59
|
+
description: "Bir dosyaya içerik yazar (oluşturur veya üzerine yazar). append=true ise sona ekler. " +
|
|
60
|
+
"Klasörler otomatik oluşturulur.",
|
|
61
|
+
parameters: {
|
|
62
|
+
type: "object",
|
|
63
|
+
properties: {
|
|
64
|
+
path: { type: "string", description: "Yazılacak dosyanın yolu" },
|
|
65
|
+
content: { type: "string", description: "Dosya içeriği" },
|
|
66
|
+
append: { type: "boolean", description: "true ise içeriği dosyanın sonuna ekler (varsayılan: false)" }
|
|
67
|
+
},
|
|
68
|
+
required: ["path", "content"]
|
|
69
|
+
},
|
|
70
|
+
handler: async (params, ctx) => {
|
|
71
|
+
const filePath = resolvePath(ctx.cwd, params.path);
|
|
72
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
73
|
+
const content = normalizeLines(params.content);
|
|
74
|
+
writeFileSync(filePath, content, { encoding: "utf8", flag: params.append ? "a" : "w" });
|
|
75
|
+
const action = params.append ? "eklendi" : "yazıldı";
|
|
76
|
+
return { ok: true, output: `Dosya ${action}: ${filePath} (${Buffer.byteLength(content)} bayt)` };
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
export const listDirTool = {
|
|
80
|
+
name: "list_dir",
|
|
81
|
+
description: "Bir dizindeki dosya ve klasörleri listeler. recursive=true ile alt dizinler de dahil edilir.",
|
|
82
|
+
parameters: {
|
|
83
|
+
type: "object",
|
|
84
|
+
properties: {
|
|
85
|
+
path: { type: "string", description: "Listelenecek dizin (varsayılan: cwd)" },
|
|
86
|
+
recursive: { type: "boolean", description: "Alt dizinleri de listele (varsayılan: false)" }
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
handler: async (params, ctx) => {
|
|
90
|
+
const dirPath = resolvePath(ctx.cwd, params.path ?? ".");
|
|
91
|
+
if (!existsSync(dirPath)) {
|
|
92
|
+
return { ok: false, output: `Dizin bulunamadı: ${params.path ?? "."}` };
|
|
93
|
+
}
|
|
94
|
+
if (!statSync(dirPath).isDirectory()) {
|
|
95
|
+
return { ok: false, output: `Bu bir dizin değil: ${params.path ?? "."}` };
|
|
96
|
+
}
|
|
97
|
+
const entries = [];
|
|
98
|
+
let skippedCount = 0;
|
|
99
|
+
const MAX_ENTRIES = 300;
|
|
100
|
+
const walk = (current, depth) => {
|
|
101
|
+
if (depth > 5 || entries.length >= MAX_ENTRIES)
|
|
102
|
+
return;
|
|
103
|
+
let children;
|
|
104
|
+
try {
|
|
105
|
+
children = readdirSync(current, { withFileTypes: true });
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
entries.push(` ${" ".repeat(depth)}[hata: ${err.message}]`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
for (const child of children) {
|
|
112
|
+
if (entries.length >= MAX_ENTRIES)
|
|
113
|
+
break;
|
|
114
|
+
const childPath = join(current, child.name);
|
|
115
|
+
const isDir = child.isDirectory();
|
|
116
|
+
if (isDir && shouldSkipDir(child.name)) {
|
|
117
|
+
skippedCount++;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const indent = " ".repeat(depth);
|
|
121
|
+
const rel = relative(dirPath, childPath).replace(/\\/g, "/");
|
|
122
|
+
if (isDir) {
|
|
123
|
+
entries.push(`${indent}${rel}/`);
|
|
124
|
+
if (params.recursive)
|
|
125
|
+
walk(childPath, depth + 1);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
let size = 0;
|
|
129
|
+
try {
|
|
130
|
+
size = statSync(childPath).size;
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
size = 0;
|
|
134
|
+
}
|
|
135
|
+
entries.push(`${indent}${rel} (${size} bayt)`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
walk(dirPath, 0);
|
|
140
|
+
let output = `Dizin: ${dirPath}\n${entries.join("\n")}`;
|
|
141
|
+
if (skippedCount > 0)
|
|
142
|
+
output += `\n(... ${skippedCount} ağır/gizli klasör atlandı: node_modules, .git, dist, build vb.)`;
|
|
143
|
+
if (entries.length >= MAX_ENTRIES)
|
|
144
|
+
output += `\n(... ${entries.length} öğe sınırına ulaşıldı)`;
|
|
145
|
+
return { ok: true, output: limitOutput(output, ctx.config.maxToolOutput) };
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
export const searchFilesTool = {
|
|
149
|
+
name: "search_files",
|
|
150
|
+
description: "Bir dizindeki dosyaları isim desenine (glob) göre arar.",
|
|
151
|
+
parameters: {
|
|
152
|
+
type: "object",
|
|
153
|
+
properties: {
|
|
154
|
+
path: { type: "string", description: "Aranacak dizin (varsayılan: cwd)" },
|
|
155
|
+
pattern: { type: "string", description: "Glob deseni, ör. '*.ts' veya '**/*.ts'" }
|
|
156
|
+
},
|
|
157
|
+
required: ["pattern"]
|
|
158
|
+
},
|
|
159
|
+
handler: async (params, ctx) => {
|
|
160
|
+
const dirPath = resolvePath(ctx.cwd, params.path ?? ".");
|
|
161
|
+
const matches = [];
|
|
162
|
+
const pattern = params.pattern.replace(/^\/+/, "").replace(/\\/g, "/");
|
|
163
|
+
const regex = globToRegExp(pattern);
|
|
164
|
+
const walk = (current, depth) => {
|
|
165
|
+
if (depth > 8)
|
|
166
|
+
return;
|
|
167
|
+
let children;
|
|
168
|
+
try {
|
|
169
|
+
children = readdirSync(current, { withFileTypes: true });
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
for (const child of children) {
|
|
175
|
+
const childPath = join(current, child.name);
|
|
176
|
+
const rel = relative(dirPath, childPath).replace(/\\/g, "/");
|
|
177
|
+
if (child.isDirectory()) {
|
|
178
|
+
if (shouldSkipDir(child.name))
|
|
179
|
+
continue;
|
|
180
|
+
if (regex.test(`${rel}/`))
|
|
181
|
+
matches.push(`${rel}/`);
|
|
182
|
+
walk(childPath, depth + 1);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
if (regex.test(rel))
|
|
186
|
+
matches.push(rel);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
walk(dirPath, 0);
|
|
191
|
+
const result = matches.length > 0 ? matches.join("\n") : "Eşleşen dosya bulunamadı.";
|
|
192
|
+
return { ok: true, output: `Glob: ${params.pattern}\n${result}` };
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
export const grepTool = {
|
|
196
|
+
name: "grep_files",
|
|
197
|
+
description: "Dosyalarda içerik araması yapar. pattern bir regex'dir.",
|
|
198
|
+
parameters: {
|
|
199
|
+
type: "object",
|
|
200
|
+
properties: {
|
|
201
|
+
path: { type: "string", description: "Aranacak dizin (varsayılan: cwd)" },
|
|
202
|
+
pattern: { type: "string", description: "Regex deseni" },
|
|
203
|
+
include: { type: "string", description: "Dosya deseni filtresi, ör. '*.ts'" }
|
|
204
|
+
},
|
|
205
|
+
required: ["pattern"]
|
|
206
|
+
},
|
|
207
|
+
handler: async (params, ctx) => {
|
|
208
|
+
const dirPath = resolvePath(ctx.cwd, params.path ?? ".");
|
|
209
|
+
let regex;
|
|
210
|
+
try {
|
|
211
|
+
regex = new RegExp(params.pattern);
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
return { ok: false, output: `Geçersiz regex deseni: ${err.message}` };
|
|
215
|
+
}
|
|
216
|
+
let includeRe = null;
|
|
217
|
+
if (params.include) {
|
|
218
|
+
try {
|
|
219
|
+
includeRe = new RegExp(`^${params.include.replace(/\./g, "\\.").replace(/\*/g, ".*")}$`);
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
includeRe = null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const results = [];
|
|
226
|
+
let fileCount = 0;
|
|
227
|
+
const walk = (current, depth) => {
|
|
228
|
+
if (depth > 8 || fileCount > 500)
|
|
229
|
+
return;
|
|
230
|
+
let children;
|
|
231
|
+
try {
|
|
232
|
+
children = readdirSync(current, { withFileTypes: true });
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
for (const child of children) {
|
|
238
|
+
if (shouldSkipDir(child.name))
|
|
239
|
+
continue;
|
|
240
|
+
const childPath = join(current, child.name);
|
|
241
|
+
if (child.isDirectory()) {
|
|
242
|
+
walk(childPath, depth + 1);
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
if (includeRe && !includeRe.test(child.name))
|
|
246
|
+
continue;
|
|
247
|
+
fileCount++;
|
|
248
|
+
try {
|
|
249
|
+
if (statSync(childPath).size > 2 * 1024 * 1024)
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
try {
|
|
256
|
+
const lines = normalizeLines(readFileSync(childPath, "utf8")).split("\n");
|
|
257
|
+
for (let i = 0; i < lines.length; i++) {
|
|
258
|
+
const line = lines[i] ?? "";
|
|
259
|
+
if (regex.test(line)) {
|
|
260
|
+
results.push(`${relative(dirPath, childPath).replace(/\\/g, "/")}:${i + 1}: ${line.trim().slice(0, 200)}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
/* okunamayan dosyayı atla */
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
walk(dirPath, 0);
|
|
271
|
+
return { ok: true, output: limitOutput(results.join("\n"), ctx.config.maxToolOutput) };
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
//# sourceMappingURL=file.js.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, renameSync, rmSync, statSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { resolveUserPath } from "./utils.js";
|
|
4
|
+
export const moveFileTool = {
|
|
5
|
+
name: "move_file",
|
|
6
|
+
description: "Dosya veya klasörü taşır / yeniden adlandırır. Hedef dizin yoksa oluşturulur. Var olan dosyanın üzerine yazmaz.",
|
|
7
|
+
parameters: {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: {
|
|
10
|
+
source: { type: "string", description: "Kaynak yol" },
|
|
11
|
+
destination: { type: "string", description: "Hedef yol (dosya veya klasör)" }
|
|
12
|
+
},
|
|
13
|
+
required: ["source", "destination"]
|
|
14
|
+
},
|
|
15
|
+
handler: async (params, ctx) => {
|
|
16
|
+
const src = params.source?.trim();
|
|
17
|
+
const dest = params.destination?.trim();
|
|
18
|
+
if (!src || !dest)
|
|
19
|
+
return { ok: false, output: "source ve destination zorunludur." };
|
|
20
|
+
const srcPath = resolveUserPath(ctx.cwd, src);
|
|
21
|
+
const destPath = resolveUserPath(ctx.cwd, dest);
|
|
22
|
+
if (srcPath === destPath)
|
|
23
|
+
return { ok: false, output: "Kaynak ve hedef aynı." };
|
|
24
|
+
if (!existsSync(srcPath))
|
|
25
|
+
return { ok: false, output: `Kaynak bulunamadı: ${src}` };
|
|
26
|
+
if (existsSync(destPath))
|
|
27
|
+
return { ok: false, output: `Hedef zaten var: ${dest} (önce silin)` };
|
|
28
|
+
try {
|
|
29
|
+
mkdirSync(dirname(destPath), { recursive: true });
|
|
30
|
+
renameSync(srcPath, destPath);
|
|
31
|
+
return { ok: true, output: `Taşındı: ${srcPath} → ${destPath}` };
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
return { ok: false, output: `Taşıma başarısız: ${err.message}` };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
export const deleteFileTool = {
|
|
39
|
+
name: "delete_file",
|
|
40
|
+
description: "Dosya veya klasör siler. Klasörler için recursive:true gerekir. Dikkatli kullan — geri alınamaz.",
|
|
41
|
+
parameters: {
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {
|
|
44
|
+
path: { type: "string", description: "Silinecek yol" },
|
|
45
|
+
recursive: { type: "boolean", description: "Klasörü içeriğiyle sil (varsayılan: false)" }
|
|
46
|
+
},
|
|
47
|
+
required: ["path"]
|
|
48
|
+
},
|
|
49
|
+
handler: async (params, ctx) => {
|
|
50
|
+
const p = params.path?.trim();
|
|
51
|
+
if (!p)
|
|
52
|
+
return { ok: false, output: "path zorunludur." };
|
|
53
|
+
const target = resolveUserPath(ctx.cwd, p);
|
|
54
|
+
if (!existsSync(target))
|
|
55
|
+
return { ok: false, output: `Bulunamadı: ${p}` };
|
|
56
|
+
const cwdResolved = resolve(ctx.cwd);
|
|
57
|
+
if (target === cwdResolved || cwdResolved.startsWith(target + "\\") || cwdResolved.startsWith(target + "/")) {
|
|
58
|
+
return { ok: false, output: "Çalışma dizinini veya üstünü silemezsin." };
|
|
59
|
+
}
|
|
60
|
+
const stat = statSync(target);
|
|
61
|
+
const isDir = stat.isDirectory();
|
|
62
|
+
if (isDir && !params.recursive) {
|
|
63
|
+
return { ok: false, output: `"${p}" bir klasör; silmek için recursive:true ver.` };
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
rmSync(target, { recursive: isDir, force: true });
|
|
67
|
+
return { ok: true, output: `Silindi: ${target}${isDir ? " (klasör)" : ""}` };
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
return { ok: false, output: `Silme başarısız: ${err.message}` };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=filesys.js.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { limitOutput } from "./utils.js";
|
|
4
|
+
function notesPath() {
|
|
5
|
+
const base = process.env.AJAN_HOME ?? join(process.env.USERPROFILE ?? process.env.HOME ?? ".", ".ajan");
|
|
6
|
+
return join(base, "notes.json");
|
|
7
|
+
}
|
|
8
|
+
function loadNotes() {
|
|
9
|
+
try {
|
|
10
|
+
if (existsSync(notesPath())) {
|
|
11
|
+
const parsed = JSON.parse(readFileSync(notesPath(), "utf8"));
|
|
12
|
+
return { notes: Array.isArray(parsed.notes) ? parsed.notes : [] };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
/* bozuk dosya: sıfırla */
|
|
17
|
+
}
|
|
18
|
+
return { notes: [] };
|
|
19
|
+
}
|
|
20
|
+
function saveNotes(data) {
|
|
21
|
+
writeFileSync(notesPath(), JSON.stringify(data, null, 2), "utf8");
|
|
22
|
+
}
|
|
23
|
+
export const noteAddTool = {
|
|
24
|
+
name: "not_al",
|
|
25
|
+
description: "Kalıcı hafızaya not alır. Kullanıcı tercihleri, proje kararları, önemli bilgiler için kullan. " +
|
|
26
|
+
"Notlar oturumlar arasında korunur (~/.ajan/notes.json).",
|
|
27
|
+
parameters: {
|
|
28
|
+
type: "object",
|
|
29
|
+
properties: {
|
|
30
|
+
text: { type: "string", description: "Kaydedilecek not metni" }
|
|
31
|
+
},
|
|
32
|
+
required: ["text"]
|
|
33
|
+
},
|
|
34
|
+
handler: async (params) => {
|
|
35
|
+
const text = (params.text ?? "").trim();
|
|
36
|
+
if (!text)
|
|
37
|
+
return { ok: false, output: "Not metni boş olamaz." };
|
|
38
|
+
const data = loadNotes();
|
|
39
|
+
const id = data.notes.reduce((max, n) => Math.max(max, n.id), 0) + 1;
|
|
40
|
+
data.notes.push({ id, text, createdAt: new Date().toISOString() });
|
|
41
|
+
saveNotes(data);
|
|
42
|
+
return { ok: true, output: `Not #${id} kaydedildi.` };
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
export const noteReadTool = {
|
|
46
|
+
name: "not_oku",
|
|
47
|
+
description: "Kalıcı hafızadaki notları okur. query verilirse içinde o kelime geçen notlar döner; yoksa son 30 not.",
|
|
48
|
+
parameters: {
|
|
49
|
+
type: "object",
|
|
50
|
+
properties: {
|
|
51
|
+
query: { type: "string", description: "Notlar içinde aranacak kelime (opsiyonel)" }
|
|
52
|
+
},
|
|
53
|
+
required: []
|
|
54
|
+
},
|
|
55
|
+
handler: async (params, ctx) => {
|
|
56
|
+
const data = loadNotes();
|
|
57
|
+
let notes = data.notes;
|
|
58
|
+
const q = params.query?.trim().toLowerCase();
|
|
59
|
+
if (q)
|
|
60
|
+
notes = notes.filter((n) => n.text.toLowerCase().includes(q));
|
|
61
|
+
if (notes.length === 0)
|
|
62
|
+
return { ok: true, output: q ? `"${params.query}" ile eşleşen not yok.` : "Hafızada not yok." };
|
|
63
|
+
const slice = notes.slice(-30);
|
|
64
|
+
const lines = slice.map((n) => `#${n.id} [${n.createdAt.slice(0, 10)}] ${n.text}`);
|
|
65
|
+
const header = `${notes.length} not${q ? ` ("${params.query}")` : ""}${notes.length > slice.length ? ` (son ${slice.length} gösteriliyor)` : ""}:\n`;
|
|
66
|
+
return { ok: true, output: limitOutput(header + lines.join("\n"), ctx.config.maxToolOutput) };
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AgentTool } from "../config/types.js";
|
|
2
|
+
type Hunk = {
|
|
3
|
+
oldStart: number;
|
|
4
|
+
newStart: number;
|
|
5
|
+
lines: {
|
|
6
|
+
type: " " | "+" | "-";
|
|
7
|
+
text: string;
|
|
8
|
+
}[];
|
|
9
|
+
};
|
|
10
|
+
declare function parseUnifiedDiff(diff: string): {
|
|
11
|
+
file: string;
|
|
12
|
+
hunks: Hunk[];
|
|
13
|
+
} | null;
|
|
14
|
+
export declare const applyPatchTool: AgentTool;
|
|
15
|
+
export { parseUnifiedDiff };
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { logger } from "../utils/paths.js";
|
|
3
|
+
import { resolveUserPath } from "./utils.js";
|
|
4
|
+
function resolvePath(cwd, input) {
|
|
5
|
+
return resolveUserPath(cwd, input);
|
|
6
|
+
}
|
|
7
|
+
function parseUnifiedDiff(diff) {
|
|
8
|
+
const lines = diff.replace(/\r\n/g, "\n").split("\n");
|
|
9
|
+
let file;
|
|
10
|
+
const hunks = [];
|
|
11
|
+
let current;
|
|
12
|
+
for (const rawLine of lines) {
|
|
13
|
+
if (rawLine.startsWith("--- ")) {
|
|
14
|
+
file = rawLine.slice(4).replace(/^\s*[ab]\//, "").trim();
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
if (rawLine.startsWith("+++ "))
|
|
18
|
+
continue;
|
|
19
|
+
const hunkMatch = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/.exec(rawLine);
|
|
20
|
+
if (hunkMatch) {
|
|
21
|
+
current = {
|
|
22
|
+
oldStart: parseInt(hunkMatch[1] ?? "1", 10),
|
|
23
|
+
newStart: parseInt(hunkMatch[3] ?? "1", 10),
|
|
24
|
+
lines: []
|
|
25
|
+
};
|
|
26
|
+
hunks.push(current);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (!current)
|
|
30
|
+
continue;
|
|
31
|
+
if (rawLine.startsWith("+")) {
|
|
32
|
+
current.lines.push({ type: "+", text: rawLine.slice(1) });
|
|
33
|
+
}
|
|
34
|
+
else if (rawLine.startsWith("-")) {
|
|
35
|
+
current.lines.push({ type: "-", text: rawLine.slice(1) });
|
|
36
|
+
}
|
|
37
|
+
else if (rawLine.startsWith(" ")) {
|
|
38
|
+
current.lines.push({ type: " ", text: rawLine.slice(1) });
|
|
39
|
+
}
|
|
40
|
+
else if (rawLine.startsWith("\\")) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (!file || hunks.length === 0)
|
|
45
|
+
return null;
|
|
46
|
+
return { file, hunks };
|
|
47
|
+
}
|
|
48
|
+
function applyHunks(original, hunks) {
|
|
49
|
+
const lines = original.split("\n");
|
|
50
|
+
const newLines = [];
|
|
51
|
+
let oldLine = 1;
|
|
52
|
+
let changedLines = 0;
|
|
53
|
+
for (const hunk of hunks) {
|
|
54
|
+
while (oldLine < hunk.oldStart && oldLine <= lines.length) {
|
|
55
|
+
newLines.push(lines[oldLine - 1] ?? "");
|
|
56
|
+
oldLine++;
|
|
57
|
+
}
|
|
58
|
+
let oldPos = hunk.oldStart;
|
|
59
|
+
let contextMatched = true;
|
|
60
|
+
const contextCheck = hunk.lines.filter((l) => l.type !== "+");
|
|
61
|
+
for (const cl of contextCheck) {
|
|
62
|
+
const current = lines[oldPos - 1];
|
|
63
|
+
if (current !== cl.text) {
|
|
64
|
+
contextMatched = false;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
oldPos++;
|
|
68
|
+
}
|
|
69
|
+
if (!contextMatched) {
|
|
70
|
+
throw new Error(`Hunk satırları eşleşmiyor (${hunk.oldStart}. satır civarı). Dosya içeriği güncelliğini yitirmiş olabilir.`);
|
|
71
|
+
}
|
|
72
|
+
for (const line of hunk.lines) {
|
|
73
|
+
if (line.type === "-") {
|
|
74
|
+
oldLine++;
|
|
75
|
+
changedLines++;
|
|
76
|
+
}
|
|
77
|
+
else if (line.type === "+") {
|
|
78
|
+
newLines.push(line.text);
|
|
79
|
+
changedLines++;
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
newLines.push(line.text);
|
|
83
|
+
oldLine++;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
oldPos = 0;
|
|
87
|
+
}
|
|
88
|
+
while (oldLine <= lines.length) {
|
|
89
|
+
newLines.push(lines[oldLine - 1] ?? "");
|
|
90
|
+
oldLine++;
|
|
91
|
+
}
|
|
92
|
+
return { result: newLines.join("\n"), changedLines };
|
|
93
|
+
}
|
|
94
|
+
export const applyPatchTool = {
|
|
95
|
+
name: "apply_patch",
|
|
96
|
+
description: "Unified diff formatında bir yamayı dosyaya uygular. Eksiksiz dosya değişiklikleri için write_file'ı tercih edin. " +
|
|
97
|
+
"Yama formatı: --- a/yol + +++ b/yol + @@ -eskiSatır,sayı +yeniSatır,sayı @@ + satırlar. " +
|
|
98
|
+
"Küçük ve hedefli değişikliklerde daha güvenilirdir.",
|
|
99
|
+
parameters: {
|
|
100
|
+
type: "object",
|
|
101
|
+
properties: {
|
|
102
|
+
patch: { type: "string", description: "Uygulanacak unified diff içeriği" },
|
|
103
|
+
cwd: { type: "string", description: "Göreli yolların çözüleceği dizin" }
|
|
104
|
+
},
|
|
105
|
+
required: ["patch"]
|
|
106
|
+
},
|
|
107
|
+
handler: async (params, ctx) => {
|
|
108
|
+
const parsed = parseUnifiedDiff(params.patch);
|
|
109
|
+
if (!parsed) {
|
|
110
|
+
return { ok: false, output: "Geçerli bir unified diff bulunamadı. --- a/yol ve @@ hunk satırları gerekli." };
|
|
111
|
+
}
|
|
112
|
+
const filePath = resolvePath(params.cwd ?? ctx.cwd, parsed.file);
|
|
113
|
+
if (!existsSync(filePath)) {
|
|
114
|
+
return { ok: false, output: `Yamalanacak dosya bulunamadı: ${parsed.file}` };
|
|
115
|
+
}
|
|
116
|
+
const original = readFileSync(filePath, "utf8");
|
|
117
|
+
try {
|
|
118
|
+
const { result, changedLines } = applyHunks(original, parsed.hunks);
|
|
119
|
+
writeFileSync(filePath, result, "utf8");
|
|
120
|
+
logger.debug(`Patch uygulandi: ${filePath} (${changedLines} satir degisti)`);
|
|
121
|
+
return { ok: true, output: `Yama uygulandı: ${filePath} (${changedLines} satır değişti)` };
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
return { ok: false, output: `Yama uygulanamadı: ${err.message}` };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
export { parseUnifiedDiff };
|
|
129
|
+
//# sourceMappingURL=patch.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ChatSessionModelFunctions } from "node-llama-cpp";
|
|
2
|
+
import type { AgentTool, ToolExecutionContext, ToolExecutionSignal } from "../config/types.js";
|
|
3
|
+
export type ToolEvents = {
|
|
4
|
+
onToolCall?: (name: string, params: unknown) => void;
|
|
5
|
+
onToolResult?: (name: string, ok: boolean, output: string) => void;
|
|
6
|
+
};
|
|
7
|
+
export declare function createCoreTools(ctx: ToolExecutionContext): AgentTool[];
|
|
8
|
+
export declare function toolNames(): string[];
|
|
9
|
+
export declare function buildSessionFunctions(tools: AgentTool[], ctx: ToolExecutionContext, events?: ToolEvents): ChatSessionModelFunctions;
|
|
10
|
+
export type { ToolExecutionSignal };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { readFileTool, writeFileTool, listDirTool, searchFilesTool, grepTool } from "./file.js";
|
|
2
|
+
import { runCommandTool } from "./terminal.js";
|
|
3
|
+
import { applyPatchTool } from "./patch.js";
|
|
4
|
+
import { editFileTool } from "./edit.js";
|
|
5
|
+
import { webSearchTool, fetchUrlTool } from "./web.js";
|
|
6
|
+
import { moveFileTool, deleteFileTool } from "./filesys.js";
|
|
7
|
+
import { readDataTool, unzipFileTool } from "./data.js";
|
|
8
|
+
import { noteAddTool, noteReadTool } from "./memory.js";
|
|
9
|
+
import { taskCompleteTool } from "./taskComplete.js";
|
|
10
|
+
import { logger } from "../utils/paths.js";
|
|
11
|
+
export function createCoreTools(ctx) {
|
|
12
|
+
return [
|
|
13
|
+
readFileTool,
|
|
14
|
+
writeFileTool,
|
|
15
|
+
listDirTool,
|
|
16
|
+
searchFilesTool,
|
|
17
|
+
grepTool,
|
|
18
|
+
editFileTool,
|
|
19
|
+
moveFileTool,
|
|
20
|
+
deleteFileTool,
|
|
21
|
+
readDataTool,
|
|
22
|
+
unzipFileTool,
|
|
23
|
+
runCommandTool,
|
|
24
|
+
applyPatchTool,
|
|
25
|
+
webSearchTool,
|
|
26
|
+
fetchUrlTool,
|
|
27
|
+
noteAddTool,
|
|
28
|
+
noteReadTool,
|
|
29
|
+
taskCompleteTool
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
export function toolNames() {
|
|
33
|
+
return createCoreTools({ cwd: process.cwd(), config: { modelId: "x" } }).map((t) => t.name);
|
|
34
|
+
}
|
|
35
|
+
export function buildSessionFunctions(tools, ctx, events = {}) {
|
|
36
|
+
const result = {};
|
|
37
|
+
for (const tool of tools) {
|
|
38
|
+
result[tool.name] = {
|
|
39
|
+
description: tool.description,
|
|
40
|
+
params: tool.parameters,
|
|
41
|
+
handler: async (params) => {
|
|
42
|
+
events.onToolCall?.(tool.name, params);
|
|
43
|
+
logger.debug(`Arac cagirildi: ${tool.name}`);
|
|
44
|
+
try {
|
|
45
|
+
const res = await tool.handler(params, ctx);
|
|
46
|
+
events.onToolResult?.(tool.name, res.ok, res.output);
|
|
47
|
+
if (res.ok)
|
|
48
|
+
return res.output;
|
|
49
|
+
return (`HATA (${tool.name}): ${res.output} | ` +
|
|
50
|
+
"NOT: Araç başarısız oldu; bu işlem GERÇEKLEŞMEDİ. Hatayı kullanıcıya bildir veya sorunu çözüp tekrar dene. Başarılı diye raporlama.");
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
const msg = err.message;
|
|
54
|
+
const out = `HATA (${tool.name}): ${msg} | NOT: Araç hata verdi; işlem gerçekleşmedi. Başarılı diye raporlama.`;
|
|
55
|
+
logger.error(`Araç hatasi: ${tool.name}: ${msg}`);
|
|
56
|
+
events.onToolResult?.(tool.name, false, msg);
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const taskCompleteTool = {
|
|
2
|
+
name: "task_complete",
|
|
3
|
+
description: "Görevin TAMAMLANDIĞINI bildirir. Tüm istenen işler gerçekten bittiğinde ve doğrulandığında çağır. " +
|
|
4
|
+
"report parametresine yapılan işlerin özetini yaz.",
|
|
5
|
+
parameters: {
|
|
6
|
+
type: "object",
|
|
7
|
+
properties: {
|
|
8
|
+
report: { type: "string", description: "Tamamlanan işlerin kısa özeti" }
|
|
9
|
+
},
|
|
10
|
+
required: ["report"]
|
|
11
|
+
},
|
|
12
|
+
handler: async (params, ctx) => {
|
|
13
|
+
if (ctx.signals)
|
|
14
|
+
ctx.signals.taskCompleted = true;
|
|
15
|
+
const report = (params.report ?? "").trim();
|
|
16
|
+
return { ok: true, output: report || "Görev tamamlandı." };
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=taskComplete.js.map
|