wormclaude 1.0.37 → 1.0.39

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.37';
19
+ export const VERSION = '1.0.39';
package/dist/tui.js CHANGED
@@ -6,6 +6,8 @@
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';
10
+ import { sanitizeOutput } from './errorsan.js';
9
11
  import { itemAnsi } from './ansi.js';
10
12
  import { theme, VERSION } from './theme.js';
11
13
  import { cleanModelText } from './textclean.js';
@@ -43,7 +45,11 @@ export async function runTui() {
43
45
  const displayItems = [{ kind: 'banner' }];
44
46
  let inputBuf = '', busy = false, streamChars = 0, spin = 0;
45
47
  const SPIN = ['·', '✢', '✳', '✶', '✻', '✽', '✶', '✳', '✢'];
46
- const FOOTER_H = 2; // giriş satırı + ipucu satırı
48
+ const FOOTER_H = 4; // kutu (üst çizgi + giriş/dialog + alt çizgi) + ipucu satırı
49
+ setToolConfig(config); // araçlar (Write/Bash/Edit…) aynı config'i kullansın
50
+ // İzin (araç onayı) durumu
51
+ let perm = null;
52
+ const allowAll = new Set(); // "Tümüne izin" denen araçlar (oturum boyu)
47
53
  // ── Claude tarzı başlık (model/plan/mail/cwd) — responsive ──
48
54
  function headerLines(W) {
49
55
  const a = paint(' WormClaude ', theme.greyDim) + paint('v' + VERSION, theme.red, true)
@@ -61,16 +67,28 @@ export async function runTui() {
61
67
  function drawFooter() {
62
68
  const W = Math.max(8, cols());
63
69
  const start = rows() - FOOTER_H + 1;
64
- let shown = inputBuf;
65
- const avail = W - 3;
66
- if (vis(shown) > avail)
67
- shown = '…' + shown.slice(-(avail - 1));
68
- const inputLine = paint(' ', theme.redBright, true) + shown + paint('', theme.greyDim);
69
- const hint = busy
70
- ? paint(`${SPIN[spin % SPIN.length]} yanıt geliyor… ${streamChars} karakter`, theme.grey)
71
- : paint(' /kopyala panoya · /help komutlar · Ctrl+C çıkış', theme.greyDim);
70
+ const line = paint('─'.repeat(W), theme.red);
71
+ let body;
72
+ if (perm) {
73
+ // İzin dialogu: araç adı + Evet/Tümü/Hayır
74
+ const q = paint(' ', theme.redBright, true) + paint(perm.label, theme.white) + paint(' çalıştırılsın mı?', theme.grey);
75
+ const opts = paint(' [E] Evet', theme.redBright, true) + paint(' · ', theme.greyDim)
76
+ + paint('[T] Tümüne izin', theme.grey) + paint(' · ', theme.greyDim) + paint('[H] Hayır', theme.grey);
77
+ body = [line, q, opts, line];
78
+ }
79
+ else {
80
+ let shown = inputBuf;
81
+ const avail = W - 3;
82
+ if (vis(shown) > avail)
83
+ shown = '…' + shown.slice(-(avail - 1));
84
+ const inputLine = paint('❯ ', theme.redBright, true) + paint(shown, theme.white) + paint('▌', theme.greyDim);
85
+ const hint = busy
86
+ ? paint(`${SPIN[spin % SPIN.length]} çalışıyor… ${streamChars ? streamChars + ' karakter' : ''}`, theme.grey)
87
+ : paint(' /kopyala panoya · /help komutlar · Ctrl+C çıkış', theme.greyDim);
88
+ body = [line, inputLine, line, hint];
89
+ }
72
90
  let out = '\x1b7'; // imleci kaydet
73
- [fit(inputLine, W), fit(hint, W)].forEach((l, i) => { out += `\x1b[${start + i};1H\x1b[2K` + l; });
91
+ body.forEach((l, i) => { out += `\x1b[${start + i};1H\x1b[2K` + fit(l, W); });
74
92
  out += '\x1b8'; // imleci geri yükle (içerik alanı)
75
93
  process.stdout.write(out);
76
94
  }
@@ -88,34 +106,71 @@ export async function runTui() {
88
106
  }
89
107
  // İçerik öğesini ekle + içerik alanına bas (taşarsa scrollback'e → kopyalanır). Footer sabit kalır.
90
108
  const printItem = (it) => { displayItems.push(it); process.stdout.write(itemAnsi(it, cols()) + '\n'); drawFooter(); };
91
- // ── Bir sohbet turu (M1: araç yok). Akış footer'da karakter sayacı; bitince içeriğe basılır ──
109
+ // ── Agent döngüsü (M2: araçlar + izin). Model tool çağırırsa izin sorulup çalıştırılır,
110
+ // sonuç geçmişe eklenip döngü devam eder; tool yoksa biter. ──
92
111
  async function runTurn(userText) {
93
112
  busy = true;
94
113
  streamChars = 0;
95
114
  history.push({ role: 'user', content: userText });
96
- const timer = setInterval(() => { spin++; if (busy)
115
+ const timer = setInterval(() => { spin++; if (busy && !perm)
97
116
  drawFooter(); }, 120);
98
- let answer = '';
117
+ let lastSig = '', sameCount = 0;
99
118
  try {
100
- for await (const ev of streamChat(history, [], config)) {
101
- if (ev.type === 'text') {
102
- answer += ev.text;
103
- streamChars = answer.length;
119
+ for (let iter = 0; iter < 25; iter++) {
120
+ streamChars = 0;
121
+ let answer = '';
122
+ let toolCalls = [];
123
+ for await (const ev of streamChat(history, allToolSchemas(), config)) {
124
+ if (ev.type === 'text') {
125
+ answer += ev.text;
126
+ streamChars = answer.length;
127
+ drawFooter();
128
+ }
129
+ else if (ev.type === 'done')
130
+ toolCalls = ev.toolCalls || [];
131
+ else if (ev.type === 'error')
132
+ answer += `\n[hata: ${ev.error}]`;
133
+ }
134
+ answer = cleanModelText(answer).trim();
135
+ const am = { role: 'assistant', content: answer || '' };
136
+ if (toolCalls.length)
137
+ am.tool_calls = toolCalls.map((t) => ({ id: t.id, type: 'function', function: { name: t.name, arguments: t.args || '{}' } }));
138
+ history.push(am);
139
+ if (answer)
140
+ printItem({ kind: 'assistant', text: answer });
141
+ if (!toolCalls.length)
142
+ break;
143
+ // takılma koruması: aynı araç-çağrısı 3 kez üst üste → dur
144
+ const sig = JSON.stringify(toolCalls.map((tc) => tc.name + ':' + (tc.args || '')));
145
+ if (sig === lastSig)
146
+ sameCount++;
147
+ else {
148
+ sameCount = 0;
149
+ lastSig = sig;
150
+ }
151
+ if (sameCount >= 2) {
152
+ printItem({ kind: 'note', text: 'Aynı adım tekrarlandı, döngü önlemek için durduruldu.' });
153
+ break;
104
154
  }
105
- else if (ev.type === 'error')
106
- answer += `\n[hata: ${ev.error}]`;
155
+ const results = await executeToolCalls(toolCalls, {
156
+ confirm: (c, args) => new Promise((resolve) => {
157
+ if (allowAll.has(c.name))
158
+ return resolve('allow');
159
+ perm = { label: toolLabel(c.name, args), name: c.name, resolve };
160
+ drawFooter();
161
+ }),
162
+ onResult: (c, _i, res) => printItem({ kind: 'tool', label: toolLabel(c.name, res.args), result: sanitizeOutput(res.output), ok: res.ok }),
163
+ });
164
+ for (let i = 0; i < toolCalls.length; i++)
165
+ history.push({ role: 'tool', tool_call_id: toolCalls[i].id, content: (results[i].output || '').slice(0, 8000) });
107
166
  }
108
167
  }
109
168
  catch (e) {
110
- answer += `\n[bağlantı hatası: ${e?.message || e}]`;
169
+ printItem({ kind: 'note', text: `[bağlantı hatası: ${e?.message || e}]` });
111
170
  }
112
171
  clearInterval(timer);
113
- answer = cleanModelText(answer).trim();
114
172
  busy = false;
115
- if (answer) {
116
- history.push({ role: 'assistant', content: answer });
117
- printItem({ kind: 'assistant', text: answer });
118
- }
173
+ perm = null;
119
174
  drawFooter();
120
175
  }
121
176
  // ── Kurulum ──
@@ -139,6 +194,34 @@ export async function runTui() {
139
194
  catch { } });
140
195
  let ctrlcAt = 0;
141
196
  process.stdin.on('keypress', (str, key) => {
197
+ // İzin dialogu aktifse: E=Evet, T=Tümüne izin, H/Esc=Hayır (Ctrl+C de Hayır)
198
+ if (perm) {
199
+ const k = (str || '').toLowerCase();
200
+ const r = perm.resolve, nm = perm.name;
201
+ if (key && key.ctrl && key.name === 'c') {
202
+ perm = null;
203
+ r({ deny: 'kullanıcı iptal etti' });
204
+ drawFooter();
205
+ return;
206
+ }
207
+ if (k === 'e' || k === '1' || (key && key.name === 'return')) {
208
+ perm = null;
209
+ r('allow');
210
+ drawFooter();
211
+ }
212
+ else if (k === 't' || k === '2') {
213
+ allowAll.add(nm);
214
+ perm = null;
215
+ r('allow');
216
+ drawFooter();
217
+ }
218
+ else if (k === 'h' || k === '3' || (key && key.name === 'escape')) {
219
+ perm = null;
220
+ r({ deny: '' });
221
+ drawFooter();
222
+ }
223
+ return;
224
+ }
142
225
  if (key && key.ctrl && key.name === 'c') {
143
226
  if (inputBuf) {
144
227
  inputBuf = '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wormclaude",
3
- "version": "1.0.37",
3
+ "version": "1.0.39",
4
4
  "description": "WormClaude CLI - uncensored security+code assistant (ink TUI, Claude-style)",
5
5
  "type": "module",
6
6
  "bin": {