wormclaude 1.0.23 → 1.0.25

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/cli.js CHANGED
@@ -90,15 +90,18 @@ const _initHistory = () => {
90
90
  // silemeyip KASKAD yapıyor (scrollback reflow). Alt-screen tüm ekranı yönetir → resize'da
91
91
  // temiz yeniden çizim, kaskad YOK. Kopyalama: alt-screen'de fare seçimi sınırlı olduğundan
92
92
  // /kopyala komutu (OSC52 ile panoya) sağlanır. ?1049h alt-screen · ?1007h alternate-scroll.
93
- try {
94
- process.stdout.write('\x1b[?1049h\x1b[?1007h\x1b[2J\x1b[H');
95
- }
96
- catch { }
97
- const _leaveAlt = () => { try {
98
- process.stdout.write('\x1b[?1007l\x1b[?1049l');
93
+ const _TUI = !!process.env.WORMCLAUDE_TUI; // özel renderer (deneysel) → ink'i atla
94
+ if (!_TUI) {
95
+ try {
96
+ process.stdout.write('\x1b[?1049h\x1b[?1007h\x1b[2J\x1b[H');
97
+ }
98
+ catch { }
99
+ const _leaveAlt = () => { try {
100
+ process.stdout.write('\x1b[?1007l\x1b[?1049l');
101
+ }
102
+ catch { } };
103
+ process.on('exit', _leaveAlt);
99
104
  }
100
- catch { } };
101
- process.on('exit', _leaveAlt);
102
105
  function useDimensions() {
103
106
  const [d, setD] = useState({
104
107
  cols: process.stdout.columns || 80,
@@ -1070,4 +1073,9 @@ function App() {
1070
1073
  tipText(tipId))),
1071
1074
  React.createElement(StatusLine, { model: config.model, ctxTokens: ctxTokens, trust: trustLevel }))) : null));
1072
1075
  }
