wormclaude 1.0.47 → 1.0.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/theme.js CHANGED
@@ -16,4 +16,4 @@ export const theme = {
16
16
  synType: '#a78bfa', // tip/sınıf adları, sabitler
17
17
  synProp: '#e0e0e0', // özellik/anahtar adları
18
18
  };
19
- export const VERSION = '1.0.47';
19
+ export const VERSION = '1.0.49';
package/dist/tui.js CHANGED
@@ -6,13 +6,20 @@
6
6
  import readline from 'node:readline';
7
7
  import stringWidth from 'string-width';
8
8
  import { loadConfig, streamChat, fetchAccount } from './api.js';
9
- import { allToolSchemas, executeToolCalls, toolLabel, setToolConfig } from './tools.js';
9
+ import { allToolSchemas, executeToolCalls, executeTool, toolLabel, setToolConfig } from './tools.js';
10
10
  import { sanitizeOutput } from './errorsan.js';
11
11
  import { itemAnsi } from './ansi.js';
12
12
  import { theme, VERSION } from './theme.js';
13
13
  import { cleanModelText } from './textclean.js';
14
14
  import { COMMANDS, runSlashCommand } from './commands.js';
15
15
  import { cmdDesc } from './i18n.js';
16
+ import { getSkill, getSkills, buildSkillPrompt } from './skills.js';
17
+ import { getExtCommand, getExtCommands, buildExtCommandPrompt } from './extensions.js';
18
+ import { resolveAtMentions } from './atmention.js';
19
+ import { loadMemoryContext, shouldExtract, triggerMemory } from './memory.js';
20
+ import { shouldAutoCompact, runCompact } from './compact.js';
21
+ import { recordLearned } from './learn.js';
22
+ import { connectMcpServers } from './mcp.js';
16
23
  const RESET = '\x1b[0m';
17
24
  const hex = (h) => { const m = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(h); return m ? `\x1b[38;2;${parseInt(m[1], 16)};${parseInt(m[2], 16)};${parseInt(m[3], 16)}m` : ''; };
18
25
  const paint = (s, c, bold = false) => `${bold ? '\x1b[1m' : ''}${c ? hex(c) : ''}${s}${RESET}`;
