wormclaude 1.0.46 → 1.0.48

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.46';
19
+ export const VERSION = '1.0.48';
package/dist/tui.js CHANGED
@@ -12,6 +12,7 @@ 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
+ import { cmdDesc } from './i18n.js';
15
16
  const RESET = '\x1b[0m';
16
17
  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` : ''; };
17
18
  const paint = (s, c, bold = false) => `${bold ? '\x1b[1m' : ''}${c ? hex(c) : ''}${s}${RESET}`;
@@ -54,7 +55,38 @@ export async function runTui() {
54
55
  const displayItems = [{ kind: 'banner' }];
55
56
  let inputBuf = '', busy = false, streamChars = 0, spin = 0;
56
57
  const SPIN = ['·', '✢', '✳', '✶', '✻', '✽', '✶', '✳', '✢'];
57
- const FOOTER_H = 4; // kutu (üst çizgi + giriş/dialog + alt çizgi) + ipucu satırı
58
+ // Giriş kutusunu genişliğe göre ÇOK SATIRA sar (uzun mesaj/kod sağa taşmaz, alta iner).
59
+ const MAX_INPUT_LINES = 10;
60
+ const inputBoxLines = (W) => {
61
+ const inner = Math.max(4, W - 2); // "✶ " önek payı
62
+ const wrapped = [];
63
+ let cur = '';
64
+ for (const ch of inputBuf) {
65
+ if (vis(cur + ch) > inner) {
66
+ wrapped.push(cur);
67
+ cur = ch;
68
+ }
69
+ else
70
+ cur += ch;
71
+ }
72
+ wrapped.push(cur);
73
+ const shown = wrapped.length > MAX_INPUT_LINES ? wrapped.slice(-MAX_INPUT_LINES) : wrapped; // çok uzunsa sonu göster
74
+ return shown.map((ln, i) => (i === 0 ? paint('✶ ', theme.redBright, true) : ' ') + paint(ln, theme.white) + (i === shown.length - 1 ? paint('▌', theme.greyDim) : ''));
75
+ };
76
+ // Footer yüksekliği DİNAMİK: izin=4; değilse (durum/menü) + çizgi + giriş-satırları + çizgi.
77
+ let prevFH = 4;
78
+ const footerHeight = () => {
79
+ if (perm)
80
+ return 4;
81
+ const m = cmdMatches();
82
+ const statusLines = m.length ? Math.min(8, m.length) : 1;
83
+ return statusLines + 2 + inputBoxLines(Math.max(8, cols())).length;
84
+ };
85
+ // Yükseklik değiştiyse scroll-region'ı + içeriği yeniden bas; değişmediyse sadece footer'ı çiz.
86
+ const refresh = () => { const fh = footerHeight(); if (fh !== prevFH)
87
+ redrawAll();
88
+ else
89
+ drawFooter(); };
58
90
  setToolConfig(config); // araçlar (Write/Bash/Edit…) aynı config'i kullansın
59
91
  // İzin (araç onayı) durumu
60
92
  let perm = null;
@@ -81,9 +113,9 @@ export async function runTui() {
81
113
  };
82
114
  async function runCommand(v) {
83
115
  busy = true;
84
- drawFooter();
116
+ refresh();
85
117
  const timer = setInterval(() => { spin++; if (busy && !perm)
86
- drawFooter(); }, 120);
118
+ refresh(); }, 120);
87
119
  try {
88
120
  await runSlashCommand(v, cmdCtx);
89
121
  }
@@ -92,7 +124,7 @@ export async function runTui() {
92
124
  }
93
125
  clearInterval(timer);
94
126
  busy = false;
95
- drawFooter();
127
+ refresh();
96
128
  }
97
129
  // ── İkonlu, 2-sütunlu bilgi başlığı (3 sol / 3 sağ) — responsive ──
98
130
  function headerLines(W) {
@@ -115,15 +147,16 @@ export async function runTui() {
115
147
  return [...L, ...R].map((r) => fit(cell(r), W)); // dar ekran → alt alta
116
148
  return [0, 1, 2].map((i) => fit(padVis(cell(L[i]), colW) + cell(R[i]), W)); // geniş → 2 sütun
117
149
  }
118
- // ── Scroll region: üst = içerik (kayar), alt = sabit footer ──
150
+ // ── Scroll region: üst = içerik (kayar), alt = sabit footer (dinamik yükseklik) ──
119
151
  function setRegion() {
120
- const bottom = Math.max(1, rows() - FOOTER_H - 1); // footer üstünde 1 boş satır
152
+ const bottom = Math.max(1, rows() - footerHeight() - 1); // footer üstünde 1 boş satır
121
153
  process.stdout.write(`\x1b[1;${bottom}r`);
122
154
  }
123
155
  // ── Sabit footer'ı en alta mutlak konumla çiz (imleci kaydet/geri yükle → içerik bozulmaz) ──
124
156
  function drawFooter() {
125
157
  const W = Math.max(8, cols());
126
- const start = rows() - FOOTER_H + 1;
158
+ const fh = footerHeight();
159
+ const start = rows() - fh + 1;
127
160
  const line = paint('─'.repeat(W), theme.red);
128
161
  let body;
129
162
  if (perm) {
@@ -134,26 +167,24 @@ export async function runTui() {
134
167
  body = [line, q, opts, line];
135
168
  }
136
169
  else {
137
- let shown = inputBuf;
138
- const avail = W - 3;
139
- if (vis(shown) > avail)
140
- shown = '…' + shown.slice(-(avail - 1));
141
- const inputLine = paint('✶ ', theme.redBright, true) + paint(shown, theme.white) + paint('▌', theme.greyDim);
142
- // Durum satırı KUTUNUN ÜSTÜNDE: slash menü > spinner > ipucu.
170
+ const inputLines = inputBoxLines(W); // çok-satırlı giriş (sarılmış)
143
171
  const m = cmdMatches();
144
- let status;
145
172
  if (m.length) {
146
- const sel = Math.min(cmdSel, m.length - 1);
147
- const names = m.slice(0, 8).map((c, i) => i === sel ? paint(c.name, theme.redBright, true) : paint(c.name, theme.greyDim)).join(' ');
148
- status = ' ' + names + paint(' ↹ tamamla · ↑↓ seç', theme.greyDim);
149
- }
150
- else if (busy) {
151
- status = paint(` ${SPIN[spin % SPIN.length]} çalışıyor…${streamChars ? ' ' + streamChars + ' karakter' : ''}`, theme.grey);
173
+ // DİKEY slash menü (alt alta), seçili kırmızı — kutunun üstünde.
174
+ const items = m.slice(0, 8);
175
+ const sel = Math.min(cmdSel, items.length - 1);
176
+ const menu = items.map((c, i) => {
177
+ const on = i === sel;
178
+ return ' ' + paint((on ? '✶ ' : ' ') + c.name.padEnd(13), on ? theme.redBright : theme.grey) + paint(cmdDesc(c.name) || c.desc || '', theme.greyDim);
179
+ });
180
+ body = [...menu, line, ...inputLines, line];
152
181
  }
153
182
  else {
154
- status = paint(' / komutlar · ↑↓ geçmiş · /kopyala · Ctrl+C çıkış', theme.greyDim);
183
+ const status = busy
184
+ ? paint(` ${SPIN[spin % SPIN.length]} çalışıyor…${streamChars ? ' ' + streamChars + ' karakter' : ''}`, theme.grey)
185
+ : paint(' / komutlar · ↑↓ geçmiş · /kopyala · Ctrl+C çıkış', theme.greyDim);
186
+ body = [status, line, ...inputLines, line];
155
187
  }
156
- body = [status, line, inputLine, line];
157
188
  }
158
189
  let out = '\x1b7'; // imleci kaydet
159
190
  body.forEach((l, i) => { out += `\x1b[${start + i};1H\x1b[2K` + fit(l, W); });
@@ -162,6 +193,7 @@ export async function runTui() {
162
193
  }
163
194
  // ── Her şeyi yeni boyutta yeniden bas (banner/kod responsive); içerik bölgesine ──
164
195
  function redrawAll() {
196
+ prevFH = footerHeight(); // mevcut footer yüksekliğini kaydet (refresh karşılaştırması için)
165
197
  setRegion();
166
198
  process.stdout.write('\x1b[2J\x1b[3J\x1b[H');
167
199
  const W = cols();
@@ -173,7 +205,7 @@ export async function runTui() {
173
205
  drawFooter();
174
206
  }
175
207
  // İçerik öğesini ekle + içerik alanına bas (taşarsa scrollback'e → kopyalanır). Footer sabit kalır.
176
- const printItem = (it) => { displayItems.push(it); process.stdout.write(itemAnsi(it, cols()) + '\n'); drawFooter(); };
208
+ const printItem = (it) => { displayItems.push(it); process.stdout.write(itemAnsi(it, cols()) + '\n'); refresh(); };
177
209
  // ── Agent döngüsü (M2: araçlar + izin). Model tool çağırırsa izin sorulup çalıştırılır,
178
210
  // sonuç geçmişe eklenip döngü devam eder; tool yoksa biter. ──
179
211
  async function runTurn(userText) {
@@ -181,7 +213,7 @@ export async function runTui() {
181
213
  streamChars = 0;
182
214
  history.push({ role: 'user', content: userText });
183
215
  const timer = setInterval(() => { spin++; if (busy && !perm)
184
- drawFooter(); }, 120);
216
+ refresh(); }, 120);
185
217
  let lastSig = '', sameCount = 0;
186
218
  try {
187
219
  for (let iter = 0; iter < 25; iter++) {
@@ -226,7 +258,7 @@ export async function runTui() {
226
258
  if (allowAll.has(c.name))
227
259
  return resolve('allow');
228
260
  perm = { label: toolLabel(c.name, args), name: c.name, resolve };
229
- drawFooter();
261
+ refresh();
230
262
  }),
231
263
  onResult: (c, _i, res) => printItem({ kind: 'tool', label: toolLabel(c.name, res.args), result: sanitizeOutput(res.output), ok: res.ok }),
232
264
  });
@@ -240,7 +272,7 @@ export async function runTui() {
240
272
  clearInterval(timer);
241
273
  busy = false;
242
274
  perm = null;
243
- drawFooter();
275
+ refresh();
244
276
  }
245
277
  // ── Kurulum ──
246
278
  readline.emitKeypressEvents(process.stdin);
@@ -270,31 +302,31 @@ export async function runTui() {
270
302
  if (key && key.ctrl && key.name === 'c') {
271
303
  perm = null;
272
304
  r({ deny: 'kullanıcı iptal etti' });
273
- drawFooter();
305
+ refresh();
274
306
  return;
275
307
  }
276
308
  if (k === 'e' || k === '1' || (key && key.name === 'return')) {
277
309
  perm = null;
278
310
  r('allow');
279
- drawFooter();
311
+ refresh();
280
312
  }
281
313
  else if (k === 't' || k === '2') {
282
314
  allowAll.add(nm);
283
315
  perm = null;
284
316
  r('allow');
285
- drawFooter();
317
+ refresh();
286
318
  }
287
319
  else if (k === 'h' || k === '3' || (key && key.name === 'escape')) {
288
320
  perm = null;
289
321
  r({ deny: '' });
290
- drawFooter();
322
+ refresh();
291
323
  }
292
324
  return;
293
325
  }
294
326
  if (key && key.ctrl && key.name === 'c') {
295
327
  if (inputBuf) {
296
328
  inputBuf = '';
297
- drawFooter();
329
+ refresh();
298
330
  return;
299
331
  }
300
332
  const now = Date.now();
@@ -313,13 +345,13 @@ export async function runTui() {
313
345
  const m = cmdMatches();
314
346
  if (m.length) {
315
347
  cmdSel = (cmdSel - 1 + m.length) % m.length;
316
- drawFooter();
348
+ refresh();
317
349
  return;
318
350
  }
319
351
  if (inputHistory.length) {
320
352
  histIdx = histIdx < 0 ? inputHistory.length - 1 : Math.max(0, histIdx - 1);
321
353
  inputBuf = inputHistory[histIdx];
322
- drawFooter();
354
+ refresh();
323
355
  }
324
356
  return;
325
357
  }
@@ -327,7 +359,7 @@ export async function runTui() {
327
359
  const m = cmdMatches();
328
360
  if (m.length) {
329
361
  cmdSel = (cmdSel + 1) % m.length;
330
- drawFooter();
362
+ refresh();
331
363
  return;
332
364
  }
333
365
  if (histIdx >= 0) {
@@ -338,7 +370,7 @@ export async function runTui() {
338
370
  }
339
371
  else
340
372
  inputBuf = inputHistory[histIdx];
341
- drawFooter();
373
+ refresh();
342
374
  }
343
375
  return;
344
376
  }
@@ -348,7 +380,7 @@ export async function runTui() {
348
380
  if (m.length) {
349
381
  inputBuf = m[Math.min(cmdSel, m.length - 1)].name + ' ';
350
382
  cmdSel = 0;
351
- drawFooter();
383
+ refresh();
352
384
  }
353
385
  return;
354
386
  }
@@ -359,7 +391,7 @@ export async function runTui() {
359
391
  inputBuf = '';
360
392
  histIdx = -1;
361
393
  if (!v) {
362
- drawFooter();
394
+ refresh();
363
395
  return;
364
396
  }
365
397
  // Slash komutu: tam eşleşme yoksa menüde SEÇİLİ olanı kullan
@@ -395,19 +427,19 @@ export async function runTui() {
395
427
  if (key && key.name === 'backspace') {
396
428
  inputBuf = inputBuf.slice(0, -1);
397
429
  cmdSel = 0;
398
- drawFooter();
430
+ refresh();
399
431
  return;
400
432
  }
401
433
  if (key && key.name === 'escape') {
402
434
  inputBuf = '';
403
- drawFooter();
435
+ refresh();
404
436
  return;
405
437
  }
406
438
  // sadece gerçek yazdırılabilir karakter (her zaman → cevap üretilirken bile type-ahead)
407
439
  if (str && !key?.ctrl && !key?.meta && !str.startsWith('\x1b') && !/[\x00-\x1f]/.test(str)) {
408
440
  inputBuf += str;
409
441
  cmdSel = 0;
410
- drawFooter();
442
+ refresh();
411
443
  }
412
444
  });
413
445
  // 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.46",
3
+ "version": "1.0.48",
4
4
  "description": "WormClaude CLI - uncensored security+code assistant (ink TUI, Claude-style)",
5
5
  "type": "module",
6
6
  "bin": {