1073
- render(React.createElement(App, null));
1076
+ if (_TUI) {
1077
+ import('./tui.js').then((m) => m.runTui()).catch((e) => { console.error(e); process.exit(1); });
1078
+ }
1079
+ else {
1080
+ render(React.createElement(App, null));
1081
+ }
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.23';
19
+ export const VERSION = '1.0.25';
package/dist/tui.js ADDED
@@ -0,0 +1,168 @@
1
+ // Kendi renderer (ink YOK) — Milestone 1: saf sohbet.
2
+ // Mimari: geçmiş → console.log (kalıcı scrollback → fare ile kopyalama + doğal kaydırma);
3
+ // canlı footer → log-update (kendi bölgesini yönetir, resize'da kaskad yok); giriş → readline keypress.
4
+ // Renkli kod + markdown: ansi.ts (itemAnsi). WORMCLAUDE_TUI=1 ile çalışır; ink sürümü dokunulmadan kalır.
5
+ import readline from 'node:readline';
6
+ import logUpdate from 'log-update';
7
+ import stringWidth from 'string-width';
8
+ import { loadConfig, streamChat } from './api.js';
9
+ import { itemAnsi, bannerAnsi } from './ansi.js';
10
+ import { theme, VERSION } from './theme.js';
11
+ import { cleanModelText } from './textclean.js';
12
+ const RESET = '\x1b[0m';
13
+ 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` : ''; };
14
+ const paint = (s, c, bold = false) => `${bold ? '\x1b[1m' : ''}${c ? hex(c) : ''}${s}${RESET}`;
15
+ const cols = () => process.stdout.columns || 80;
16
+ // Footer satırını terminal genişliğine ANSI-korumalı kırp (otomatik sarmayı önler → log-update
17
+ // satır sayımı şaşmaz, küçük terminalde bozulmaz). ANSI escape'leri genişliğe saymaz, korur.
18
+ function fit(line, max) {
19
+ let out = '', w = 0, i = 0;
20
+ while (i < line.length) {
21
+ if (line[i] === '\x1b') {
22
+ const m = /^\x1b\[[0-9;]*m/.exec(line.slice(i));
23
+ if (m) {
24
+ out += m[0];
25
+ i += m[0].length;
26
+ continue;
27
+ }
28
+ }
29
+ const ch = line[i];
30
+ const cw = stringWidth(ch) || 1;
31
+ if (w + cw > max)
32
+ break;
33
+ out += ch;
34
+ w += cw;
35
+ i++;
36
+ }
37
+ return out + RESET;
38
+ }
39
+ // Görünür (ANSI'siz) genişlik
40
+ const vis = (s) => stringWidth(s.replace(/\x1b\[[0-9;]*m/g, ''));
41
+ export async function runTui() {
42
+ const config = loadConfig();
43
+ const history = []; // model bağlamı {role, content}
44
+ let inputBuf = '';
45
+ let busy = false;
46
+ let streamPreview = ''; // akış sırasında footer'da gösterilen ham metin
47
+ let spin = 0;
48
+ const SPIN = ['·', '✢', '✳', '✶', '✻', '✽', '✶', '✳', '✢'];
49
+ // ── Geçmişe kalıcı yaz (scrollback) ──
50
+ const printItem = (it) => { logUpdate.clear(); process.stdout.write(itemAnsi(it, cols()) + '\n'); renderFooter(); };
51
+ // ── Canlı footer (log-update yönetir) ──
52
+ function renderFooter() {
53
+ const W = Math.max(8, cols());
54
+ const lines = [];
55
+ if (busy && !streamPreview)
56
+ lines.push(paint(`${SPIN[spin % SPIN.length]} `, theme.red) + paint('yanıt bekleniyor…', theme.grey));
57
+ if (streamPreview) {
58
+ const maxTail = Math.max(2, Math.min(12, (process.stdout.rows || 24) - 4)); // ekran boyunu aşma
59
+ const tail = streamPreview.split('\n').slice(-maxTail);
60
+ lines.push(paint('⏺ ', theme.redBright, true) + paint(tail.shift() || '', theme.white));
61
+ for (const l of tail)
62
+ lines.push(' ' + paint(l, theme.white));
63
+ }
64
+ lines.push('');
65
+ // Giriş satırı: uzunsa SONU göster (yazdıkça imleç görünür kalsın)
66
+ const prompt = paint('❯ ', theme.redBright, true);
67
+ let shown = inputBuf;
68
+ const avail = W - 3; // "❯ " + imleç payı
69
+ if (vis(shown) > avail)
70
+ shown = '…' + shown.slice(-(avail - 1));
71
+ lines.push(prompt + shown + (busy ? '' : paint('▌', theme.greyDim)));
72
+ lines.push(paint('/kopyala panoya · Ctrl+C çıkış', theme.greyDim));
73
+ // HER satırı genişliğe kırp → küçük terminalde sarma/bozulma olmaz
74
+ logUpdate(lines.map((l) => fit(l, W)).join('\n'));
75
+ }
76
+ // ── Bir sohbet turu (Milestone 1: araç yok, saf metin) ──
77
+ async function runTurn(userText) {
78
+ busy = true;
79
+ streamPreview = '';
80
+ history.push({ role: 'user', content: userText });
81
+ const spinTimer = setInterval(() => { spin++; if (busy && !streamPreview)
82
+ renderFooter(); }, 120);
83
+ let answer = '';
84
+ try {
85
+ for await (const ev of streamChat(history, [], config)) {
86
+ if (ev.type === 'text') {
87
+ answer += ev.text;
88
+ streamPreview = cleanModelText(answer);
89
+ renderFooter();
90
+ }
91
+ else if (ev.type === 'error') {
92
+ answer += `\n[hata: ${ev.error}]`;
93
+ }
94
+ }
95
+ }
96
+ catch (e) {
97
+ answer += `\n[bağlantı hatası: ${e?.message || e}]`;
98
+ }
99
+ clearInterval(spinTimer);
100
+ answer = cleanModelText(answer).trim();
101
+ busy = false;
102
+ streamPreview = '';
103
+ if (answer) {
104
+ history.push({ role: 'assistant', content: answer });
105
+ printItem({ kind: 'assistant', text: answer });
106
+ }
107
+ renderFooter();
108
+ }
109
+ // ── Giriş (readline keypress, raw mode) ──
110
+ readline.emitKeypressEvents(process.stdin);
111
+ if (process.stdin.isTTY)
112
+ process.stdin.setRawMode(true);
113
+ process.stdout.write(bannerAnsi(cols()) + '\n');
114
+ process.stdout.write(paint(' uncensored security + code', theme.greyDim) + paint(' · WormClaude ', theme.greyDim) + paint('v' + VERSION, theme.red, true) + paint(' · özel renderer (deneysel)', theme.greyDim) + '\n\n');
115
+ renderFooter();
116
+ process.stdin.on('keypress', (str, key) => {
117
+ if (key && key.ctrl && key.name === 'c') {
118
+ logUpdate.clear();
119
+ process.stdout.write('\n');
120
+ process.exit(0);
121
+ }
122
+ if (busy)
123
+ return; // tur sırasında giriş kilitli (M1)
124
+ if (key && key.name === 'return') {
125
+ const v = inputBuf.trim();
126
+ inputBuf = '';
127
+ if (!v) {
128
+ renderFooter();
129
+ return;
130
+ }
131
+ if (v === '/cikis' || v === '/exit' || v === '/quit') {
132
+ logUpdate.clear();
133
+ process.exit(0);
134
+ }
135
+ // /kopyala — son yanıtı OSC52 ile panoya
136
+ if (v === '/kopyala' || v === '/copy') {
137
+ const last = [...history].reverse().find((m) => m.role === 'assistant');
138
+ if (last) {
139
+ process.stdout.write(`\x1b]52;c;${Buffer.from(last.content, 'utf8').toString('base64')}\x07`);
140
+ printItem({ kind: 'note', text: `✓ Panoya kopyalandı (${last.content.length} karakter).` });
141
+ }
142
+ else
143
+ printItem({ kind: 'note', text: 'Kopyalanacak yanıt yok.' });
144
+ return;
145
+ }
146
+ printItem({ kind: 'user', text: v });
147
+ runTurn(v);
148
+ return;
149
+ }
150
+ if (key && (key.name === 'backspace')) {
151
+ inputBuf = inputBuf.slice(0, -1);
152
+ renderFooter();
153
+ return;
154
+ }
155
+ if (key && key.name === 'escape') {
156
+ inputBuf = '';
157
+ renderFooter();
158
+ return;
159
+ }
160
+ // yazdırılabilir karakter / yapıştırma
161
+ if (str && !key?.ctrl && !key?.meta && str !== '\r' && str !== '\n') {
162
+ inputBuf += str;
163
+ renderFooter();
164
+ }
165
+ });
166
+ // resize → footer'ı yeniden çiz (geçmiş scrollback'te terminalce sarılır)
167
+ process.stdout.on('resize', () => renderFooter());
168
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wormclaude",
3
- "version": "1.0.23",
3
+ "version": "1.0.25",
4
4
  "description": "WormClaude CLI - uncensored security+code assistant (ink TUI, Claude-style)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,6 +19,7 @@
19
19
  "ink": "^5.0.1",
20
20
  "ink-spinner": "^5.0.0",
21
21
  "ink-text-input": "^6.0.0",
22
+ "log-update": "^5.0.1",
22
23
  "react": "^18.3.1",
23
24
  "string-width": "^7.2.0"
24
25
  },