wormclaude 1.0.49 → 1.0.51

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.49';
19
+ export const VERSION = '1.0.51';
package/dist/tui.js CHANGED
@@ -64,6 +64,8 @@ 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
69
  const SPIN = ['·', '✢', '✳', '✶', '✻', '✽', '✶', '✳', '✢'];
68
70
  // Giriş kutusunu genişliğe göre ÇOK SATIRA sar (uzun mesaj/kod sağa taşmaz, alta iner).
69
71
  const MAX_INPUT_LINES = 10;
@@ -88,9 +90,14 @@ export async function runTui() {
88
90
  const footerHeight = () => {
89
91
  if (perm)
90
92
  return 4;
93
+ if (ask)
94
+ return ask.options.length + 3; // çizgi + soru + seçenekler... aslında: line+q+opts+line
95
+ const il = inputBoxLines(Math.max(8, cols())).length;
96
+ if (busy && streamPreview)
97
+ return PREVIEW_LINES + 2 + il; // canlı akış önizlemesi
91
98
  const m = cmdMatches();
92
99
  const statusLines = m.length ? Math.min(8, m.length) : 1;
93
- return statusLines + 2 + inputBoxLines(Math.max(8, cols())).length;
100
+ return statusLines + 2 + il;
94
101
  };
95
102
  // Yükseklik değiştiyse scroll-region'ı + içeriği yeniden bas; değişmediyse sadece footer'ı çiz.
96
103
  const refresh = () => { const fh = footerHeight(); if (fh !== prevFH)
@@ -98,8 +105,9 @@ export async function runTui() {
98
105
  else
99
106
  drawFooter(); };
100
107
  setToolConfig(config); // araçlar (Write/Bash/Edit…) aynı config'i kullansın
101
- // İzin (araç onayı) durumu
108
+ // İzin (araç onayı) + soru (AskUserQuestion) durumu
102
109
  let perm = null;
110
+ let ask = null;
103
111
  const allowAll = new Set(); // "Tümüne izin" denen araçlar (oturum boyu)
104
112
  const inputHistory = []; // gönderilen mesajlar (↑/↓ ile geri çağrılır)
105
113
  let histIdx = -1; // -1 = geçmişte gezinmiyor
@@ -182,15 +190,32 @@ export async function runTui() {
182
190
  + paint('[T] Tümüne izin', theme.grey) + paint(' · ', theme.greyDim) + paint('[H] Hayır', theme.grey);
183
191
  body = [line, q, opts, line];
184
192
  }
193
+ else if (ask) {
194
+ // Soru dialogu (AskUserQuestion): soru + numaralı seçenekler
195
+ const q = paint(' ? ', theme.redBright, true) + paint(ask.question, theme.white);
196
+ const opts = ask.options.map((o, i) => ' ' + paint((i === ask.sel ? '✶ ' : ' ') + (i + 1) + '. ' + o.label, i === ask.sel ? theme.redBright : theme.grey) + (o.description ? paint(' ' + o.description, theme.greyDim) : ''));
197
+ body = [line, q, ...opts, line];
198
+ }
185
199
  else {
186
200
  const inputLines = inputBoxLines(W); // çok-satırlı giriş (sarılmış)
187
201
  const m = cmdMatches();
188
- if (m.length) {
189
- // DİKEY slash menü (alt alta), seçili kırmızı — kutunun üstünde.
190
- const items = m.slice(0, 8);
191
- const sel = Math.min(cmdSel, items.length - 1);
192
- const menu = items.map((c, i) => {
193
- const on = i === sel;
202
+ if (busy && streamPreview) {
203
+ // CANLI akış önizlemesi (son PREVIEW_LINES satır, sabit yükseklik)
204
+ const pl = streamPreview.split('\n');
205
+ const tail = pl.slice(-PREVIEW_LINES);
206
+ while (tail.length < PREVIEW_LINES)
207
+ tail.push('');
208
+ const prev = tail.map((l, i) => (i === 0 ? paint('✶ ', theme.redBright, true) : ' ') + paint(l, theme.white));
209
+ body = [...prev, line, ...inputLines, line];
210
+ }
211
+ else if (m.length) {
212
+ // DİKEY slash menü — KAYAN pencere (8 satır, seçimi takip eder), seçili kırmızı.
213
+ const sel = Math.min(cmdSel, m.length - 1);
214
+ const WIN = 8;
215
+ const start = Math.max(0, Math.min(sel - 3, m.length - WIN));
216
+ const view = m.slice(start, start + WIN);
217
+ const menu = view.map((c, i) => {
218
+ const on = (start + i) === sel;
194
219
  return ' ' + paint((on ? '✶ ' : ' ') + c.name.padEnd(13), on ? theme.redBright : theme.grey) + paint(cmdDesc(c.name) || c.desc || '', theme.greyDim);
195
220
  });
196
221
  body = [...menu, line, ...inputLines, line];
@@ -227,15 +252,17 @@ export async function runTui() {
227
252
  async function runTurn(userText, displayText) {
228
253
  busy = true;
229
254
  streamChars = 0;
255
+ streamPreview = '';
230
256
  if (displayText !== undefined)
231
257
  printItem({ kind: 'user', text: displayText }); // skill/@mention: gösterim farklı
232
258
  history.push({ role: 'user', content: userText });
233
- const timer = setInterval(() => { spin++; if (busy && !perm)
259
+ const timer = setInterval(() => { spin++; if (busy && !perm && !ask)
234
260
  refresh(); }, 120);
235
261
  let lastSig = '', sameCount = 0, usedWeb = false, lastAnswer = '';
236
262
  try {
237
263
  for (let iter = 0; iter < 25; iter++) {
238
264
  streamChars = 0;
265
+ streamPreview = '';
239
266
  // Oto-compact: bağlam eşiği aşılınca otomatik özetle (uzun sohbet patlamasın)
240
267
  if (shouldAutoCompact(history)) {
241
268
  try {
@@ -249,11 +276,11 @@ export async function runTui() {
249
276
  let answer = '';
250
277
  let toolCalls = [];
251
278
  for await (const ev of streamChat(history, allToolSchemas(), config)) {
252
- // NOT: token başına drawFooter ÇAĞIRMA (uzun cevapta binlerce çizim titreme/kaybolma).
253
- // streamChars güncellenir; footer'ı 120ms'lik spinTimer çizer; yazma anında keypress çizer.
279
+ // CANLI akış: answer biriktir + streamPreview güncelle. drawFooter'ı 120ms timer çizer (titreme yok).
254
280
  if (ev.type === 'text') {
255
281
  answer += ev.text;
256
282
  streamChars = answer.length;
283
+ streamPreview = cleanModelText(answer);
257
284
  }
258
285
  else if (ev.type === 'done')
259
286
  toolCalls = ev.toolCalls || [];
@@ -261,6 +288,7 @@ export async function runTui() {
261
288
  answer += `\n[hata: ${ev.error}]`;
262
289
  }
263
290
  answer = cleanModelText(answer).trim();
291
+ streamPreview = ''; // canlı önizlemeyi kapat → footer küçülür, formatlı cevap içeriğe basılır
264
292
  const am = { role: 'assistant', content: answer || '' };
265
293
  if (toolCalls.length)
266
294
  am.tool_calls = toolCalls.map((t) => ({ id: t.id, type: 'function', function: { name: t.name, arguments: t.args || '{}' } }));
@@ -290,6 +318,7 @@ export async function runTui() {
290
318
  perm = { label: toolLabel(c.name, args), name: c.name, resolve };
291
319
  refresh();
292
320
  }),
321
+ ask: (q) => new Promise((resolve) => { ask = { question: q.question, options: q.options.length ? q.options : [{ label: 'Tamam' }], sel: 0, resolve }; refresh(); }),
293
322
  onResult: (c, _i, res) => { if ((c.name === 'WebSearch' || c.name === 'WebFetch') && res.ok)
294
323
  usedWeb = true; printItem({ kind: 'tool', label: toolLabel(c.name, res.args), result: sanitizeOutput(res.output), ok: res.ok }); },
295
324
  });
@@ -303,6 +332,8 @@ export async function runTui() {
303
332
  clearInterval(timer);
304
333
  busy = false;
305
334
  perm = null;
335
+ ask = null;
336
+ streamPreview = '';
306
337
  refresh();
307
338
  // Web-öğrenme: web'de arayıp cevap ürettiyse {soru→cevap}'ı eğitim datasına ekle
308
339
  if (usedWeb && lastAnswer) {
@@ -337,6 +368,40 @@ export async function runTui() {
337
368
  catch { } });
338
369
  let ctrlcAt = 0;
339
370
  process.stdin.on('keypress', (str, key) => {
371
+ // Soru dialogu (AskUserQuestion) aktifse: ↑↓ / 1-9 seç, Enter onayla, Esc/Ctrl+C ilk seçenek
372
+ if (ask) {
373
+ const n = ask.options.length;
374
+ if (key && key.name === 'up') {
375
+ ask.sel = (ask.sel - 1 + n) % n;
376
+ refresh();
377
+ return;
378
+ }
379
+ if (key && key.name === 'down') {
380
+ ask.sel = (ask.sel + 1) % n;
381
+ refresh();
382
+ return;
383
+ }
384
+ if (str && /^[1-9]$/.test(str) && +str <= n) {
385
+ ask.sel = +str - 1;
386
+ refresh();
387
+ return;
388
+ }
389
+ if (key && key.name === 'return') {
390
+ const r = ask.resolve, v = ask.options[ask.sel]?.label || '';
391
+ ask = null;
392
+ r(v);
393
+ refresh();
394
+ return;
395
+ }
396
+ if (key && (key.name === 'escape' || (key.ctrl && key.name === 'c'))) {
397
+ const r = ask.resolve, v = ask.options[0]?.label || '';
398
+ ask = null;
399
+ r(v);
400
+ refresh();
401
+ return;
402
+ }
403
+ return;
404
+ }
340
405
  // İzin dialogu aktifse: E=Evet, T=Tümüne izin, H/Esc=Hayır (Ctrl+C de Hayır)
341
406
  if (perm) {
342
407
  const k = (str || '').toLowerCase();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wormclaude",
3
- "version": "1.0.49",
3
+ "version": "1.0.51",
4
4
  "description": "WormClaude CLI - uncensored security+code assistant (ink TUI, Claude-style)",
5
5
  "type": "module",
6
6
  "bin": {