wormclaude 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +147 -0
- package/dist/agent.js +61 -0
- package/dist/api.js +163 -0
- package/dist/auth.js +108 -0
- package/dist/cli.js +851 -0
- package/dist/commands.js +540 -0
- package/dist/compact.js +53 -0
- package/dist/i18n.js +177 -0
- package/dist/learn.js +47 -0
- package/dist/links.js +31 -0
- package/dist/mcp.js +104 -0
- package/dist/memory.js +135 -0
- package/dist/skills.js +275 -0
- package/dist/tasks.js +63 -0
- package/dist/theme.js +11 -0
- package/dist/tips.js +60 -0
- package/dist/toolSummary.js +24 -0
- package/dist/tools.js +1136 -0
- package/dist/usage.js +71 -0
- package/package.json +44 -0
package/dist/commands.js
ADDED
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
// Slash komutları — Claude Code komut setinin WormClaude'a çalışır uyarlaması.
|
|
2
|
+
// Her komut yerel model (OpenAI-uyumlu backend), git ve dosya sistemiyle gerçek iş yapar.
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
4
|
+
import * as fs from 'node:fs';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import { streamChat } from './api.js';
|
|
7
|
+
import { tasks } from './tasks.js';
|
|
8
|
+
import { connectMcpServers, getMcpServers, getMcpConfigPath } from './mcp.js';
|
|
9
|
+
import * as usage from './usage.js';
|
|
10
|
+
import { runCompact, contextPercent, autoCompactThreshold } from './compact.js';
|
|
11
|
+
import { cmdDesc, setLang, saveLang, getLang } from './i18n.js';
|
|
12
|
+
import { loadSkills, getSkills, getSkillsDir, installSkill, updateSkill, removeSkill, getRegistry } from './skills.js';
|
|
13
|
+
import { isLearnEnabled, setLearnEnabled, getLearnFile, getLearnCount } from './learn.js';
|
|
14
|
+
export const COMMANDS = [
|
|
15
|
+
{ name: '/help', desc: 'komutları ve ipuçlarını göster' },
|
|
16
|
+
{ name: '/clear', desc: 'sohbeti ve geçmişi temizle' },
|
|
17
|
+
{ name: '/compact', desc: 'geçmişi modelle özetleyip bağlamı küçült' },
|
|
18
|
+
{ name: '/context', desc: 'bağlam / token kullanımını göster' },
|
|
19
|
+
{ name: '/cost', desc: 'oturum token tahminini göster' },
|
|
20
|
+
{ name: '/config', desc: 'ayarları göster / değiştir (url, key, model)' },
|
|
21
|
+
{ name: '/model', desc: 'modeli göster / değiştir' },
|
|
22
|
+
{ name: '/diff', desc: 'git diff göster' },
|
|
23
|
+
{ name: '/commit', desc: 'stage edilmiş değişiklikleri modelle commit et' },
|
|
24
|
+
{ name: '/review', desc: 'mevcut diff’i modelle incele' },
|
|
25
|
+
{ name: '/init', desc: 'projeyi tarayıp WORMCLAUDE.md üret' },
|
|
26
|
+
{ name: '/memory', desc: 'WORMCLAUDE.md hafızasını oku / satır ekle' },
|
|
27
|
+
{ name: '/doctor', desc: 'sistem ve backend sağlık kontrolü' },
|
|
28
|
+
{ name: '/lang', desc: 'arayüz dilini değiştir (tr/en)' },
|
|
29
|
+
{ name: '/skills', desc: 'skill\'leri listele / indir / yeniden yükle' },
|
|
30
|
+
{ name: '/skillify', desc: 'bu oturumu yeniden kullanılabilir skill\'e çevir' },
|
|
31
|
+
{ name: '/agent', desc: 'tek bir alt-agent\'a görev ver: /agent <görev>' },
|
|
32
|
+
{ name: '/multi-agent', desc: 'paralel çoklu-agent koordinasyonu: /multi-agent <görev>' },
|
|
33
|
+
{ name: '/mcp', desc: 'MCP sunucularını listele / yeniden bağlan' },
|
|
34
|
+
{ name: '/tasks', desc: 'arka plan görevlerini (shell/agent) listele' },
|
|
35
|
+
{ name: '/kill', desc: 'bir arka plan görevini durdur: /kill <id>' },
|
|
36
|
+
{ name: '/dream', desc: 'arka planda hafızayı konsolide et (.wormclaude/memory.md)' },
|
|
37
|
+
{ name: '/learn', desc: 'web-öğrenme datasını göster / aç-kapa (eğitim için)' },
|
|
38
|
+
{ name: '/export', desc: 'sohbeti dosyaya kaydet' },
|
|
39
|
+
{ name: '/resume', desc: 'en son kaydedilen oturumu yükle' },
|
|
40
|
+
{ name: '/quit', desc: 'çıkış' },
|
|
41
|
+
];
|
|
42
|
+
// Tek seferlik tam yanıt (stream'i biriktirir, araç çağrısı yok)
|
|
43
|
+
async function complete(messages, config) {
|
|
44
|
+
let out = '';
|
|
45
|
+
for await (const ev of streamChat(messages, [], config)) {
|
|
46
|
+
if (ev.type === 'text')
|
|
47
|
+
out += ev.text;
|
|
48
|
+
else if (ev.type === 'error')
|
|
49
|
+
return `[hata: ${ev.error}]`;
|
|
50
|
+
}
|
|
51
|
+
return out.trim();
|
|
52
|
+
}
|
|
53
|
+
function git(cmd) {
|
|
54
|
+
try {
|
|
55
|
+
const out = execSync(`git ${cmd}`, { encoding: 'utf8', timeout: 30000, windowsHide: true });
|
|
56
|
+
return { ok: true, out: out.trim() };
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
return { ok: false, out: (e?.stdout || e?.stderr || e?.message || String(e)).toString().trim() };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function isGitRepo() {
|
|
63
|
+
return git('rev-parse --is-inside-work-tree').ok;
|
|
64
|
+
}
|
|
65
|
+
function approxTokens(obj) {
|
|
66
|
+
return Math.round(JSON.stringify(obj).length / 4);
|
|
67
|
+
}
|
|
68
|
+
const SESSION_DIR = path.join(process.cwd(), '.wormclaude', 'sessions');
|
|
69
|
+
function tsStamp() {
|
|
70
|
+
const d = new Date();
|
|
71
|
+
const p = (n) => String(n).padStart(2, '0');
|
|
72
|
+
return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`;
|
|
73
|
+
}
|
|
74
|
+
// Komut yürütücü. true → komut işlendi, false → komut değil (normal mesaj).
|
|
75
|
+
export async function runSlashCommand(input, ctx) {
|
|
76
|
+
const v = input.trim();
|
|
77
|
+
if (!v.startsWith('/'))
|
|
78
|
+
return false;
|
|
79
|
+
const [cmd, ...rest] = v.split(/\s+/);
|
|
80
|
+
const arg = rest.join(' ').trim();
|
|
81
|
+
const C = ctx.config;
|
|
82
|
+
switch (cmd) {
|
|
83
|
+
case '/quit':
|
|
84
|
+
case '/exit':
|
|
85
|
+
ctx.exit();
|
|
86
|
+
return true;
|
|
87
|
+
case '/clear':
|
|
88
|
+
ctx.clearConv();
|
|
89
|
+
return true;
|
|
90
|
+
case '/help':
|
|
91
|
+
ctx.note((getLang() === 'en' ? 'Commands:\n' : 'Komutlar:\n') +
|
|
92
|
+
COMMANDS.map((c) => ` ${c.name.padEnd(10)} ${cmdDesc(c.name)}`).join('\n') +
|
|
93
|
+
'\n\nTools: Bash · Read · Write · Edit · Glob · Grep · WebFetch · Agent');
|
|
94
|
+
return true;
|
|
95
|
+
case '/lang': {
|
|
96
|
+
const v = (arg || '').toLowerCase();
|
|
97
|
+
if (v === 'tr' || v === 'en') {
|
|
98
|
+
setLang(v);
|
|
99
|
+
saveLang(v);
|
|
100
|
+
ctx.note(v === 'en' ? 'Language set to English.' : 'Dil Türkçe olarak ayarlandı.');
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
const next = getLang() === 'tr' ? 'en' : 'tr';
|
|
104
|
+
setLang(next);
|
|
105
|
+
saveLang(next);
|
|
106
|
+
ctx.note(next === 'en' ? 'Language set to English.' : 'Dil Türkçe olarak ayarlandı.');
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
case '/model':
|
|
111
|
+
if (arg) {
|
|
112
|
+
C.model = arg;
|
|
113
|
+
ctx.note(`Model: ${arg}`);
|
|
114
|
+
}
|
|
115
|
+
else
|
|
116
|
+
ctx.note(`Model: ${C.model}`);
|
|
117
|
+
return true;
|
|
118
|
+
case '/config': {
|
|
119
|
+
// /config → göster
|
|
120
|
+
// /config url <x> → baseUrl
|
|
121
|
+
// /config key <x> → apiKey
|
|
122
|
+
// /config model <x> → model
|
|
123
|
+
const [sub, ...vrest] = rest;
|
|
124
|
+
const val = vrest.join(' ').trim();
|
|
125
|
+
if (!sub) {
|
|
126
|
+
const masked = C.apiKey ? C.apiKey.slice(0, 3) + '***' : '(yok)';
|
|
127
|
+
ctx.note(`Yapılandırma:\n baseUrl: ${C.baseUrl}\n apiKey: ${masked}\n model: ${C.model}`);
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
if (sub === 'url' && val) {
|
|
131
|
+
C.baseUrl = val;
|
|
132
|
+
ctx.note(`baseUrl → ${val}`);
|
|
133
|
+
}
|
|
134
|
+
else if (sub === 'key' && val) {
|
|
135
|
+
C.apiKey = val;
|
|
136
|
+
ctx.note('apiKey güncellendi');
|
|
137
|
+
}
|
|
138
|
+
else if (sub === 'model' && val) {
|
|
139
|
+
C.model = val;
|
|
140
|
+
ctx.note(`model → ${val}`);
|
|
141
|
+
}
|
|
142
|
+
else
|
|
143
|
+
ctx.note('Kullanım: /config [url|key|model] <değer>');
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
case '/context': {
|
|
147
|
+
const h = ctx.getHistory();
|
|
148
|
+
const byRole = {};
|
|
149
|
+
for (const m of h)
|
|
150
|
+
byRole[m.role] = (byRole[m.role] || 0) + approxTokens(m);
|
|
151
|
+
const total = approxTokens(h);
|
|
152
|
+
const pct = contextPercent(h);
|
|
153
|
+
const lines = Object.entries(byRole).map(([r, t]) => ` ${r.padEnd(10)} ~${t} tok`);
|
|
154
|
+
ctx.note(`Bağlam: ~${total.toLocaleString()} tok · %${pct}\n` +
|
|
155
|
+
`Oto-compact eşiği: ~${autoCompactThreshold().toLocaleString()} tok\n` +
|
|
156
|
+
`Mesaj sayısı: ${h.length}\n` +
|
|
157
|
+
lines.join('\n'));
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
case '/cost': {
|
|
161
|
+
ctx.note('── Bu oturum ──\n' + usage.formatTotals(usage.getSession()) +
|
|
162
|
+
'\n\n── Toplam (kümülatif) ──\n' + usage.formatTotals(usage.getLifetime()) +
|
|
163
|
+
'\n\n(token verisi backend usage alanından; maliyet usage.ts fiyat tablosundan)');
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
case '/compact': {
|
|
167
|
+
const h = ctx.getHistory();
|
|
168
|
+
if (h.filter((m) => m.role !== 'system').length === 0) {
|
|
169
|
+
ctx.note('Özetlenecek geçmiş yok.');
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
const { history: newHist, summary } = await runCompact(h, C, arg || undefined);
|
|
173
|
+
ctx.setHistory(newHist);
|
|
174
|
+
ctx.note(`Sohbet özetlendi: ${h.length} → ${newHist.length} mesaj`);
|
|
175
|
+
ctx.assistant(summary);
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
case '/diff': {
|
|
179
|
+
if (!isGitRepo()) {
|
|
180
|
+
ctx.note('Burası bir git deposu değil.');
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
const r = git('diff');
|
|
184
|
+
ctx.note(r.out ? r.out.slice(0, 8000) : 'Değişiklik yok (working tree temiz).');
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
case '/commit': {
|
|
188
|
+
if (!isGitRepo()) {
|
|
189
|
+
ctx.note('Burası bir git deposu değil.');
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
const staged = git('diff --cached');
|
|
193
|
+
if (!staged.out) {
|
|
194
|
+
ctx.note('Stage edilmiş değişiklik yok. Önce: git add <dosya>');
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
const msg = await complete([
|
|
198
|
+
{ role: 'system', content: 'You write concise git commit messages. Output ONLY the message, no quotes, no prose. First line under 70 chars, imperative mood.' },
|
|
199
|
+
{ role: 'user', content: 'Write a commit message for this staged diff:\n\n' + staged.out.slice(0, 12000) },
|
|
200
|
+
], C);
|
|
201
|
+
const clean = msg.replace(/^["'`]+|["'`]+$/g, '').split('\n')[0].slice(0, 100);
|
|
202
|
+
const res = git(`commit -m "${clean.replace(/"/g, '\\"')}"`);
|
|
203
|
+
ctx.note(res.ok ? `Commit edildi: ${clean}\n${res.out}` : `Commit hatası:\n${res.out}`);
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
case '/review': {
|
|
207
|
+
if (!isGitRepo()) {
|
|
208
|
+
ctx.note('Burası bir git deposu değil.');
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
const diff = git('diff HEAD');
|
|
212
|
+
if (!diff.out) {
|
|
213
|
+
ctx.note('İncelenecek değişiklik yok.');
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
ctx.note('Değişiklikler inceleniyor…');
|
|
217
|
+
const out = await complete([
|
|
218
|
+
{ role: 'system', content: 'You are a senior code reviewer. Review the diff for correctness bugs, security issues, and simplifications. Be specific with file:line. Answer in the user\'s language.' },
|
|
219
|
+
{ role: 'user', content: 'Review this diff:\n\n' + diff.out.slice(0, 14000) },
|
|
220
|
+
], C);
|
|
221
|
+
ctx.assistant(out);
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
case '/init': {
|
|
225
|
+
const files = [];
|
|
226
|
+
const walk = (d, depth = 0) => {
|
|
227
|
+
if (depth > 3 || files.length > 400)
|
|
228
|
+
return;
|
|
229
|
+
let entries;
|
|
230
|
+
try {
|
|
231
|
+
entries = fs.readdirSync(d, { withFileTypes: true });
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
for (const e of entries) {
|
|
237
|
+
if (['node_modules', '.git', 'dist', '.wormclaude', 'venv'].includes(e.name))
|
|
238
|
+
continue;
|
|
239
|
+
const full = path.join(d, e.name);
|
|
240
|
+
if (e.isDirectory())
|
|
241
|
+
walk(full, depth + 1);
|
|
242
|
+
else
|
|
243
|
+
files.push(path.relative(process.cwd(), full));
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
walk(process.cwd());
|
|
247
|
+
let pkg = '';
|
|
248
|
+
try {
|
|
249
|
+
pkg = fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8');
|
|
250
|
+
}
|
|
251
|
+
catch { }
|
|
252
|
+
ctx.note('Proje taranıyor, WORMCLAUDE.md üretiliyor…');
|
|
253
|
+
const md = await complete([
|
|
254
|
+
{ role: 'system', content: 'You generate a concise WORMCLAUDE.md describing a codebase for an AI assistant: overview, structure, key commands (build/test/run), and conventions. Output only markdown.' },
|
|
255
|
+
{ role: 'user', content: `Dosya listesi:\n${files.slice(0, 300).join('\n')}\n\npackage.json:\n${pkg.slice(0, 3000)}` },
|
|
256
|
+
], C);
|
|
257
|
+
const dest = path.join(process.cwd(), 'WORMCLAUDE.md');
|
|
258
|
+
fs.writeFileSync(dest, md);
|
|
259
|
+
ctx.note(`WORMCLAUDE.md yazıldı (${md.length} karakter).`);
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
case '/memory': {
|
|
263
|
+
const dest = path.join(process.cwd(), 'WORMCLAUDE.md');
|
|
264
|
+
if (arg) {
|
|
265
|
+
const prefix = fs.existsSync(dest) ? fs.readFileSync(dest, 'utf8').replace(/\s*$/, '') + '\n' : '';
|
|
266
|
+
fs.writeFileSync(dest, prefix + `- ${arg}\n`);
|
|
267
|
+
ctx.note(`Hafızaya eklendi: ${arg}`);
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
ctx.note(fs.existsSync(dest) ? fs.readFileSync(dest, 'utf8').slice(0, 6000) : 'WORMCLAUDE.md yok. /init ile oluştur veya /memory <metin> ile ekle.');
|
|
271
|
+
}
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
case '/doctor': {
|
|
275
|
+
const checks = [];
|
|
276
|
+
checks.push(`Node: ${process.version}`);
|
|
277
|
+
checks.push(`CWD: ${process.cwd()}`);
|
|
278
|
+
checks.push(`Git: ${git('--version').ok ? git('--version').out : '✗ bulunamadı'}`);
|
|
279
|
+
checks.push(`Repo: ${isGitRepo() ? '✓ git deposu' : '— git deposu değil'}`);
|
|
280
|
+
checks.push(`Model: ${C.model}`);
|
|
281
|
+
checks.push(`Backend: ${C.baseUrl}`);
|
|
282
|
+
try {
|
|
283
|
+
const root = C.baseUrl.replace(/\/v1\/?$/, '');
|
|
284
|
+
const res = await fetch(root + '/docs', { signal: AbortSignal.timeout(4000) });
|
|
285
|
+
checks.push(` ${res.ok ? '✓ erişilebilir' : '✗ HTTP ' + res.status}`);
|
|
286
|
+
}
|
|
287
|
+
catch (e) {
|
|
288
|
+
checks.push(` ✗ ulaşılamıyor (${e?.message || e})`);
|
|
289
|
+
}
|
|
290
|
+
ctx.note('Sağlık kontrolü:\n' + checks.map((c) => ' ' + c).join('\n'));
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
case '/skills': {
|
|
294
|
+
const en = getLang() === 'en';
|
|
295
|
+
const [sub, ...vrest] = rest;
|
|
296
|
+
const subArg = vrest.join(' ').trim();
|
|
297
|
+
// /skills install <ad | owner/repo | git url | https://.../x.md>
|
|
298
|
+
if (sub === 'install' && subArg) {
|
|
299
|
+
ctx.note(en ? `Downloading: ${subArg}…` : `İndiriliyor: ${subArg}…`);
|
|
300
|
+
const res = await installSkill(subArg);
|
|
301
|
+
if (res.error)
|
|
302
|
+
ctx.note((en ? 'Install error: ' : 'İndirme hatası: ') + res.error);
|
|
303
|
+
else if (!res.names.length)
|
|
304
|
+
ctx.note(en ? `No skills found in ${subArg}` : `${subArg} içinde skill bulunamadı`);
|
|
305
|
+
else
|
|
306
|
+
ctx.note((en ? 'Installed: ' : 'Yüklendi: ') + res.names.map((n) => '/' + n).join(', '));
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
309
|
+
// /skills update <ad>
|
|
310
|
+
if (sub === 'update' && subArg) {
|
|
311
|
+
ctx.note(en ? `Updating: ${subArg}…` : `Güncelleniyor: ${subArg}…`);
|
|
312
|
+
const res = await updateSkill(subArg);
|
|
313
|
+
ctx.note(res.ok ? (en ? `Updated: /${subArg}` : `Güncellendi: /${subArg}`) : (en ? 'Update error: ' : 'Güncelleme hatası: ') + (res.error || ''));
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
// /skills remove <ad>
|
|
317
|
+
if (sub === 'remove' && subArg) {
|
|
318
|
+
ctx.note(removeSkill(subArg) ? (en ? `Removed: ${subArg}` : `Silindi: ${subArg}`) : (en ? `Not found: ${subArg}` : `Bulunamadı: ${subArg}`));
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
// /skills search <kelime>
|
|
322
|
+
if (sub === 'search') {
|
|
323
|
+
const reg = await getRegistry();
|
|
324
|
+
const q = subArg.toLowerCase();
|
|
325
|
+
const hits = Object.entries(reg).filter(([n, e]) => !q || n.toLowerCase().includes(q) || (e.description || '').toLowerCase().includes(q));
|
|
326
|
+
if (!hits.length) {
|
|
327
|
+
ctx.note(en ? 'No skills found in registry.' : 'Registry\'de skill bulunamadı.');
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
330
|
+
ctx.note((en ? 'Registry:\n' : 'Registry:\n') +
|
|
331
|
+
hits.map(([n, e]) => ` ${n.padEnd(18)} ${e.description || ''} (${e.repo})`).join('\n') +
|
|
332
|
+
`\n\n${en ? 'install with' : 'kurmak için'}: /skills install <ad>`);
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
if (sub === 'reload')
|
|
336
|
+
loadSkills();
|
|
337
|
+
// Liste
|
|
338
|
+
const skills = getSkills();
|
|
339
|
+
if (!skills.length) {
|
|
340
|
+
ctx.note((en ? 'No skills loaded.\n' : 'Yüklü skill yok.\n') +
|
|
341
|
+
`${en ? 'Folder' : 'Klasör'}: ${getSkillsDir()}\n` +
|
|
342
|
+
(en
|
|
343
|
+
? 'Add: /skills install <name|owner/repo|url> · search: /skills search <q>'
|
|
344
|
+
: 'Ekle: /skills install <ad|owner/repo|url> · ara: /skills search <kelime>'));
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
ctx.note((en ? 'Skills:\n' : 'Skill\'ler:\n') +
|
|
348
|
+
skills.map((s) => ` /${s.name.padEnd(16)} ${s.context}${s.autoInvoke ? ',auto' : ''} ${s.description}`).join('\n') +
|
|
349
|
+
`\n\n/skills install <ad> · search <kelime> · update <ad> · remove <ad>`);
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
case '/skillify': {
|
|
353
|
+
const en = getLang() === 'en';
|
|
354
|
+
const h = ctx.getHistory().filter((m) => m.role !== 'system');
|
|
355
|
+
if (h.length < 2) {
|
|
356
|
+
ctx.note(en ? 'Not enough conversation to skillify.' : 'Skill üretmek için yeterli konuşma yok.');
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
ctx.note(en ? 'Capturing this session as a skill…' : 'Bu oturum skill\'e çevriliyor…');
|
|
360
|
+
const sys = ctx.getHistory().find((m) => m.role === 'system');
|
|
361
|
+
const md = await complete([
|
|
362
|
+
sys ?? { role: 'system', content: 'You create reusable skills.' },
|
|
363
|
+
...h,
|
|
364
|
+
{
|
|
365
|
+
role: 'user',
|
|
366
|
+
content: 'Bu oturumdaki tekrar edilebilir süreci yeniden kullanılabilir bir SKILL olarak yakala. ' +
|
|
367
|
+
'SADECE markdown döndür, başında frontmatter olsun:\n' +
|
|
368
|
+
'---\nname: <kısa-kebab-ad>\ndescription: <bir cümle>\ncontext: inline\n---\n' +
|
|
369
|
+
'<adım adım, parametrik, yeniden kullanılabilir talimat gövdesi>\n\n' +
|
|
370
|
+
(arg ? `Kullanıcı notu: ${arg}\n` : '') +
|
|
371
|
+
"Answer in the user's language. Frontmatter alan adları İngilizce kalsın (name/description/context).",
|
|
372
|
+
},
|
|
373
|
+
], C);
|
|
374
|
+
// name'i frontmatter'dan çek
|
|
375
|
+
let name = (arg || '').trim().replace(/[^\w-]/g, '-');
|
|
376
|
+
const m = md.match(/name:\s*(.+)/);
|
|
377
|
+
if (!name && m)
|
|
378
|
+
name = m[1].trim().replace(/[^\w-]/g, '-');
|
|
379
|
+
if (!name)
|
|
380
|
+
name = 'skill-' + Date.now();
|
|
381
|
+
const dir = getSkillsDir();
|
|
382
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
383
|
+
const file = path.join(dir, `${name}.md`);
|
|
384
|
+
fs.writeFileSync(file, md.replace(/^```\w*\n?|```$/g, '').trim() + '\n');
|
|
385
|
+
loadSkills();
|
|
386
|
+
ctx.note((en ? 'Skill created: ' : 'Skill üretildi: ') + `/${name}\n${file}`);
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
case '/mcp': {
|
|
390
|
+
if (arg === 'reconnect' || arg === 'reload') {
|
|
391
|
+
ctx.note('MCP yeniden bağlanıyor…');
|
|
392
|
+
await connectMcpServers();
|
|
393
|
+
}
|
|
394
|
+
const servers = getMcpServers();
|
|
395
|
+
if (!servers.length) {
|
|
396
|
+
ctx.note('Bağlı MCP sunucusu yok.\n' +
|
|
397
|
+
`Yapılandırma: ${getMcpConfigPath()}\n` +
|
|
398
|
+
'Örnek:\n' +
|
|
399
|
+
'{\n "mcpServers": {\n' +
|
|
400
|
+
' "filesystem": { "command": "npx", "args": ["-y","@modelcontextprotocol/server-filesystem","' +
|
|
401
|
+
process.cwd().replace(/\\/g, '\\\\') +
|
|
402
|
+
'"] }\n }\n}\n' +
|
|
403
|
+
'Sonra: /mcp reconnect');
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
ctx.note('MCP sunucuları:\n' +
|
|
407
|
+
servers
|
|
408
|
+
.map((s) => {
|
|
409
|
+
const head = ` ${s.status === 'connected' ? '●' : '✗'} ${s.name} (${s.status})${s.error ? ' — ' + s.error : ''}`;
|
|
410
|
+
const tools = s.toolNames.length ? '\n ' + s.toolNames.join('\n ') : '';
|
|
411
|
+
return head + tools;
|
|
412
|
+
})
|
|
413
|
+
.join('\n') +
|
|
414
|
+
`\n\nYapılandırma: ${getMcpConfigPath()} · yeniden bağlan: /mcp reconnect`);
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
case '/tasks': {
|
|
418
|
+
const list = tasks.list();
|
|
419
|
+
if (!list.length) {
|
|
420
|
+
ctx.note('Aktif/geçmiş görev yok.');
|
|
421
|
+
return true;
|
|
422
|
+
}
|
|
423
|
+
const icon = (s) => (s === 'running' ? '●' : s === 'done' ? '✓' : s === 'error' ? '✗' : '■');
|
|
424
|
+
ctx.note('Görevler:\n' +
|
|
425
|
+
list
|
|
426
|
+
.map((t) => {
|
|
427
|
+
const dur = ((t.endedAt ?? Date.now()) - t.startedAt) / 1000;
|
|
428
|
+
return ` ${icon(t.status)} ${t.id.padEnd(9)} ${t.status.padEnd(8)} ${dur.toFixed(0)}s ${t.label}`;
|
|
429
|
+
})
|
|
430
|
+
.join('\n') +
|
|
431
|
+
'\n\nÇıktı için modele "TaskOutput(<id>)" dedirt veya /kill <id> ile durdur.');
|
|
432
|
+
return true;
|
|
433
|
+
}
|
|
434
|
+
case '/kill': {
|
|
435
|
+
if (!arg) {
|
|
436
|
+
ctx.note('Kullanım: /kill <görev-id> (örn. shell_1, agent_2)');
|
|
437
|
+
return true;
|
|
438
|
+
}
|
|
439
|
+
ctx.note(tasks.stop(arg) ? `Durduruldu: ${arg}` : `Durdurulamadı (çalışmıyor ya da yok): ${arg}`);
|
|
440
|
+
return true;
|
|
441
|
+
}
|
|
442
|
+
case '/learn': {
|
|
443
|
+
const en = getLang() === 'en';
|
|
444
|
+
if (arg === 'on' || arg === 'off') {
|
|
445
|
+
setLearnEnabled(arg === 'on');
|
|
446
|
+
}
|
|
447
|
+
ctx.note((en ? 'Web-learning (training data collection)\n' : 'Web-öğrenme (eğitim datası toplama)\n') +
|
|
448
|
+
` ${en ? 'status' : 'durum'}: ${isLearnEnabled() ? (en ? 'ON' : 'AÇIK') : (en ? 'OFF' : 'KAPALI')} · /learn on|off\n` +
|
|
449
|
+
` ${en ? 'collected' : 'toplanan'}: ${getLearnCount()} ${en ? 'pairs' : 'çift'}\n` +
|
|
450
|
+
` ${en ? 'file' : 'dosya'}: ${getLearnFile()}\n` +
|
|
451
|
+
(en
|
|
452
|
+
? ' → feed this JSONL to modelegitim SFT/LoRA so the model permanently learns.'
|
|
453
|
+
: ' → bu JSONL\'i modelegitim SFT/LoRA\'ya ver, model kalıcı öğrensin.'));
|
|
454
|
+
return true;
|
|
455
|
+
}
|
|
456
|
+
case '/dream': {
|
|
457
|
+
const h = ctx.getHistory();
|
|
458
|
+
const convo = h.filter((m) => m.role !== 'system');
|
|
459
|
+
if (!convo.length) {
|
|
460
|
+
ctx.note('Konsolide edilecek geçmiş yok.');
|
|
461
|
+
return true;
|
|
462
|
+
}
|
|
463
|
+
const task = tasks.create('dream', 'hafıza konsolidasyonu');
|
|
464
|
+
ctx.note(`Dream başlatıldı (${task.id}) — arka planda hafıza güncelleniyor…`);
|
|
465
|
+
const sys = h.find((m) => m.role === 'system');
|
|
466
|
+
(async () => {
|
|
467
|
+
try {
|
|
468
|
+
const memFile = path.join(process.cwd(), '.wormclaude', 'memory.md');
|
|
469
|
+
let prev = '';
|
|
470
|
+
try {
|
|
471
|
+
prev = fs.readFileSync(memFile, 'utf8');
|
|
472
|
+
}
|
|
473
|
+
catch { }
|
|
474
|
+
tasks.append(task.id, 'özetleniyor…\n');
|
|
475
|
+
const out = await complete([
|
|
476
|
+
sys ?? { role: 'system', content: 'You consolidate long-term memory.' },
|
|
477
|
+
...convo,
|
|
478
|
+
{
|
|
479
|
+
role: 'user',
|
|
480
|
+
content: 'Update the long-term memory below with durable facts, decisions, preferences, and project state ' +
|
|
481
|
+
'learned in this conversation. Merge — do not duplicate. Output the FULL updated memory as markdown bullet points.\n\n' +
|
|
482
|
+
'CURRENT MEMORY:\n' + (prev || '(empty)'),
|
|
483
|
+
},
|
|
484
|
+
], C);
|
|
485
|
+
fs.mkdirSync(path.dirname(memFile), { recursive: true });
|
|
486
|
+
fs.writeFileSync(memFile, out);
|
|
487
|
+
tasks.append(task.id, `\nhafıza yazıldı: ${memFile} (${out.length} karakter)`);
|
|
488
|
+
tasks.finish(task.id, 'done');
|
|
489
|
+
}
|
|
490
|
+
catch (e) {
|
|
491
|
+
tasks.append(task.id, `\n[hata: ${e?.message || e}]`);
|
|
492
|
+
tasks.finish(task.id, 'error');
|
|
493
|
+
}
|
|
494
|
+
})();
|
|
495
|
+
return true;
|
|
496
|
+
}
|
|
497
|
+
case '/export': {
|
|
498
|
+
fs.mkdirSync(SESSION_DIR, { recursive: true });
|
|
499
|
+
const file = path.join(SESSION_DIR, `session-${tsStamp()}.json`);
|
|
500
|
+
fs.writeFileSync(file, JSON.stringify(ctx.getHistory(), null, 2));
|
|
501
|
+
const md = path.join(SESSION_DIR, `session-${tsStamp()}.md`);
|
|
502
|
+
const text = ctx
|
|
503
|
+
.getHistory()
|
|
504
|
+
.filter((m) => m.role !== 'system')
|
|
505
|
+
.map((m) => `### ${m.role}\n\n${typeof m.content === 'string' ? m.content : JSON.stringify(m.content)}`)
|
|
506
|
+
.join('\n\n');
|
|
507
|
+
fs.writeFileSync(md, text);
|
|
508
|
+
ctx.note(`Oturum kaydedildi:\n ${file}\n ${md}`);
|
|
509
|
+
return true;
|
|
510
|
+
}
|
|
511
|
+
case '/resume': {
|
|
512
|
+
if (!fs.existsSync(SESSION_DIR)) {
|
|
513
|
+
ctx.note('Kayıtlı oturum yok. /export ile kaydet.');
|
|
514
|
+
return true;
|
|
515
|
+
}
|
|
516
|
+
const files = fs
|
|
517
|
+
.readdirSync(SESSION_DIR)
|
|
518
|
+
.filter((f) => f.endsWith('.json'))
|
|
519
|
+
.map((f) => ({ f, m: fs.statSync(path.join(SESSION_DIR, f)).mtimeMs }))
|
|
520
|
+
.sort((a, b) => b.m - a.m);
|
|
521
|
+
if (!files.length) {
|
|
522
|
+
ctx.note('Kayıtlı oturum yok.');
|
|
523
|
+
return true;
|
|
524
|
+
}
|
|
525
|
+
const latest = path.join(SESSION_DIR, files[0].f);
|
|
526
|
+
try {
|
|
527
|
+
const hist = JSON.parse(fs.readFileSync(latest, 'utf8'));
|
|
528
|
+
ctx.setHistory(hist);
|
|
529
|
+
ctx.note(`Oturum yüklendi: ${files[0].f} (${hist.length} mesaj)`);
|
|
530
|
+
}
|
|
531
|
+
catch (e) {
|
|
532
|
+
ctx.note(`Yükleme hatası: ${e?.message || e}`);
|
|
533
|
+
}
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
536
|
+
default:
|
|
537
|
+
ctx.note(`Bilinmeyen komut: ${cmd} — /help ile listeye bak.`);
|
|
538
|
+
return true;
|
|
539
|
+
}
|
|
540
|
+
}
|
package/dist/compact.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Bağlam kompaksiyonu — Claude Code compact/ mantığının uyarlaması.
|
|
2
|
+
// Manuel /compact ve otomatik (eşik aşılınca) tetikleme aynı çekirdeği kullanır.
|
|
3
|
+
import { completeText } from './agent.js';
|
|
4
|
+
import { approxTokens } from './usage.js';
|
|
5
|
+
// Bağlam penceresi (Qwen 2.5 7B = 32768). Env ile override edilebilir.
|
|
6
|
+
const CONTEXT_WINDOW = Number(process.env.WORMCLAUDE_CONTEXT_WINDOW) || 32768;
|
|
7
|
+
const SUMMARY_RESERVE = 4000; // özet çıktısı için rezerv (CC: ~20K büyük modellerde)
|
|
8
|
+
const AUTOCOMPACT_BUFFER = 2000;
|
|
9
|
+
export function autoCompactThreshold() {
|
|
10
|
+
return CONTEXT_WINDOW - SUMMARY_RESERVE - AUTOCOMPACT_BUFFER;
|
|
11
|
+
}
|
|
12
|
+
export function shouldAutoCompact(history) {
|
|
13
|
+
return approxTokens(history) >= autoCompactThreshold();
|
|
14
|
+
}
|
|
15
|
+
export function contextPercent(history) {
|
|
16
|
+
return Math.min(100, Math.round((approxTokens(history) / CONTEXT_WINDOW) * 100));
|
|
17
|
+
}
|
|
18
|
+
// Backend hatası bağlam/uzunluk taşması mı? → reactive compact tetikler.
|
|
19
|
+
export function isContextError(msg) {
|
|
20
|
+
return /context|prompt.*(long|length)|too long|maximum.*token|413|exceed/i.test(msg || '');
|
|
21
|
+
}
|
|
22
|
+
// Claude Code BASE_COMPACT_PROMPT'un 9-bölümlü uyarlaması.
|
|
23
|
+
export const COMPACT_PROMPT = 'KRİTİK: SADECE METİN İLE YANIT VER. HİÇBİR ARAÇ ÇAĞIRMA.\n\n' +
|
|
24
|
+
'Yukarıdaki konuşmanın detaylı bir özetini oluştur. Teknik detayları, kod desenlerini, ' +
|
|
25
|
+
'mimari kararları ve dosya adlarını koru. Bu özet konuşmanın yerine geçecek, o yüzden ' +
|
|
26
|
+
'devam etmek için gereken her şeyi içermeli.\n\n' +
|
|
27
|
+
'Aşağıdaki 9 bölümü ZORUNLU olarak doldur:\n' +
|
|
28
|
+
'1. Birincil İstek ve Amaç: Kullanıcının ne istediği (detaylı)\n' +
|
|
29
|
+
'2. Anahtar Teknik Kavramlar: Kullanılan teknoloji/framework/desenler\n' +
|
|
30
|
+
'3. Dosyalar ve Kod: Dokunulan dosyalar, önemli kod parçaları, neden önemli oldukları\n' +
|
|
31
|
+
'4. Hatalar ve Düzeltmeler: Karşılaşılan hatalar ve nasıl çözüldükleri\n' +
|
|
32
|
+
'5. Problem Çözme: Çözülen sorunlar ve yaklaşımlar\n' +
|
|
33
|
+
'6. Tüm Kullanıcı Mesajları: Kullanıcının tüm istekleri (araç sonuçları hariç)\n' +
|
|
34
|
+
'7. Bekleyen Görevler: Henüz yapılmamış işler\n' +
|
|
35
|
+
'8. Mevcut Çalışma: En son ne yapılıyordu (detaylı)\n' +
|
|
36
|
+
'9. Sonraki Adım: Varsa, en son kullanıcı isteğine uygun bir sonraki adım\n\n' +
|
|
37
|
+
"Yanıtı kullanıcının dilinde yaz.";
|
|
38
|
+
// Geçmişi özetleyip [system, özet] olarak döndürür.
|
|
39
|
+
export async function runCompact(history, config, focus) {
|
|
40
|
+
const sys = history.find((m) => m.role === 'system');
|
|
41
|
+
const convo = history.filter((m) => m.role !== 'system');
|
|
42
|
+
const msgs = [
|
|
43
|
+
sys ?? { role: 'system', content: 'You are WormClaude.' },
|
|
44
|
+
...convo,
|
|
45
|
+
{ role: 'user', content: (focus ? `Özellikle şuna odaklan: ${focus}\n\n` : '') + COMPACT_PROMPT },
|
|
46
|
+
];
|
|
47
|
+
const summary = await completeText(msgs, config);
|
|
48
|
+
const newHist = [
|
|
49
|
+
sys ?? { role: 'system', content: 'You are WormClaude.' },
|
|
50
|
+
{ role: 'user', content: 'Önceki konuşmanın özeti (bağlam):\n\n' + summary },
|
|
51
|
+
];
|
|
52
|
+
return { history: newHist, summary };
|
|
53
|
+
}
|