wormclaude 1.0.38 → 1.0.40

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