wormclaude 1.0.52 → 1.0.54

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/ansi.js CHANGED
@@ -81,7 +81,7 @@ export function bannerAnsi(cols) {
81
81
  return body + '\n' + paint(' ' + t('banner.subtitle'), theme.greyDim);
82
82
  }
83
83
  // ── Markdown bloğu → ANSI (kod blokları sözdizimi-vurgulu) ──
84
- function markdownAnsi(text, cols) {
84
+ export function markdownAnsi(text, cols) {
85
85
  const W = Math.max(20, cols - 2);
86
86
  const lines = text.split('\n');
87
87
  const out = [];
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.52';
19
+ export const VERSION = '1.0.54';
package/dist/tui.js CHANGED
@@ -8,7 +8,7 @@ import stringWidth from 'string-width';
8
8
  import { loadConfig, streamChat, fetchAccount } from './api.js';
9
9
  import { allToolSchemas, executeToolCalls, executeTool, toolLabel, setToolConfig } from './tools.js';
10
10
  import { sanitizeOutput } from './errorsan.js';
11
- import { itemAnsi } from './ansi.js';
11
+ import { itemAnsi, markdownAnsi } 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';
@@ -64,11 +64,10 @@ export async function runTui() {
64
64
  ];
65
65
  const displayItems = [{ kind: 'banner' }];
66
66
  let inputBuf = '', busy = false, streamChars = 0, spin = 0;
67
- let streamPreview = ''; // canlı akış önizlemesi (footer'da)
68
- const PREVIEW_LINES = 6; // akışta sabit önizleme satırı (yükseklik stabil → titreme yok)
67
+ // Canlı akış artık FOOTER'da DEĞİL içerik akışına (mesajın altından aşağı) satır-satır basılır.
69
68
  const SPIN = ['·', '✢', '✳', '✶', '✻', '✽', '✶', '✳', '✢'];
70
- // Giriş kutusunu genişliğe göre ÇOK SATIRA sar (uzun mesaj/kod sağa taşmaz, alta iner).
71
- const MAX_INPUT_LINES = 10;
69
+ // Giriş kutusunu genişliğe göre ÇOK SATIRA sar; çok uzunsa (yapıştırma) KISALTIP özet gösterir.
70
+ const MAX_INPUT_LINES = 6;
72
71
  const inputBoxLines = (W) => {
73
72
  const inner = Math.max(4, W - 2); // "✶ " önek payı
74
73
  const wrapped = [];
@@ -82,8 +81,14 @@ export async function runTui() {
82
81
  cur += ch;
83
82
  }
84
83
  wrapped.push(cur);
85
- const shown = wrapped.length > MAX_INPUT_LINES ? wrapped.slice(-MAX_INPUT_LINES) : wrapped; // çok uzunsa sonu göster
86
- return shown.map((ln, i) => (i === 0 ? paint('✶ ', theme.redBright, true) : ' ') + paint(ln, theme.white) + (i === shown.length - 1 ? paint('▌', theme.greyDim) : ''));
84
+ if (wrapped.length <= MAX_INPUT_LINES) {
85
+ return wrapped.map((ln, i) => (i === 0 ? paint('✶ ', theme.redBright, true) : ' ') + paint(ln, theme.white) + (i === wrapped.length - 1 ? paint('▌', theme.greyDim) : ''));
86
+ }
87
+ // Çok uzun (büyük yapıştırma): son birkaç satır + üstte özet — footer'ı doldurmaz.
88
+ const extra = wrapped.length - (MAX_INPUT_LINES - 1);
89
+ const tail = wrapped.slice(-(MAX_INPUT_LINES - 1));
90
+ const head = paint(`✶ …(+${extra} satır · ${inputBuf.length} karakter, Enter ile gönder)`, theme.greyDim);
91
+ return [head, ...tail.map((ln, i) => ' ' + paint(ln, theme.white) + (i === tail.length - 1 ? paint('▌', theme.greyDim) : ''))];
87
92
  };
88
93
  // Footer yüksekliği DİNAMİK: izin=4; değilse (durum/menü) + çizgi + giriş-satırları + çizgi.
89
94
  // SABİT rezerve footer alanı (scroll region bir kez ayarlanır → churn/duplicate yok). Footer bu
@@ -91,6 +96,11 @@ export async function runTui() {
91
96
  const FOOTER_MAX = () => Math.min(11, Math.max(5, rows() - 6));
92
97
  // refresh = sadece footer'ı çiz (region sabit; içerik yeniden BASILMAZ → duplicate olmaz).
93
98
  const refresh = () => drawFooter();
99
+ // DEBOUNCE: hızlı yazma/yapıştırma akınında her tuşta DEĞİL, tick başına BİR KEZ çiz. Yoksa
100
+ // her karakter tam footer redraw'u tetikleyip render'ı kuyruğa biriktiriyor (elini çeksen de gidiyor).
101
+ let _fpending = false;
102
+ const scheduleFooter = () => { if (_fpending)
103
+ return; _fpending = true; setImmediate(() => { _fpending = false; drawFooter(); }); };
94
104
  setToolConfig(config); // araçlar (Write/Bash/Edit…) aynı config'i kullansın
95
105
  // İzin (araç onayı) + soru (AskUserQuestion) durumu
96
106
  let perm = null;
@@ -185,16 +195,7 @@ export async function runTui() {
185
195
  else {
186
196
  const inputLines = inputBoxLines(W); // çok-satırlı giriş (sarılmış)
187
197
  const m = cmdMatches();
188
- if (busy && streamPreview) {
189
- // CANLI akış önizlemesi (son PREVIEW_LINES satır, sabit yükseklik)
190
- const pl = streamPreview.split('\n');
191
- const tail = pl.slice(-PREVIEW_LINES);
192
- while (tail.length < PREVIEW_LINES)
193
- tail.push('');
194
- const prev = tail.map((l, i) => (i === 0 ? paint('✶ ', theme.redBright, true) : ' ') + paint(l, theme.white));
195
- body = [...prev, line, ...inputLines, line];
196
- }
197
- else if (m.length) {
198
+ if (m.length) {
198
199
  // DİKEY slash menü — KAYAN pencere (8 satır, seçimi takip eder), seçili kırmızı.
199
200
  const sel = Math.min(cmdSel, m.length - 1);
200
201
  const WIN = 8;
@@ -243,7 +244,6 @@ export async function runTui() {
243
244
  async function runTurn(userText, displayText) {
244
245
  busy = true;
245
246
  streamChars = 0;
246
- streamPreview = '';
247
247
  if (displayText !== undefined)
248
248
  printItem({ kind: 'user', text: displayText }); // skill/@mention: gösterim farklı
249
249
  history.push({ role: 'user', content: userText });
@@ -253,7 +253,6 @@ export async function runTui() {
253
253
  try {
254
254
  for (let iter = 0; iter < 25; iter++) {
255
255
  streamChars = 0;
256
- streamPreview = '';
257
256
  // Oto-compact: bağlam eşiği aşılınca otomatik özetle (uzun sohbet patlamasın)
258
257
  if (shouldAutoCompact(history)) {
259
258
  try {
@@ -266,28 +265,59 @@ export async function runTui() {
266
265
  }
267
266
  let answer = '';
268
267
  let toolCalls = [];
268
+ // ── CANLI akış İÇERİK AKIŞINA (footer'a değil): tamamlanan satırları, mesajın altından
269
+ // aşağı doğru, formatlı (kod renkli) bas. Açık kod-bloğu kapanana dek bekle (yarım fence
270
+ // bozuk renklenmesin). ✶ öneki yalnız bu cevabın İLK satırına; gerisi " " ile hizalı. ──
271
+ let committed = 0, emittedFirst = false, leadEmitted = false;
272
+ const flushStream = (final) => {
273
+ const lines = answer.split('\n');
274
+ let cut;
275
+ if (final)
276
+ cut = answer.length;
277
+ else {
278
+ let pos = 0, fence = false, lastSafe = 0;
279
+ for (let k = 0; k < lines.length - 1; k++) { // son eleman = yarım satır (newline gelmedi)
280
+ pos += lines[k].length + 1;
281
+ if (/^ *(`{3,}|~{3,})/.test(lines[k]))
282
+ fence = !fence;
283
+ if (!fence)
284
+ lastSafe = pos;
285
+ }
286
+ cut = lastSafe;
287
+ }
288
+ if (cut <= committed)
289
+ return;
290
+ const chunk = cleanModelText(answer.slice(committed, cut)).replace(/\n+$/, '');
291
+ committed = cut;
292
+ if (!chunk && !final)
293
+ return;
294
+ const md = markdownAnsi(chunk, cols()).split('\n');
295
+ let out = leadEmitted ? '' : (leadEmitted = true, '\n'); // cevabın üstüne 1 ayraç satır
296
+ out += md.map((ln) => emittedFirst ? ' ' + ln : (emittedFirst = true, paint('✶ ', theme.redBright, true) + ln)).join('\n') + '\n';
297
+ process.stdout.write(out); // imleç içerik bölgesinde → aşağı akar, taşınca scrollback'e
298
+ refresh(); // footer'ı dibe yeniden sabitle
299
+ };
269
300
  for await (const ev of streamChat(history, allToolSchemas(), config)) {
270
- // CANLI akış: answer biriktir + streamPreview güncelle. drawFooter'ı 120ms timer çizer (titreme yok).
271
301
  if (ev.type === 'text') {
272
302
  answer += ev.text;
273
303
  streamChars = answer.length;
274
- streamPreview = cleanModelText(answer);
304
+ flushStream(false);
275
305
  }
276
306
  else if (ev.type === 'done')
277
307
  toolCalls = ev.toolCalls || [];
278
- else if (ev.type === 'error')
308
+ else if (ev.type === 'error') {
279
309
  answer += `\n[hata: ${ev.error}]`;
310
+ flushStream(false);
311
+ }
280
312
  }
313
+ flushStream(true); // kalan yarım satırı bas
281
314
  answer = cleanModelText(answer).trim();
282
- streamPreview = ''; // canlı önizlemeyi kapat → footer küçülür, formatlı cevap içeriğe basılır
283
315
  const am = { role: 'assistant', content: answer || '' };
284
316
  if (toolCalls.length)
285
317
  am.tool_calls = toolCalls.map((t) => ({ id: t.id, type: 'function', function: { name: t.name, arguments: t.args || '{}' } }));
286
318
  history.push(am);
287
- if (answer) {
288
- lastAnswer = answer;
289
- printItem({ kind: 'assistant', text: answer });
290
- }
319
+ if (answer)
320
+ lastAnswer = answer; // gösterim akışta yapıldı; tekrar printItem YOK (duplikasyon olmaz)
291
321
  if (!toolCalls.length)
292
322
  break;
293
323
  // takılma koruması: aynı araç-çağrısı 3 kez üst üste → dur
@@ -324,7 +354,6 @@ export async function runTui() {
324
354
  busy = false;
325
355
  perm = null;
326
356
  ask = null;
327
- streamPreview = '';
328
357
  refresh();
329
358
  // Web-öğrenme: web'de arayıp cevap ürettiyse {soru→cevap}'ı eğitim datasına ekle
330
359
  if (usedWeb && lastAnswer) {
@@ -558,7 +587,7 @@ export async function runTui() {
558
587
  if (key && key.name === 'backspace') {
559
588
  inputBuf = inputBuf.slice(0, -1);
560
589
  cmdSel = 0;
561
- refresh();
590
+ scheduleFooter();
562
591
  return;
563
592
  }
564
593
  if (key && key.name === 'escape') {
@@ -570,7 +599,7 @@ export async function runTui() {
570
599
  if (str && !key?.ctrl && !key?.meta && !str.startsWith('\x1b') && !/[\x00-\x1f]/.test(str)) {
571
600
  inputBuf += str;
572
601
  cmdSel = 0;
573
- refresh();
602
+ scheduleFooter();
574
603
  }
575
604
  });
576
605
  // resize → settle sonrası her şeyi yeni boyutta yeniden bas (region + banner + footer)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wormclaude",
3
- "version": "1.0.52",
3
+ "version": "1.0.54",
4
4
  "description": "WormClaude CLI - uncensored security+code assistant (ink TUI, Claude-style)",
5
5
  "type": "module",
6
6
  "bin": {