@@ -49,21 +56,41 @@ export async function runTui() {
49
56
  const _winNote = process.platform === 'win32'
50
57
  ? ' This is WINDOWS. Write files to the CURRENT directory (relative paths) or under C:\\Users\\... — NEVER use /home/user or other Unix paths. The Bash tool runs via cmd.exe (use Windows commands).'
51
58
  : ' Use POSIX paths.';
59
+ const _memCtx = loadMemoryContext(); // WORMCLAUDE.md + .wormclaude/memory.md
60
+ const _sysCount = _memCtx ? 2 : 1; // başlangıç sistem mesajı sayısı (clear bunları korur)
52
61
  const history = [
53
62
  { role: 'system', content: `ENVIRONMENT: The user machine runs ${_plat}. Current working directory: ${process.cwd()}.${_winNote}` },
63
+ ...(_memCtx ? [{ role: 'system', content: _memCtx }] : []),
54
64
  ];
55
65
  const displayItems = [{ kind: 'banner' }];
56
66
  let inputBuf = '', busy = false, streamChars = 0, spin = 0;
57
67
  const SPIN = ['·', '✢', '✳', '✶', '✻', '✽', '✶', '✳', '✢'];
58
- // Footer yüksekliği DİNAMİK: izin=4, slash menü=N+3 (menü satırları + çizgi/giriş/çizgi), normal=4.
68
+ // Giriş kutusunu genişliğe göre ÇOK SATIRA sar (uzun mesaj/kod sağa taşmaz, alta iner).
69
+ const MAX_INPUT_LINES = 10;
70
+ const inputBoxLines = (W) => {
71
+ const inner = Math.max(4, W - 2); // "✶ " önek payı
72
+ const wrapped = [];
73
+ let cur = '';
74
+ for (const ch of inputBuf) {
75
+ if (vis(cur + ch) > inner) {
76
+ wrapped.push(cur);
77
+ cur = ch;
78
+ }
79
+ else
80
+ cur += ch;
81
+ }
82
+ wrapped.push(cur);
83
+ const shown = wrapped.length > MAX_INPUT_LINES ? wrapped.slice(-MAX_INPUT_LINES) : wrapped; // çok uzunsa sonu göster
84
+ return shown.map((ln, i) => (i === 0 ? paint('✶ ', theme.redBright, true) : ' ') + paint(ln, theme.white) + (i === shown.length - 1 ? paint('▌', theme.greyDim) : ''));
85
+ };
86
+ // Footer yüksekliği DİNAMİK: izin=4; değilse (durum/menü) + çizgi + giriş-satırları + çizgi.
59
87
  let prevFH = 4;
60
88
  const footerHeight = () => {
61
89
  if (perm)
62
90
  return 4;
63
91
  const m = cmdMatches();
64
- if (m.length)
65
- return Math.min(8, m.length) + 3;
66
- return 4;
92
+ const statusLines = m.length ? Math.min(8, m.length) : 1;
93
+ return statusLines + 2 + inputBoxLines(Math.max(8, cols())).length;
67
94
  };
68
95
  // Yükseklik değiştiyse scroll-region'ı + içeriği yeniden bas; değişmediyse sadece footer'ı çiz.
69
96
  const refresh = () => { const fh = footerHeight(); if (fh !== prevFH)
@@ -77,12 +104,18 @@ export async function runTui() {
77
104
  const inputHistory = []; // gönderilen mesajlar (↑/↓ ile geri çağrılır)
78
105
  let histIdx = -1; // -1 = geçmişte gezinmiyor
79
106
  let cmdSel = 0; // slash menüde seçili komut
107
+ // Tüm komutlar: yerleşik + skill + extension
108
+ const allCmds = () => [
109
+ ...COMMANDS,
110
+ ...getSkills().map((s) => ({ name: '/' + s.name, desc: s.description })),
111
+ ...getExtCommands().map((c) => ({ name: '/' + c.name, desc: c.description })),
112
+ ];
80
113
  // Slash menü: "/" ile başlayan girişe uyan komutlar
81
114
  const cmdMatches = () => {
82
115
  if (busy || !inputBuf.startsWith('/'))
83
116
  return [];
84
117
  const tok = inputBuf.split(' ')[0];
85
- return COMMANDS.filter((c) => c.name.startsWith(tok));
118
+ return allCmds().filter((c) => c.name.startsWith(tok));
86
119
  };
87
120
  // Slash komutları için bağlam (cli.tsx ile aynı arayüz) — /clear,/config,/model,/memory,/program…
88
121
  const cmdCtx = {
@@ -91,7 +124,7 @@ export async function runTui() {
91
124
  setHistory: (h) => { history.length = 0; history.push(...h); },
92
125
  note: (text) => printItem({ kind: 'note', text }),
93
126
  assistant: (text) => printItem({ kind: 'assistant', text }),
94
- clearConv: () => { history.length = 1; displayItems.length = 1; redrawAll(); }, // sistem msg + banner kalır
127
+ clearConv: () => { history.length = _sysCount; displayItems.length = 1; redrawAll(); }, // sistem mesajları + banner kalır
95
128
  exit: () => quit(),
96
129
  };
97
130
  async function runCommand(v) {
@@ -150,11 +183,7 @@ export async function runTui() {
150
183
  body = [line, q, opts, line];
151
184
  }
152
185
  else {
153
- let shown = inputBuf;
154
- const avail = W - 3;
155
- if (vis(shown) > avail)
156
- shown = '…' + shown.slice(-(avail - 1));
157
- const inputLine = paint('✶ ', theme.redBright, true) + paint(shown, theme.white) + paint('▌', theme.greyDim);
186
+ const inputLines = inputBoxLines(W); // çok-satırlı giriş (sarılmış)
158
187
  const m = cmdMatches();
159
188
  if (m.length) {
160
189
  // DİKEY slash menü (alt alta), seçili kırmızı — kutunun üstünde.
@@ -164,13 +193,13 @@ export async function runTui() {
164
193
  const on = i === sel;
165
194
  return ' ' + paint((on ? '✶ ' : ' ') + c.name.padEnd(13), on ? theme.redBright : theme.grey) + paint(cmdDesc(c.name) || c.desc || '', theme.greyDim);
166
195
  });
167
- body = [...menu, line, inputLine, line];
196
+ body = [...menu, line, ...inputLines, line];
168
197
  }
169
198
  else {
170
199
  const status = busy
171
200
  ? paint(` ${SPIN[spin % SPIN.length]} çalışıyor…${streamChars ? ' ' + streamChars + ' karakter' : ''}`, theme.grey)
172
201
  : paint(' / komutlar · ↑↓ geçmiş · /kopyala · Ctrl+C çıkış', theme.greyDim);
173
- body = [status, line, inputLine, line];
202
+ body = [status, line, ...inputLines, line];
174
203
  }
175
204
  }
176
205
  let out = '\x1b7'; // imleci kaydet
@@ -195,16 +224,28 @@ export async function runTui() {
195
224
  const printItem = (it) => { displayItems.push(it); process.stdout.write(itemAnsi(it, cols()) + '\n'); refresh(); };
196
225
  // ── Agent döngüsü (M2: araçlar + izin). Model tool çağırırsa izin sorulup çalıştırılır,
197
226
  // sonuç geçmişe eklenip döngü devam eder; tool yoksa biter. ──
198
- async function runTurn(userText) {
227
+ async function runTurn(userText, displayText) {
199
228
  busy = true;
200
229
  streamChars = 0;
230
+ if (displayText !== undefined)
231
+ printItem({ kind: 'user', text: displayText }); // skill/@mention: gösterim farklı
201
232
  history.push({ role: 'user', content: userText });
202
233
  const timer = setInterval(() => { spin++; if (busy && !perm)
203
234
  refresh(); }, 120);
204
- let lastSig = '', sameCount = 0;
235
+ let lastSig = '', sameCount = 0, usedWeb = false, lastAnswer = '';
205
236
  try {
206
237
  for (let iter = 0; iter < 25; iter++) {
207
238
  streamChars = 0;
239
+ // Oto-compact: bağlam eşiği aşılınca otomatik özetle (uzun sohbet patlamasın)
240
+ if (shouldAutoCompact(history)) {
241
+ try {
242
+ const { history: nh } = await runCompact(history, config);
243
+ history.length = 0;
244
+ history.push(...nh);
245
+ printItem({ kind: 'note', text: '✎ bağlam otomatik özetlendi' });
246
+ }
247
+ catch { }
248
+ }
208
249
  let answer = '';
209
250
  let toolCalls = [];
210
251
  for await (const ev of streamChat(history, allToolSchemas(), config)) {
@@ -224,8 +265,10 @@ export async function runTui() {
224
265
  if (toolCalls.length)
225
266
  am.tool_calls = toolCalls.map((t) => ({ id: t.id, type: 'function', function: { name: t.name, arguments: t.args || '{}' } }));
226
267
  history.push(am);
227
- if (answer)
268
+ if (answer) {
269
+ lastAnswer = answer;
228
270
  printItem({ kind: 'assistant', text: answer });
271
+ }
229
272
  if (!toolCalls.length)
230
273
  break;
231
274
  // takılma koruması: aynı araç-çağrısı 3 kez üst üste → dur
@@ -247,7 +290,8 @@ export async function runTui() {
247
290
  perm = { label: toolLabel(c.name, args), name: c.name, resolve };
248
291
  refresh();
249
292
  }),
250
- onResult: (c, _i, res) => printItem({ kind: 'tool', label: toolLabel(c.name, res.args), result: sanitizeOutput(res.output), ok: res.ok }),
293
+ onResult: (c, _i, res) => { if ((c.name === 'WebSearch' || c.name === 'WebFetch') && res.ok)
294
+ usedWeb = true; printItem({ kind: 'tool', label: toolLabel(c.name, res.args), result: sanitizeOutput(res.output), ok: res.ok }); },
251
295
  });
252
296
  for (let i = 0; i < toolCalls.length; i++)
253
297
  history.push({ role: 'tool', tool_call_id: toolCalls[i].id, content: (results[i].output || '').slice(0, 8000) });
@@ -260,6 +304,15 @@ export async function runTui() {
260
304
  busy = false;
261
305
  perm = null;
262
306
  refresh();
307
+ // Web-öğrenme: web'de arayıp cevap ürettiyse {soru→cevap}'ı eğitim datasına ekle
308
+ if (usedWeb && lastAnswer) {
309
+ const sources = (lastAnswer.match(/https?:\/\/[^\s<>"')\]]+/g) || []).slice(0, 8);
310
+ if (recordLearned(userText, lastAnswer, sources, config))
311
+ printItem({ kind: 'note', text: '✎ eğitim datasına eklendi' });
312
+ }
313
+ // Oto-hafıza: eşik geçildiyse arka planda hafızayı güncelle
314
+ if (shouldExtract(history))
315
+ triggerMemory(history, config);
263
316
  }
264
317
  // ── Kurulum ──
265
318
  readline.emitKeypressEvents(process.stdin);
@@ -275,6 +328,8 @@ export async function runTui() {
275
328
  account = a;
276
329
  redrawAll();
277
330
  } }).catch(() => { });
331
+ connectMcpServers().then((srv) => { if (srv && srv.length)
332
+ printItem({ kind: 'note', text: `🔌 ${srv.length} MCP sunucusu bağlandı` }); }).catch(() => { }); // MCP araçları (varsa)
278
333
  const quit = () => { process.stdout.write('\x1b[r\x1b[?25h\x1b[2J\x1b[3J\x1b[H'); process.exit(0); };
279
334
  process.on('exit', () => { try {
280
335
  process.stdout.write('\x1b[r\x1b[?25h');
@@ -381,18 +436,35 @@ export async function runTui() {
381
436
  refresh();
382
437
  return;
383
438
  }
384
- // Slash komutu: tam eşleşme yoksa menüde SEÇİLİ olanı kullan
439
+ inputHistory.push(v);
440
+ // ! shell modu — LLM'siz doğrudan shell komutu; çıktıyı modele bağlam olarak ekle
441
+ if (v.startsWith('!') && v.length > 1) {
442
+ const cmd = v.slice(1).trim();
443
+ printItem({ kind: 'user', text: v });
444
+ busy = true;
445
+ refresh();
446
+ executeTool('Bash', { command: cmd }).then((res) => {
447
+ printItem({ kind: 'tool', label: `! ${cmd.slice(0, 60)}`, result: sanitizeOutput(res.output), ok: res.ok });
448
+ history.push({ role: 'user', content: `Şu shell komutunu çalıştırdım:\n\`\`\`\n${cmd}\n\`\`\`\nÇıktı:\n\`\`\`\n${(res.output || '').slice(0, 4000)}\n\`\`\`` });
449
+ }).catch((e) => printItem({ kind: 'note', text: 'Komut hatası: ' + (e?.message || e) }))
450
+ .finally(() => { busy = false; refresh(); });
451
+ return;
452
+ }
385
453
  if (v.startsWith('/')) {
386
- const tok = v.split(' ')[0];
387
- const matches = COMMANDS.filter((c) => c.name.startsWith(tok));
388
- if (!COMMANDS.some((c) => c.name === tok) && matches.length)
389
- v = matches[Math.min(cmdSel, matches.length - 1)].name;
454
+ const tok0 = v.split(' ')[0];
455
+ // tam eşleşme yoksa menüde SEÇİLİ olanı kullan (skill/ext dahil), argümanları koru
456
+ if (!allCmds().some((c) => c.name === tok0)) {
457
+ const ms = allCmds().filter((c) => c.name.startsWith(tok0));
458
+ if (ms.length)
459
+ v = ms[Math.min(cmdSel, ms.length - 1)].name + v.slice(tok0.length);
460
+ }
390
461
  cmdSel = 0;
391
- if (v === '/cikis' || v === '/exit' || v === '/quit') {
462
+ const tok = v.split(' ')[0];
463
+ if (tok === '/cikis' || tok === '/exit' || tok === '/quit') {
392
464
  quit();
393
465
  return;
394
466
  }
395
- if (v === '/kopyala' || v === '/copy') {
467
+ if (tok === '/kopyala' || tok === '/copy') {
396
468
  const last = [...history].reverse().find((mm) => mm.role === 'assistant');
397
469
  if (last) {
398
470
  process.stdout.write(`\x1b]52;c;${Buffer.from(last.content, 'utf8').toString('base64')}\x07`);
@@ -402,13 +474,29 @@ export async function runTui() {
402
474
  printItem({ kind: 'note', text: 'Kopyalanacak yanıt yok.' });
403
475
  return;
404
476
  }
405
- inputHistory.push(v);
477
+ const builtin = COMMANDS.some((c) => c.name === tok);
478
+ const skill = !builtin ? getSkill(tok.slice(1)) : undefined;
479
+ if (skill) {
480
+ const a = v.slice(tok.length).trim();
481
+ runTurn(buildSkillPrompt(skill, a), `/${skill.name}${a ? ' ' + a : ''}`);
482
+ return;
483
+ }
484
+ const ext = (!builtin && !skill) ? getExtCommand(tok.slice(1)) : undefined;
485
+ if (ext) {
486
+ const a = v.slice(tok.length).trim();
487
+ runTurn(buildExtCommandPrompt(ext, a), `/${ext.name}${a ? ' ' + a : ''}`);
488
+ return;
489
+ }
406
490
  runCommand(v);
407
491
  return;
408
492
  }
409
- inputHistory.push(v); // geçmişe ekle (↑ ile geri çağrılır)
410
- printItem({ kind: 'user', text: v });
411
- runTurn(v);
493
+ // @dosya mention referanslanan dosya içeriğini modele enjekte et, kullanıcıya orijinali göster
494
+ const at = resolveAtMentions(v);
495
+ if (at.files.length) {
496
+ runTurn(at.augmented, v);
497
+ return;
498
+ }
499
+ runTurn(v, v);
412
500
  return;
413
501
  }
414
502
  if (key && key.name === 'backspace') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wormclaude",
3
- "version": "1.0.47",
3
+ "version": "1.0.49",
4
4
  "description": "WormClaude CLI - uncensored security+code assistant (ink TUI, Claude-style)",
5
5
  "type": "module",
6
6
  "bin": {