@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,741 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { basename, join, resolve } from "node:path";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { AjanService } from "../service.js";
|
|
7
|
+
import { copyToClipboard } from "../utils/clipboard.js";
|
|
8
|
+
import { loadConfig, getModelById, isModelInstalled, listInstalledModels, saveConfig as saveConfigFile } from "../config/configManager.js";
|
|
9
|
+
import { setLogLevel, logger } from "../utils/paths.js";
|
|
10
|
+
import { acquireLock, releaseLock } from "../utils/lock.js";
|
|
11
|
+
import { getSacState, isSacBlocking, describeSacState } from "../utils/sacGuard.js";
|
|
12
|
+
import { ChatTui, CHAT_COMMANDS } from "./tui.js";
|
|
13
|
+
import { generateSessionId, loadSession, listSessions } from "../utils/sessions.js";
|
|
14
|
+
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
15
|
+
const VERSION = (() => {
|
|
16
|
+
try {
|
|
17
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, "..", "..", "package.json"), "utf8"));
|
|
18
|
+
return pkg.version ?? "0.0.0";
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return "0.0.0";
|
|
22
|
+
}
|
|
23
|
+
})();
|
|
24
|
+
async function launchGui() {
|
|
25
|
+
const lock = acquireLock("gui");
|
|
26
|
+
if (!lock.ok) {
|
|
27
|
+
console.log(lock.error);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
const projectRoot = join(__dirname, "..", "..");
|
|
31
|
+
const electronDist = join(projectRoot, "dist", "electron", "main.js");
|
|
32
|
+
let targetPath;
|
|
33
|
+
if (existsSync(electronDist)) {
|
|
34
|
+
targetPath = electronDist;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
console.log("Electron dosyasi bulunamadi. Once derleyin: npm run build:electron");
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
// electron.exe kontrol — yoksa otomatik kur
|
|
41
|
+
let electronExe = join(projectRoot, "node_modules", "electron", "dist", "electron.exe");
|
|
42
|
+
if (!existsSync(electronExe)) {
|
|
43
|
+
// Farklı platformlarda binary adı değişir
|
|
44
|
+
const electronBin = process.platform === "win32" ? "electron.exe" : "electron";
|
|
45
|
+
electronExe = join(projectRoot, "node_modules", "electron", "dist", electronBin);
|
|
46
|
+
if (!existsSync(electronExe)) {
|
|
47
|
+
console.log("Electron kuruluyor (ilk kullanımda bir kez indirilir)...");
|
|
48
|
+
const { execSync } = await import("node:child_process");
|
|
49
|
+
try {
|
|
50
|
+
execSync("npm install electron", {
|
|
51
|
+
cwd: projectRoot,
|
|
52
|
+
stdio: "inherit",
|
|
53
|
+
timeout: 120_000
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
console.log("Electron kurulamadi. Manuel kurun: npm install electron");
|
|
58
|
+
releaseLock();
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
// Kontrol et
|
|
62
|
+
if (!existsSync(electronExe)) {
|
|
63
|
+
console.log("Electron kuruldu ama binary bulunamadi. Yeniden deneyin.");
|
|
64
|
+
releaseLock();
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const args = [targetPath];
|
|
70
|
+
const { spawn } = await import("node:child_process");
|
|
71
|
+
const child = spawn(electronExe, args, {
|
|
72
|
+
detached: true,
|
|
73
|
+
stdio: "ignore",
|
|
74
|
+
cwd: projectRoot,
|
|
75
|
+
env: { ...process.env },
|
|
76
|
+
windowsHide: false
|
|
77
|
+
});
|
|
78
|
+
child.unref();
|
|
79
|
+
console.log("AJAN GUI baslatildi. Bu pencereyi kapatabilirsiniz.");
|
|
80
|
+
releaseLock();
|
|
81
|
+
process.exit(0);
|
|
82
|
+
}
|
|
83
|
+
export function generateTitle(text) {
|
|
84
|
+
const clean = text
|
|
85
|
+
.replace(/\[(PLAN|BUILD|DEVAMLILIK|CHAT) MODU\]\s*/gi, "")
|
|
86
|
+
.replace(/[*_`#>|~]/g, "")
|
|
87
|
+
.replace(/\s+/g, " ")
|
|
88
|
+
.trim();
|
|
89
|
+
if (clean.length < 5)
|
|
90
|
+
return "Yeni Sohbet";
|
|
91
|
+
const firstSentence = (clean.split(/[.;!?]/)[0] ?? clean).trim();
|
|
92
|
+
const title = firstSentence.slice(0, 32).trim();
|
|
93
|
+
const greeting = /\b(selam|merhaba|hey|hi|hello|günaydın|günaydin|iyi akşamlar|iyi aksamlar|nasılsın|nasilsin|teşekkürler|tesekkurler|sağol|sagol)\b/i;
|
|
94
|
+
return title.length >= 8 && !greeting.test(title) ? title : "Yeni Sohbet";
|
|
95
|
+
}
|
|
96
|
+
function handleTodoCommand(arg, tui) {
|
|
97
|
+
const parts = arg.split(/\s+/);
|
|
98
|
+
const sub = (parts[0] ?? "").toLowerCase();
|
|
99
|
+
if (sub === "ekle" || sub === "add") {
|
|
100
|
+
const text = parts.slice(1).join(" ").trim();
|
|
101
|
+
if (!text) {
|
|
102
|
+
tui.addInfo("Kullanım: /todo ekle <metin>");
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
tui.addTodo(text);
|
|
106
|
+
tui.addInfo(`[görev] eklendi: ${text}`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (sub === "sil" || sub === "remove" || sub === "del") {
|
|
110
|
+
const n = Number(parts[1]);
|
|
111
|
+
if (!Number.isInteger(n) || n < 1 || n > tui.getTodos().length) {
|
|
112
|
+
tui.addInfo("Geçersiz görev numarası. /todo list ile bakın.");
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
tui.removeTodo(n - 1);
|
|
116
|
+
tui.addInfo(`[görev] ${n} silindi.`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (sub === "temizle" || sub === "clear") {
|
|
120
|
+
tui.clearTodos();
|
|
121
|
+
tui.addInfo("[görev] liste temizlendi.");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (sub === "list" || arg.trim() === "") {
|
|
125
|
+
const items = tui.getTodos();
|
|
126
|
+
if (items.length === 0) {
|
|
127
|
+
tui.addInfo("Görev listesi boş.");
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const text = items.map((t, i) => ` ${i + 1}. ${t.done ? "[x]" : "[ ]"} ${t.text}`).join("\n");
|
|
131
|
+
tui.addInfo(`[görevler]\n${text}`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
tui.addInfo("Kullanım: /todo ekle <metin> | sil <no> | temizle | list");
|
|
135
|
+
}
|
|
136
|
+
async function runModelCli(args) {
|
|
137
|
+
const config = loadConfig();
|
|
138
|
+
const sub = (args[0] ?? "list").toLowerCase();
|
|
139
|
+
if (sub === "list") {
|
|
140
|
+
const entries = listInstalledModels();
|
|
141
|
+
const text = entries
|
|
142
|
+
.map((e) => ` ${e.installed ? "[kurulu]" : "[yok] "} ${e.model.id} — ${e.model.name}${e.model.id === config.modelId ? " ★" : ""}`)
|
|
143
|
+
.join("\n");
|
|
144
|
+
console.log(`\n[modeller]\n${text}\n`);
|
|
145
|
+
console.log("Kurulum: ajan model install <id>");
|
|
146
|
+
console.log("Seçim : ajan model use <id>");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (sub === "install") {
|
|
150
|
+
const id = args[1];
|
|
151
|
+
if (!id) {
|
|
152
|
+
console.log("Kullanım: ajan model install <model-id>");
|
|
153
|
+
console.log("Model listesi için: ajan model list");
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const m = getModelById(id);
|
|
157
|
+
if (!m) {
|
|
158
|
+
console.log(`Model bulunamadı: ${id}`);
|
|
159
|
+
console.log("Mevcut modeller:");
|
|
160
|
+
const entries = listInstalledModels();
|
|
161
|
+
for (const e of entries)
|
|
162
|
+
console.log(` ${e.model.id} — ${e.model.name}`);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (isModelInstalled(m)) {
|
|
166
|
+
console.log(`Model zaten kurulu: ${m.name}`);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
console.log(`İndiriliyor: ${m.name} …`);
|
|
170
|
+
try {
|
|
171
|
+
const { downloadModel } = await import("../engine/modelManager.js");
|
|
172
|
+
const path = await downloadModel(m, { showProgress: true });
|
|
173
|
+
console.log(`\nİndirildi: ${path}`);
|
|
174
|
+
console.log(`Kullanmak için: ajan model use ${id}`);
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
console.error(`İndirme başarısız: ${err.message}`);
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (sub === "use") {
|
|
182
|
+
const id = args[1];
|
|
183
|
+
if (!id) {
|
|
184
|
+
console.log("Kullanım: ajan model use <model-id>");
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const m = getModelById(id);
|
|
188
|
+
if (!m) {
|
|
189
|
+
console.log(`Model bulunamadı: ${id}`);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (!isModelInstalled(m)) {
|
|
193
|
+
console.log(`Önce indirin: ajan model install ${id}`);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
config.modelId = id;
|
|
197
|
+
saveConfigFile(config);
|
|
198
|
+
console.log(`Model seçildi: ${m.name} (${id})`);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (sub === "remove") {
|
|
202
|
+
const id = args[1];
|
|
203
|
+
const m = id ? getModelById(id) : undefined;
|
|
204
|
+
if (!m) {
|
|
205
|
+
console.log(`Model bulunamadı: ${id}`);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const { removeModelFile: rm } = await import("../config/configManager.js");
|
|
209
|
+
console.log(rm(m) ? `Silindi: ${m.name}` : "Dosya bulunamadı.");
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
console.log("Kullanım:");
|
|
213
|
+
console.log(" ajan model list — modelleri listele");
|
|
214
|
+
console.log(" ajan model install <id> — model indir");
|
|
215
|
+
console.log(" ajan model use <id> — model seç");
|
|
216
|
+
console.log(" ajan model remove <id> — model sil");
|
|
217
|
+
}
|
|
218
|
+
async function main() {
|
|
219
|
+
const args = process.argv.slice(2);
|
|
220
|
+
if (args.includes("--debug"))
|
|
221
|
+
setLogLevel("debug");
|
|
222
|
+
const subcommand = args.find(a => !a.startsWith("--"));
|
|
223
|
+
if (subcommand === "gui") {
|
|
224
|
+
await launchGui();
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
// `ajan model <subcommand>` — TUI dışı model yönetimi
|
|
228
|
+
if (subcommand === "model") {
|
|
229
|
+
const modelArgs = args.filter(a => a !== subcommand && a !== "--debug");
|
|
230
|
+
await runModelCli(modelArgs);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
// TUI (varsayılan: ajan veya ajan cli)
|
|
234
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
235
|
+
console.log("AJAN etkileşimli bir terminal penceresi gerektirir.");
|
|
236
|
+
console.log("Lütfen CMD/PowerShell içinde doğrudan 'ajan' komutunu çalıştırın.");
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
let cwdNote = null;
|
|
240
|
+
const cwdBase = basename(process.cwd()).toLowerCase();
|
|
241
|
+
if (cwdBase === "system32" || cwdBase === "syswow64") {
|
|
242
|
+
try {
|
|
243
|
+
process.chdir(homedir());
|
|
244
|
+
cwdNote = `[dizin] çalışma dizini System32 olduğu için ana dizine geçildi: ${process.cwd()}\nProje klasörüne geçmek için /cd <dizin> yazabilirsin.`;
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
cwdNote = "[uyari] çalışma dizini System32 — araçlar buraya yazmaya çalışabilir. /cd <dizin> ile değiştir.";
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const config = loadConfig();
|
|
251
|
+
const model = getModelById(config.modelId);
|
|
252
|
+
const modelMissing = model && !isModelInstalled(model);
|
|
253
|
+
const lock = acquireLock("tui");
|
|
254
|
+
if (!lock.ok) {
|
|
255
|
+
console.log(lock.error);
|
|
256
|
+
process.exit(1);
|
|
257
|
+
}
|
|
258
|
+
process.on("exit", () => releaseLock());
|
|
259
|
+
process.on("uncaughtException", () => { releaseLock(); process.exit(1); });
|
|
260
|
+
await runChatTui(config, cwdNote, modelMissing);
|
|
261
|
+
}
|
|
262
|
+
async function runChatTui(config, cwdNote, modelMissing) {
|
|
263
|
+
let currentAbort = null;
|
|
264
|
+
let currentSessionId = null;
|
|
265
|
+
let sessionTitle = "";
|
|
266
|
+
let sessionCreatedAt = "";
|
|
267
|
+
let agentActive = false;
|
|
268
|
+
const tui = new ChatTui({
|
|
269
|
+
onInput: (line) => void handleLine(line),
|
|
270
|
+
onAbort: () => currentAbort?.abort(),
|
|
271
|
+
onExit: () => void exitChat(),
|
|
272
|
+
onCommand: (cmd, arg) => handleCommand(cmd, arg)
|
|
273
|
+
});
|
|
274
|
+
const service = new AjanService({
|
|
275
|
+
onToolCall: (name) => tui.startTool(name),
|
|
276
|
+
onToolResult: (name, ok, output) => tui.endTool(name, ok, output),
|
|
277
|
+
onThinkingChunk: (text) => tui.appendThinking(text),
|
|
278
|
+
onContextTrimmed: (n) => tui.addInfo(`[bellek] eski turlar sıkıştırıldı (${n} mesaj).`),
|
|
279
|
+
onStepStart: (step, maxSteps) => tui.setStep(step, maxSteps),
|
|
280
|
+
onTextChunk: (text) => {
|
|
281
|
+
if (!text)
|
|
282
|
+
return;
|
|
283
|
+
tui.appendAssistant(text);
|
|
284
|
+
void refreshContext();
|
|
285
|
+
},
|
|
286
|
+
onModelLoadProgress: (pct) => tui.setLoading(pct),
|
|
287
|
+
onModelLoadComplete: () => tui.setThinking()
|
|
288
|
+
});
|
|
289
|
+
const refreshContext = async () => {
|
|
290
|
+
tui.setContextInfo(service.getContextStats());
|
|
291
|
+
};
|
|
292
|
+
const modelInfo = service.getModelInfo();
|
|
293
|
+
tui.setModelInfo(modelInfo?.modelId ?? config.modelId, "");
|
|
294
|
+
tui.setCwd(process.cwd());
|
|
295
|
+
void service.detectGpu().then((gpus) => {
|
|
296
|
+
tui.setModelInfo(modelInfo?.modelId ?? config.modelId, gpus.join("/") || "cpu");
|
|
297
|
+
}).catch(() => undefined);
|
|
298
|
+
void getSacState().then((state) => {
|
|
299
|
+
if (isSacBlocking(state)) {
|
|
300
|
+
tui.addInfo(`[uyari] Windows Smart App Control ${describeSacState(state)} — model yüklenirken engellenebilir. Yönetici PowerShell'de 'ajan' klasöründeki fix komutunu kullanın.`);
|
|
301
|
+
}
|
|
302
|
+
}).catch(() => undefined);
|
|
303
|
+
const handleCommand = (cmd, arg) => {
|
|
304
|
+
switch (cmd) {
|
|
305
|
+
case "yeni":
|
|
306
|
+
case "new": {
|
|
307
|
+
void startNewSession(true);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
case "clear": {
|
|
311
|
+
service.reset();
|
|
312
|
+
tui.clearMessages();
|
|
313
|
+
tui.addInfo("[komut] konuşma geçmişi temizlendi.");
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
case "sessions": {
|
|
317
|
+
void listSessions().then((all) => {
|
|
318
|
+
if (all.length === 0) {
|
|
319
|
+
tui.addInfo("Kayıtlı oturum yok.");
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const text = all
|
|
323
|
+
.slice(0, 20)
|
|
324
|
+
.map((s) => ` ${s.id} ${s.title} — ${new Date(s.updatedAt).toLocaleString("tr-TR")} (${s.messages.length} mesaj)`)
|
|
325
|
+
.join("\n");
|
|
326
|
+
tui.addInfo(`[oturumlar]\n${text}`);
|
|
327
|
+
});
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
case "resume": {
|
|
331
|
+
if (!arg) {
|
|
332
|
+
tui.addInfo("Kullanım: /resume <oturum-id>. /sessions ile listeye bakın.");
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
void loadSession(arg).then((data) => {
|
|
336
|
+
if (!data) {
|
|
337
|
+
tui.addInfo(`Oturum bulunamadı: ${arg}`);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
currentSessionId = data.id;
|
|
341
|
+
sessionTitle = data.title;
|
|
342
|
+
sessionCreatedAt = data.createdAt;
|
|
343
|
+
tui.setSessionTitle(data.title);
|
|
344
|
+
tui.setTodos(data.todos ?? []);
|
|
345
|
+
service.setChatHistory(data.history);
|
|
346
|
+
tui.loadMessages(data.messages);
|
|
347
|
+
tui.addInfo(`[oturum] "${data.title}" yüklendi (${data.messages.length} mesaj).`);
|
|
348
|
+
});
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
case "exit":
|
|
352
|
+
case "quit": {
|
|
353
|
+
void exitChat();
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
case "export": {
|
|
357
|
+
exportSession(arg);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
case "kopyala": {
|
|
361
|
+
const messages = tui.getMessages();
|
|
362
|
+
let text = "";
|
|
363
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
364
|
+
const m = messages[i];
|
|
365
|
+
if (m.kind === "tool" && m.output) {
|
|
366
|
+
text = m.output;
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
if (m.kind === "assistant" && m.text) {
|
|
370
|
+
text = m.text;
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (!text) {
|
|
375
|
+
tui.addInfo("Kopyalanacak içerik yok (son araç çıktısı ya da yanıt).");
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
void copyToClipboard(text).then((msg) => tui.addInfo(`[kopyala] ${msg}`));
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
case "todo": {
|
|
382
|
+
handleTodoCommand(arg, tui);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
case "model": {
|
|
386
|
+
void handleModelCommand(arg);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
case "cd": {
|
|
390
|
+
const target = arg.trim();
|
|
391
|
+
if (!target) {
|
|
392
|
+
tui.addInfo(`dizin: ${process.cwd()}\nKullanım: /cd <dizin> (örn: /cd C:\\PROJELER veya /cd ~)`);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const expanded = target === "~" || target.startsWith("~/") || target.startsWith("~\\")
|
|
396
|
+
? join(homedir(), target.slice(1).replace(/^[\\/]/, ""))
|
|
397
|
+
: target;
|
|
398
|
+
const p = resolve(expanded);
|
|
399
|
+
try {
|
|
400
|
+
process.chdir(p);
|
|
401
|
+
tui.setCwd(process.cwd());
|
|
402
|
+
tui.addInfo(`[dizin] ${process.cwd()}`);
|
|
403
|
+
}
|
|
404
|
+
catch (err) {
|
|
405
|
+
tui.addInfo(`Dizine geçilemedi: ${err.message}`);
|
|
406
|
+
}
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
case "doctor": {
|
|
410
|
+
void cmdDoctor(tui);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
case "stats": {
|
|
414
|
+
showStats();
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
case "tools": {
|
|
418
|
+
void import("../tools/registry.js").then(({ toolNames }) => {
|
|
419
|
+
const names = toolNames();
|
|
420
|
+
tui.addInfo(`[araçlar] ${names.length} araç:\n${names.map((n) => ` · ${n}`).join("\n")}`);
|
|
421
|
+
});
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
case "config": {
|
|
425
|
+
handleConfigCommand(arg);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
case "help": {
|
|
429
|
+
tui.addInfo("[komutlar]\n" +
|
|
430
|
+
CHAT_COMMANDS.map((c) => ` /${c.name.padEnd(9)} ${c.desc}`).join("\n"));
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
default:
|
|
434
|
+
tui.addInfo(`Bilinmeyen komut: /${cmd}. /help ile listeye bakın.`);
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
const startNewSession = async (announce) => {
|
|
438
|
+
currentAbort?.abort();
|
|
439
|
+
currentSessionId = null;
|
|
440
|
+
sessionTitle = "";
|
|
441
|
+
sessionCreatedAt = "";
|
|
442
|
+
service.reset();
|
|
443
|
+
tui.clearMessages();
|
|
444
|
+
tui.setSessionTitle("");
|
|
445
|
+
tui.addInfo("[oturum] yeni oturum hazır.");
|
|
446
|
+
if (!announce)
|
|
447
|
+
tui.clearMessages();
|
|
448
|
+
};
|
|
449
|
+
const exportSession = (fileNameArg) => {
|
|
450
|
+
const meaningful = tui.getMessages().filter((m) => m.kind !== "info");
|
|
451
|
+
if (meaningful.length === 0) {
|
|
452
|
+
tui.addInfo("Dışa aktarılacak mesaj yok.");
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
const id = currentSessionId ?? generateSessionId(new Date());
|
|
456
|
+
const fileName = fileNameArg || `export-${id}.md`;
|
|
457
|
+
const lines = [
|
|
458
|
+
`# AJAN Sohbet — ${sessionTitle || "(başlıksız)"}`,
|
|
459
|
+
`Tarih: ${new Date().toLocaleString("tr-TR")}`,
|
|
460
|
+
`Oturum: ${id}`,
|
|
461
|
+
""
|
|
462
|
+
];
|
|
463
|
+
const todos = tui.getTodos();
|
|
464
|
+
if (todos.length > 0) {
|
|
465
|
+
lines.push("## Görevler");
|
|
466
|
+
for (const t of todos)
|
|
467
|
+
lines.push(`- [${t.done ? "x" : " "}] ${t.text}`);
|
|
468
|
+
lines.push("");
|
|
469
|
+
}
|
|
470
|
+
for (const m of meaningful) {
|
|
471
|
+
if (m.kind === "user")
|
|
472
|
+
lines.push(`## Kullanıcı\n\n${m.text ?? ""}`, "");
|
|
473
|
+
else if (m.kind === "assistant")
|
|
474
|
+
lines.push(`## AJAN\n\n${m.text ?? ""}`, "");
|
|
475
|
+
else if (m.kind === "thought")
|
|
476
|
+
lines.push(`### Düşünce\n\n${m.text ?? ""}`, "");
|
|
477
|
+
else if (m.kind === "tool")
|
|
478
|
+
lines.push(`### Araç: ${m.name}\n\n\`\`\`\n${m.output ?? ""}\n\`\`\``, "");
|
|
479
|
+
}
|
|
480
|
+
try {
|
|
481
|
+
writeFileSync(fileName, lines.join("\n"), "utf8");
|
|
482
|
+
tui.addInfo(`[dışa aktarıldı] ${fileName}`);
|
|
483
|
+
}
|
|
484
|
+
catch (err) {
|
|
485
|
+
tui.addInfo(`Dışa aktarma başarısız: ${err.message}`);
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
const handleModelCommand = async (arg) => {
|
|
489
|
+
const parts = arg.split(/\s+/).filter(Boolean);
|
|
490
|
+
const sub = (parts[0] ?? "list").toLowerCase();
|
|
491
|
+
if (sub === "list") {
|
|
492
|
+
const models = service.listModels();
|
|
493
|
+
const text = models
|
|
494
|
+
.map((e) => ` ${e.installed ? "[kurulu]" : "[yok] "} ${e.model.id} — ${e.model.name}${e.active ? " ★" : ""}`)
|
|
495
|
+
.join("\n");
|
|
496
|
+
tui.addInfo(`[modeller]\n${text}\n\nKurulum: /model install <id> · Seçim: /model use <id>`);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
if (sub === "install") {
|
|
500
|
+
const id = parts[1];
|
|
501
|
+
if (!id) {
|
|
502
|
+
tui.addInfo("Kullanım: /model install <model-id>");
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
const info = service.getModelInfo();
|
|
506
|
+
if (info?.modelId === id && info.installed) {
|
|
507
|
+
tui.addInfo(`Model zaten kurulu: ${id}`);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
tui.addInfo(`İndiriliyor...`);
|
|
511
|
+
const result = await service.installModel(id);
|
|
512
|
+
if (result.ok) {
|
|
513
|
+
tui.addInfo(`[indirildi] ${id}\nKullanmak için: /model use ${id}`);
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
tui.addInfo(`İndirme başarısız: ${result.error}`);
|
|
517
|
+
}
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
if (sub === "use") {
|
|
521
|
+
const id = parts[1];
|
|
522
|
+
if (!id) {
|
|
523
|
+
tui.addInfo("Kullanım: /model use <model-id>");
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
const result = await service.useModel(id);
|
|
527
|
+
if (result.ok) {
|
|
528
|
+
tui.addInfo(`Model seçildi: ${id}. Etkin olması için /yeni ile yeni oturum açın.`);
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
tui.addInfo(result.error || "Model seçilemedi");
|
|
532
|
+
}
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (sub === "remove") {
|
|
536
|
+
const id = parts[1];
|
|
537
|
+
if (!id) {
|
|
538
|
+
tui.addInfo("Kullanım: /model remove <model-id>");
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
const result = await service.removeModel(id);
|
|
542
|
+
tui.addInfo(result.ok ? `[silindi] ${id}` : (result.error || "Silinemedi"));
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
tui.addInfo("Kullanım: /model <list|install <id>|use <id>|remove <id>>");
|
|
546
|
+
};
|
|
547
|
+
const cmdDoctor = async (tuiRef) => {
|
|
548
|
+
const cfg = service.getConfig();
|
|
549
|
+
const mi = service.getModelInfo();
|
|
550
|
+
const lines = ["[sistem kontrolü]"];
|
|
551
|
+
lines.push(` model : ${mi ? `${mi.name} ${mi.installed ? "(kurulu)" : "(İNDRİLMEDİ)"}` : `${cfg.modelId} (kayıtlı değil)`}`);
|
|
552
|
+
try {
|
|
553
|
+
const gpus = await service.detectGpu();
|
|
554
|
+
lines.push(` gpu : ${gpus.length > 0 ? gpus.join(", ") : "yok (CPU)"}`);
|
|
555
|
+
}
|
|
556
|
+
catch (err) {
|
|
557
|
+
lines.push(` gpu hata : ${err.message}`);
|
|
558
|
+
}
|
|
559
|
+
try {
|
|
560
|
+
const { getSacState, isSacBlocking, describeSacState } = await import("../utils/sacGuard.js");
|
|
561
|
+
const sac = await getSacState();
|
|
562
|
+
lines.push(` SAC : ${describeSacState(sac)}${isSacBlocking(sac) ? " ⚠ engelleyebilir" : ""}`);
|
|
563
|
+
}
|
|
564
|
+
catch {
|
|
565
|
+
lines.push(" SAC : bilinmiyor");
|
|
566
|
+
}
|
|
567
|
+
const stats = service.getContextStats();
|
|
568
|
+
lines.push(` bağlam : ${stats ? `${stats.usedTokens}/${stats.contextSize} token` : "motor kapalı"}`);
|
|
569
|
+
lines.push(` cwd : ${process.cwd()}`);
|
|
570
|
+
tuiRef.addInfo(lines.join("\n"));
|
|
571
|
+
};
|
|
572
|
+
const showStats = () => {
|
|
573
|
+
const stats = service.getContextStats();
|
|
574
|
+
const msgs = tui.getMessages();
|
|
575
|
+
const tools = msgs.filter((m) => m.kind === "tool").length;
|
|
576
|
+
const lines = [
|
|
577
|
+
"[istatistik]",
|
|
578
|
+
` oturum : ${sessionTitle || "Yeni Sohbet"} (${currentSessionId ?? "henüz kaydedilmedi"})`,
|
|
579
|
+
` mesaj : ${msgs.length} (${tools} araç çağrısı)`,
|
|
580
|
+
` görevler : ${tui.getTodos().length}`,
|
|
581
|
+
` bağlam : ${stats ? `${stats.usedTokens}/${stats.contextSize} token (%${Math.round((stats.usedTokens / Math.max(1, stats.contextSize)) * 100)})` : "-"}`
|
|
582
|
+
];
|
|
583
|
+
tui.addInfo(lines.join("\n"));
|
|
584
|
+
};
|
|
585
|
+
const handleConfigCommand = (arg) => {
|
|
586
|
+
const parts = arg.split(/\s+/).filter(Boolean);
|
|
587
|
+
const cfg = service.getConfig();
|
|
588
|
+
if (parts.length === 0) {
|
|
589
|
+
tui.addInfo("[ayarlar]\n" +
|
|
590
|
+
` temperature: ${cfg.session?.temperature ?? "-"}\n` +
|
|
591
|
+
` maxTokens : ${cfg.session?.maxTokens ?? "-"}\n` +
|
|
592
|
+
` maxSteps : ${cfg.maxSteps ?? 25}\n` +
|
|
593
|
+
` modelId : ${cfg.modelId}\n\n` +
|
|
594
|
+
"Değiştirmek için: /config <anahtar> <değer>");
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
const key = (parts[0] ?? "").toLowerCase();
|
|
598
|
+
const value = parts[1] ?? "";
|
|
599
|
+
if (!value) {
|
|
600
|
+
tui.addInfo("Değer eksik. Örn: /config temperature 0.7");
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
const patch = {};
|
|
604
|
+
switch (key) {
|
|
605
|
+
case "temperature": {
|
|
606
|
+
const v = Number(value);
|
|
607
|
+
if (Number.isNaN(v) || v < 0 || v > 2) {
|
|
608
|
+
tui.addInfo("Geçersiz sıcaklık (0-2).");
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
patch.session = { ...cfg.session, temperature: v };
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
614
|
+
case "maxtokens": {
|
|
615
|
+
const v = Number(value);
|
|
616
|
+
if (!Number.isInteger(v) || v < 256) {
|
|
617
|
+
tui.addInfo("Geçersiz maxTokens (>= 256).");
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
patch.session = { ...cfg.session, maxTokens: v };
|
|
621
|
+
break;
|
|
622
|
+
}
|
|
623
|
+
case "maxsteps": {
|
|
624
|
+
const v = Number(value);
|
|
625
|
+
if (!Number.isInteger(v) || v < 1 || v > 200) {
|
|
626
|
+
tui.addInfo("Geçersiz maxSteps (1-200).");
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
patch.maxSteps = v;
|
|
630
|
+
break;
|
|
631
|
+
}
|
|
632
|
+
default:
|
|
633
|
+
tui.addInfo(`Bilinmeyen anahtar: ${key}. (temperature, maxTokens, maxSteps)`);
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
void service.setConfig(patch).then(() => {
|
|
637
|
+
tui.addInfo(`[ayar] ${key} = ${value} kaydedildi.`);
|
|
638
|
+
});
|
|
639
|
+
};
|
|
640
|
+
const persistSession = async () => {
|
|
641
|
+
const messages = tui.getMessages();
|
|
642
|
+
const meaningful = messages.filter((m) => m.kind !== "info");
|
|
643
|
+
if (meaningful.length === 0)
|
|
644
|
+
return;
|
|
645
|
+
if (!sessionTitle) {
|
|
646
|
+
const firstUser = meaningful.find((m) => m.kind === "user");
|
|
647
|
+
if (firstUser?.text) {
|
|
648
|
+
sessionTitle = generateTitle(firstUser.text);
|
|
649
|
+
tui.setSessionTitle(sessionTitle);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
const now = new Date();
|
|
653
|
+
if (!currentSessionId) {
|
|
654
|
+
currentSessionId = generateSessionId(now);
|
|
655
|
+
sessionCreatedAt = now.toISOString();
|
|
656
|
+
}
|
|
657
|
+
await service.saveSession({
|
|
658
|
+
id: currentSessionId,
|
|
659
|
+
title: sessionTitle || "Yeni Sohbet",
|
|
660
|
+
mode: "agent",
|
|
661
|
+
createdAt: sessionCreatedAt,
|
|
662
|
+
updatedAt: now.toISOString(),
|
|
663
|
+
history: service.getChatHistory(),
|
|
664
|
+
messages: meaningful,
|
|
665
|
+
todos: tui.getTodos()
|
|
666
|
+
});
|
|
667
|
+
};
|
|
668
|
+
const handleLine = async (line) => {
|
|
669
|
+
if (!line)
|
|
670
|
+
return;
|
|
671
|
+
if (line === "exit" || line === "quit") {
|
|
672
|
+
void exitChat();
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
if (agentActive) {
|
|
676
|
+
tui.addInfo("[meşgul] AJAN çalışıyor; durdurmak için ESC.");
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
tui.addUser(line);
|
|
680
|
+
if (!sessionTitle) {
|
|
681
|
+
sessionTitle = generateTitle(line);
|
|
682
|
+
tui.setSessionTitle(sessionTitle);
|
|
683
|
+
}
|
|
684
|
+
tui.setThinking();
|
|
685
|
+
agentActive = true;
|
|
686
|
+
const controller = new AbortController();
|
|
687
|
+
currentAbort = controller;
|
|
688
|
+
try {
|
|
689
|
+
const result = await service.sendMessage(line);
|
|
690
|
+
if (!result.ok && result.error) {
|
|
691
|
+
if (result.error === "İptal edildi") {
|
|
692
|
+
tui.addInfo("[iptal] görev durduruldu.");
|
|
693
|
+
}
|
|
694
|
+
else {
|
|
695
|
+
tui.addInfo(`Hata: ${result.error}`);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
if (config.beepOnComplete !== false)
|
|
699
|
+
process.stdout.write("\x07");
|
|
700
|
+
}
|
|
701
|
+
catch (err) {
|
|
702
|
+
tui.addInfo(`Hata: ${err.message}`);
|
|
703
|
+
}
|
|
704
|
+
finally {
|
|
705
|
+
if (currentAbort === controller)
|
|
706
|
+
currentAbort = null;
|
|
707
|
+
agentActive = false;
|
|
708
|
+
tui.endTurn();
|
|
709
|
+
void persistSession();
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
const onFatal = (err) => {
|
|
713
|
+
tui.stop();
|
|
714
|
+
process.stdout.write("\x1b[?25h\x1b[0m\x1b[?1049l");
|
|
715
|
+
logger.error(`Beklenmeyen hata: ${(err instanceof Error ? err.stack : String(err)) ?? String(err)}`);
|
|
716
|
+
process.exit(1);
|
|
717
|
+
};
|
|
718
|
+
process.on("uncaughtException", onFatal);
|
|
719
|
+
process.on("unhandledRejection", onFatal);
|
|
720
|
+
const exitChat = async () => {
|
|
721
|
+
currentAbort?.abort();
|
|
722
|
+
await persistSession();
|
|
723
|
+
process.removeListener("uncaughtException", onFatal);
|
|
724
|
+
process.removeListener("unhandledRejection", onFatal);
|
|
725
|
+
tui.stop();
|
|
726
|
+
await service.dispose();
|
|
727
|
+
releaseLock();
|
|
728
|
+
process.exit(0);
|
|
729
|
+
};
|
|
730
|
+
if (cwdNote)
|
|
731
|
+
tui.addInfo(cwdNote);
|
|
732
|
+
if (modelMissing) {
|
|
733
|
+
tui.addInfo(`[uyari] Model indirilmemiş: ${getModelById(config.modelId)?.name ?? config.modelId}\n /model install ile indirin.`);
|
|
734
|
+
}
|
|
735
|
+
tui.start();
|
|
736
|
+
}
|
|
737
|
+
main().catch((err) => {
|
|
738
|
+
logger.error(`Beklenmeyen hata: ${err.stack ?? String(err)}`);
|
|
739
|
+
process.exitCode = 1;
|
|
740
|
+
});
|
|
741
|
+
//# sourceMappingURL=index.js.map
|