wormclaude 1.0.22 → 1.0.24
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 +192 -22
- package/dist/commands.js +1 -0
- package/dist/theme.js +1 -1
- package/dist/tui.js +133 -0
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import React, { useState, useRef, useEffect } from 'react';
|
|
3
|
-
import { render, Box, Text,
|
|
3
|
+
import { render, Box, Text, useApp, useInput } from 'ink';
|
|
4
4
|
import TextInput from 'ink-text-input';
|
|
5
5
|
import * as path from 'node:path';
|
|
6
6
|
import { theme, VERSION } from './theme.js';
|
|
@@ -11,6 +11,7 @@ import { allToolSchemas, executeToolCalls, executeTool, toolLabel, setToolConfig
|
|
|
11
11
|
import { sanitizeError, sanitizeOutput } from './errorsan.js';
|
|
12
12
|
import { cleanModelText } from './textclean.js';
|
|
13
13
|
import { MarkdownDisplay } from './markdown.js';
|
|
14
|
+
import { highlight } from './highlight.js';
|
|
14
15
|
import { resolveAtMentions } from './atmention.js';
|
|
15
16
|
import { summarizeTools } from './toolSummary.js';
|
|
16
17
|
import { pickTipId, tipText } from './tips.js';
|
|
@@ -85,10 +86,22 @@ const _initHistory = () => {
|
|
|
85
86
|
sys.push({ role: 'system', content: _memCtx });
|
|
86
87
|
return sys;
|
|
87
88
|
};
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
89
|
+
// ALT-SCREEN (alternate screen) — ink normal buffer'da resize'da eski dinamik çerçeveyi
|
|
90
|
+
// silemeyip KASKAD yapıyor (scrollback reflow). Alt-screen tüm ekranı yönetir → resize'da
|
|
91
|
+
// temiz yeniden çizim, kaskad YOK. Kopyalama: alt-screen'de fare seçimi sınırlı olduğundan
|
|
92
|
+
// /kopyala komutu (OSC52 ile panoya) sağlanır. ?1049h alt-screen · ?1007h alternate-scroll.
|
|
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);
|
|
104
|
+
}
|
|
92
105
|
function useDimensions() {
|
|
93
106
|
const [d, setD] = useState({
|
|
94
107
|
cols: process.stdout.columns || 80,
|
|
@@ -148,6 +161,91 @@ function wrapText(text, width) {
|
|
|
148
161
|
}
|
|
149
162
|
return out;
|
|
150
163
|
}
|
|
164
|
+
// Inline markdown (`code` **bold**) → segment'ler.
|
|
165
|
+
function inlineSegs(text, base) {
|
|
166
|
+
const segs = [];
|
|
167
|
+
const re = /(`[^`]+`|\*\*[^*]+\*\*)/g;
|
|
168
|
+
let last = 0;
|
|
169
|
+
let m;
|
|
170
|
+
while ((m = re.exec(text)) !== null) {
|
|
171
|
+
if (m.index > last)
|
|
172
|
+
segs.push({ text: text.slice(last, m.index), color: base });
|
|
173
|
+
const f = m[0];
|
|
174
|
+
if (f.startsWith('`'))
|
|
175
|
+
segs.push({ text: f.slice(1, -1), color: theme.green });
|
|
176
|
+
else
|
|
177
|
+
segs.push({ text: f.slice(2, -2), color: base, bold: true });
|
|
178
|
+
last = re.lastIndex;
|
|
179
|
+
}
|
|
180
|
+
if (last < text.length)
|
|
181
|
+
segs.push({ text: text.slice(last), color: base });
|
|
182
|
+
return segs.length ? segs : [{ text, color: base }];
|
|
183
|
+
}
|
|
184
|
+
// Markdown metnini segment-satırlarına çevirir; kod blokları sözdizimi-vurgulu (highlight()).
|
|
185
|
+
function mdToSegLines(text, W) {
|
|
186
|
+
const out = [];
|
|
187
|
+
const white = theme.white;
|
|
188
|
+
let inCode = false, fence = '', lang = '', code = [];
|
|
189
|
+
const fenceRe = /^ *(`{3,}|~{3,}) *(\w*)? *$/;
|
|
190
|
+
const flush = () => {
|
|
191
|
+
if (lang)
|
|
192
|
+
out.push([{ text: ' ' + lang, dim: true }]);
|
|
193
|
+
for (const toks of highlight(code.join('\n'), lang)) {
|
|
194
|
+
const segs = [{ text: '│ ', dim: true }];
|
|
195
|
+
if (toks.length)
|
|
196
|
+
for (const tk of toks)
|
|
197
|
+
segs.push({ text: tk.text, color: tk.color });
|
|
198
|
+
else
|
|
199
|
+
segs.push({ text: ' ' });
|
|
200
|
+
out.push(segs);
|
|
201
|
+
}
|
|
202
|
+
code = [];
|
|
203
|
+
lang = '';
|
|
204
|
+
fence = '';
|
|
205
|
+
};
|
|
206
|
+
for (const line of text.split('\n')) {
|
|
207
|
+
if (inCode) {
|
|
208
|
+
const fm = line.match(fenceRe);
|
|
209
|
+
if (fm && fm[1][0] === fence[0] && fm[1].length >= fence.length) {
|
|
210
|
+
inCode = false;
|
|
211
|
+
flush();
|
|
212
|
+
}
|
|
213
|
+
else
|
|
214
|
+
code.push(line);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const fm = line.match(fenceRe);
|
|
218
|
+
if (fm) {
|
|
219
|
+
inCode = true;
|
|
220
|
+
fence = fm[1];
|
|
221
|
+
lang = (fm[2] || '').trim();
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const hm = line.match(/^ *(#{1,4}) +(.*)/);
|
|
225
|
+
const um = line.match(/^([ \t]*)([-*+]) +(.*)/);
|
|
226
|
+
const om = line.match(/^([ \t]*)(\d+)\. +(.*)/);
|
|
227
|
+
if (hm) {
|
|
228
|
+
const lvl = hm[1].length;
|
|
229
|
+
out.push([{ text: hm[2], color: lvl <= 2 ? theme.redBright : white, bold: lvl <= 3, italic: lvl > 3 }]);
|
|
230
|
+
}
|
|
231
|
+
else if (um || om) {
|
|
232
|
+
const [, ws, marker, itemText] = (um || om);
|
|
233
|
+
const prefix = om ? `${marker}. ` : '• ';
|
|
234
|
+
const wrapped = wrapText(itemText, Math.max(8, W - ws.length - prefix.length));
|
|
235
|
+
wrapped.forEach((w, i) => out.push([{ text: ' '.repeat(ws.length) + (i === 0 ? prefix : ' '), dim: true }, ...inlineSegs(w, white)]));
|
|
236
|
+
}
|
|
237
|
+
else if (line.trim() === '') {
|
|
238
|
+
out.push([]);
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
for (const w of wrapText(line, W))
|
|
242
|
+
out.push(inlineSegs(w, white));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (inCode)
|
|
246
|
+
flush();
|
|
247
|
+
return out;
|
|
248
|
+
}
|
|
151
249
|
// Tüm öğeleri görsel satır dizisine çevirir (kaydırma satır granülerliğinde).
|
|
152
250
|
function buildLines(items, cols) {
|
|
153
251
|
const lines = [];
|
|
@@ -171,7 +269,8 @@ function buildLines(items, cols) {
|
|
|
171
269
|
lines.push([{ text: '╰' + '─'.repeat(W - 2) + '╯', dim: true }]);
|
|
172
270
|
}
|
|
173
271
|
else if (it.kind === 'assistant') {
|
|
174
|
-
|
|
272
|
+
const seg = mdToSegLines(it.text, W - 2);
|
|
273
|
+
seg.forEach((segs, i) => lines.push(i === 0 ? [{ text: '⏺ ', color: redB, bold: true }, ...segs] : [{ text: ' ', dim: true }, ...segs]));
|
|
175
274
|
}
|
|
176
275
|
else if (it.kind === 'tool') {
|
|
177
276
|
const n = it.result.split('\n').length, chars = it.result.length;
|
|
@@ -440,9 +539,24 @@ function App() {
|
|
|
440
539
|
else if (key.downArrow)
|
|
441
540
|
setCmdSel((s) => (s + 1) % n);
|
|
442
541
|
});
|
|
443
|
-
//
|
|
444
|
-
//
|
|
445
|
-
|
|
542
|
+
// Geçmişte kaydırma: PageUp/PageDown her zaman; boş input'ta ok tuşları (fare tekerleği
|
|
543
|
+
// alternate-scroll ile ok yollar → tekerlekle kaydırma). Alt-screen olduğundan biz yönetiriz.
|
|
544
|
+
useInput((_inp, key) => {
|
|
545
|
+
if (!started || perm || ask || lang === null)
|
|
546
|
+
return;
|
|
547
|
+
if (key.pageUp)
|
|
548
|
+
setScroll((s) => s + 10);
|
|
549
|
+
else if (key.pageDown)
|
|
550
|
+
setScroll((s) => Math.max(0, s - 10));
|
|
551
|
+
else if (input === '' && !busy) {
|
|
552
|
+
if (key.upArrow)
|
|
553
|
+
setScroll((s) => s + 3);
|
|
554
|
+
else if (key.downArrow)
|
|
555
|
+
setScroll((s) => Math.max(0, s - 3));
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
// Yeni mesaj gelince en alta dön
|
|
559
|
+
useEffect(() => { setScroll(0); }, [items.length]);
|
|
446
560
|
// Başlangıçta güven seviyesini (Doğrulanmış Araştırmacı Programı) çek → StatusLine rozeti
|
|
447
561
|
useEffect(() => {
|
|
448
562
|
if (!started)
|
|
@@ -695,6 +809,31 @@ function App() {
|
|
|
695
809
|
v = filtered[Math.min(cmdSel, filtered.length - 1)].name;
|
|
696
810
|
}
|
|
697
811
|
const tok = v.split(' ')[0];
|
|
812
|
+
// /kopyala [hepsi] — son yanıtı (veya tüm sohbeti) panoya kopyalar (OSC52, fare gerekmez).
|
|
813
|
+
if (tok === '/kopyala' || tok === '/copy') {
|
|
814
|
+
const arg = v.slice(tok.length).trim().toLowerCase();
|
|
815
|
+
const en = getLang() === 'en';
|
|
816
|
+
const asst = items.filter((it) => it.kind === 'assistant').map((it) => it.text);
|
|
817
|
+
let text = '';
|
|
818
|
+
if (arg === 'all' || arg === 'hepsi') {
|
|
819
|
+
text = items.map((it) => it.kind === 'user' ? '> ' + it.text : it.kind === 'assistant' ? it.text : '').filter(Boolean).join('\n\n');
|
|
820
|
+
}
|
|
821
|
+
else
|
|
822
|
+
text = asst[asst.length - 1] || '';
|
|
823
|
+
if (!text) {
|
|
824
|
+
push({ kind: 'note', text: en ? 'Nothing to copy.' : 'Kopyalanacak içerik yok.' });
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
try {
|
|
828
|
+
const b64 = Buffer.from(text, 'utf8').toString('base64');
|
|
829
|
+
process.stdout.write(`\x1b]52;c;${b64}\x07`);
|
|
830
|
+
push({ kind: 'note', text: en ? `✓ Copied to clipboard (${text.length} chars).` : `✓ Panoya kopyalandı (${text.length} karakter).` });
|
|
831
|
+
}
|
|
832
|
+
catch {
|
|
833
|
+
push({ kind: 'note', text: en ? 'Copy failed.' : 'Kopyalama başarısız.' });
|
|
834
|
+
}
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
698
837
|
// /agent ve /multi-agent — tek / çoklu alt-agent
|
|
699
838
|
if (tok === '/agent' || tok === '/multi-agent') {
|
|
700
839
|
const task = v.slice(tok.length).trim();
|
|
@@ -788,9 +927,10 @@ function App() {
|
|
|
788
927
|
runAgent(v);
|
|
789
928
|
}
|
|
790
929
|
const { cols, rows } = useDimensions();
|
|
791
|
-
// İlk açılış: dil seçimi
|
|
930
|
+
// İlk açılış: dil seçimi
|
|
792
931
|
if (lang === null) {
|
|
793
|
-
return (React.createElement(Box, { flexDirection: "column", width: cols },
|
|
932
|
+
return (React.createElement(Box, { flexDirection: "column", height: rows, width: cols },
|
|
933
|
+
React.createElement(Banner, null),
|
|
794
934
|
React.createElement(Box, { flexDirection: "column", marginTop: 1, paddingX: 1 },
|
|
795
935
|
React.createElement(Text, { color: theme.redBright, bold: true }, t('lang.title')),
|
|
796
936
|
React.createElement(Box, { flexDirection: "column", marginTop: 1 },
|
|
@@ -802,11 +942,14 @@ function App() {
|
|
|
802
942
|
t('lang.en'))),
|
|
803
943
|
React.createElement(Text, { color: theme.greyDim },
|
|
804
944
|
" ",
|
|
805
|
-
t('lang.hint')))
|
|
945
|
+
t('lang.hint'))),
|
|
946
|
+
React.createElement(Box, { flexGrow: 1 }),
|
|
947
|
+
React.createElement(StatusLine, { model: config.model, ctxTokens: 0 })));
|
|
806
948
|
}
|
|
807
|
-
// Açılış: klasöre güven sorusu (
|
|
949
|
+
// Açılış: klasöre güven sorusu (WormClaude tarzı)
|
|
808
950
|
if (!started) {
|
|
809
|
-
return (React.createElement(Box, { flexDirection: "column", width: cols },
|
|
951
|
+
return (React.createElement(Box, { flexDirection: "column", height: rows, width: cols },
|
|
952
|
+
React.createElement(Banner, null),
|
|
810
953
|
React.createElement(Box, { flexDirection: "column", marginTop: 1, paddingX: 1 },
|
|
811
954
|
React.createElement(Text, { color: theme.redBright, bold: true }, t('trust.accessing')),
|
|
812
955
|
React.createElement(Text, null, " "),
|
|
@@ -824,14 +967,36 @@ function App() {
|
|
|
824
967
|
t('trust.no'))),
|
|
825
968
|
React.createElement(Text, { color: theme.greyDim },
|
|
826
969
|
" ",
|
|
827
|
-
t('trust.hint')))
|
|
970
|
+
t('trust.hint'))),
|
|
971
|
+
React.createElement(Box, { flexGrow: 1 }),
|
|
972
|
+
React.createElement(StatusLine, { model: config.model, ctxTokens: 0 })));
|
|
828
973
|
}
|
|
829
|
-
return (React.createElement(Box, { flexDirection: "column", width: cols },
|
|
830
|
-
React.createElement(
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
974
|
+
return (React.createElement(Box, { flexDirection: "column", height: rows, width: cols },
|
|
975
|
+
React.createElement(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden" }, (() => {
|
|
976
|
+
// Alt-screen + satır-pencere pager: tüm öğeleri satıra çevir, pencere göster.
|
|
977
|
+
// (Kopyalama /kopyala komutuyla; resize'da kaskad olmaz.)
|
|
978
|
+
const all = buildLines(items, cols);
|
|
979
|
+
const reserve = busy ? 4 : 6;
|
|
980
|
+
const avail = Math.max(4, rows - reserve - (streaming ? 3 : 0) - 2);
|
|
981
|
+
const maxScroll = Math.max(0, all.length - avail);
|
|
982
|
+
const off = Math.min(scroll, maxScroll);
|
|
983
|
+
const startLine = Math.max(0, all.length - avail - off);
|
|
984
|
+
const view = all.slice(startLine, startLine + avail);
|
|
985
|
+
return (React.createElement(React.Fragment, null,
|
|
986
|
+
startLine > 0 ? React.createElement(Text, { color: theme.greyDim },
|
|
987
|
+
" \u2191 ",
|
|
988
|
+
startLine,
|
|
989
|
+
" sat\u0131r \u00B7 PageUp") : null,
|
|
990
|
+
view.map((segs, i) => (React.createElement(Text, { key: i }, segs.length === 0 ? ' ' : segs.map((s, j) => (React.createElement(Text, { key: j, color: s.dim ? theme.greyDim : s.color, bold: s.bold, italic: s.italic }, linkify(s.text))))))),
|
|
991
|
+
off > 0 ? React.createElement(Text, { color: theme.greyDim },
|
|
992
|
+
" \u2193 ",
|
|
993
|
+
off,
|
|
994
|
+
" sat\u0131r \u00B7 PageDown / Son") : null,
|
|
995
|
+
streaming && off === 0 ? (React.createElement(Box, { flexDirection: "row" },
|
|
996
|
+
React.createElement(Text, { color: theme.redBright, bold: true }, "\u23FA "),
|
|
997
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
998
|
+
React.createElement(Text, { color: theme.white }, streaming)))) : null));
|
|
999
|
+
})()),
|
|
835
1000
|
perm ? (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.red, paddingX: 1, marginTop: 1 },
|
|
836
1001
|
React.createElement(Text, { color: theme.redBright, bold: true }, t('perm.title', perm.label)),
|
|
837
1002
|
permMode === 'feedback' ? (React.createElement(React.Fragment, null,
|
|
@@ -908,4 +1073,9 @@ function App() {
|
|
|
908
1073
|
tipText(tipId))),
|
|
909
1074
|
React.createElement(StatusLine, { model: config.model, ctxTokens: ctxTokens, trust: trustLevel }))) : null));
|
|
910
1075
|
}
|
|
911
|
-
|
|
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/commands.js
CHANGED
|
@@ -18,6 +18,7 @@ import { programText, tier } from './program.js';
|
|
|
18
18
|
export const COMMANDS = [
|
|
19
19
|
{ name: '/help', desc: 'komutları ve ipuçlarını göster' },
|
|
20
20
|
{ name: '/clear', desc: 'sohbeti ve geçmişi temizle' },
|
|
21
|
+
{ name: '/kopyala', desc: 'son yanıtı panoya kopyala (/kopyala hepsi · tüm sohbet)' },
|
|
21
22
|
{ name: '/compact', desc: 'geçmişi modelle özetleyip bağlamı küçült' },
|
|
22
23
|
{ name: '/context', desc: 'bağlam / token kullanımını göster' },
|
|
23
24
|
{ name: '/cost', desc: 'oturum token tahminini göster' },
|
package/dist/theme.js
CHANGED
package/dist/tui.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
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 { loadConfig, streamChat } from './api.js';
|
|
8
|
+
import { itemAnsi, bannerAnsi } from './ansi.js';
|
|
9
|
+
import { theme, VERSION } from './theme.js';
|
|
10
|
+
import { cleanModelText } from './textclean.js';
|
|
11
|
+
const RESET = '\x1b[0m';
|
|
12
|
+
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` : ''; };
|
|
13
|
+
const paint = (s, c, bold = false) => `${bold ? '\x1b[1m' : ''}${c ? hex(c) : ''}${s}${RESET}`;
|
|
14
|
+
const cols = () => process.stdout.columns || 80;
|
|
15
|
+
export async function runTui() {
|
|
16
|
+
const config = loadConfig();
|
|
17
|
+
const history = []; // model bağlamı {role, content}
|
|
18
|
+
let inputBuf = '';
|
|
19
|
+
let busy = false;
|
|
20
|
+
let streamPreview = ''; // akış sırasında footer'da gösterilen ham metin
|
|
21
|
+
let spin = 0;
|
|
22
|
+
const SPIN = ['·', '✢', '✳', '✶', '✻', '✽', '✶', '✳', '✢'];
|
|
23
|
+
// ── Geçmişe kalıcı yaz (scrollback) ──
|
|
24
|
+
const printItem = (it) => { logUpdate.clear(); process.stdout.write(itemAnsi(it, cols()) + '\n'); renderFooter(); };
|
|
25
|
+
// ── Canlı footer (log-update yönetir) ──
|
|
26
|
+
function renderFooter() {
|
|
27
|
+
const lines = [];
|
|
28
|
+
if (busy && !streamPreview)
|
|
29
|
+
lines.push(paint(`${SPIN[spin % SPIN.length]} `, theme.red) + paint('yanıt bekleniyor…', theme.grey));
|
|
30
|
+
if (streamPreview) {
|
|
31
|
+
const tail = streamPreview.split('\n').slice(-12); // footer'ı sınırla (uzun akışta taşmasın)
|
|
32
|
+
lines.push(paint('⏺ ', theme.redBright, true) + paint(tail.shift() || '', theme.white));
|
|
33
|
+
for (const l of tail)
|
|
34
|
+
lines.push(' ' + paint(l, theme.white));
|
|
35
|
+
}
|
|
36
|
+
lines.push('');
|
|
37
|
+
lines.push(paint('❯ ', theme.redBright, true) + inputBuf + (busy ? '' : paint('▌', theme.greyDim)));
|
|
38
|
+
lines.push(paint(' /kopyala panoya · Ctrl+C çıkış', theme.greyDim));
|
|
39
|
+
logUpdate(lines.join('\n'));
|
|
40
|
+
}
|
|
41
|
+
// ── Bir sohbet turu (Milestone 1: araç yok, saf metin) ──
|
|
42
|
+
async function runTurn(userText) {
|
|
43
|
+
busy = true;
|
|
44
|
+
streamPreview = '';
|
|
45
|
+
history.push({ role: 'user', content: userText });
|
|
46
|
+
const spinTimer = setInterval(() => { spin++; if (busy && !streamPreview)
|
|
47
|
+
renderFooter(); }, 120);
|
|
48
|
+
let answer = '';
|
|
49
|
+
try {
|
|
50
|
+
for await (const ev of streamChat(history, [], config)) {
|
|
51
|
+
if (ev.type === 'text') {
|
|
52
|
+
answer += ev.text;
|
|
53
|
+
streamPreview = cleanModelText(answer);
|
|
54
|
+
renderFooter();
|
|
55
|
+
}
|
|
56
|
+
else if (ev.type === 'error') {
|
|
57
|
+
answer += `\n[hata: ${ev.error}]`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
answer += `\n[bağlantı hatası: ${e?.message || e}]`;
|
|
63
|
+
}
|
|
64
|
+
clearInterval(spinTimer);
|
|
65
|
+
answer = cleanModelText(answer).trim();
|
|
66
|
+
busy = false;
|
|
67
|
+
streamPreview = '';
|
|
68
|
+
if (answer) {
|
|
69
|
+
history.push({ role: 'assistant', content: answer });
|
|
70
|
+
printItem({ kind: 'assistant', text: answer });
|
|
71
|
+
}
|
|
72
|
+
renderFooter();
|
|
73
|
+
}
|
|
74
|
+
// ── Giriş (readline keypress, raw mode) ──
|
|
75
|
+
readline.emitKeypressEvents(process.stdin);
|
|
76
|
+
if (process.stdin.isTTY)
|
|
77
|
+
process.stdin.setRawMode(true);
|
|
78
|
+
process.stdout.write(bannerAnsi(cols()) + '\n');
|
|
79
|
+
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');
|
|
80
|
+
renderFooter();
|
|
81
|
+
process.stdin.on('keypress', (str, key) => {
|
|
82
|
+
if (key && key.ctrl && key.name === 'c') {
|
|
83
|
+
logUpdate.clear();
|
|
84
|
+
process.stdout.write('\n');
|
|
85
|
+
process.exit(0);
|
|
86
|
+
}
|
|
87
|
+
if (busy)
|
|
88
|
+
return; // tur sırasında giriş kilitli (M1)
|
|
89
|
+
if (key && key.name === 'return') {
|
|
90
|
+
const v = inputBuf.trim();
|
|
91
|
+
inputBuf = '';
|
|
92
|
+
if (!v) {
|
|
93
|
+
renderFooter();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (v === '/cikis' || v === '/exit' || v === '/quit') {
|
|
97
|
+
logUpdate.clear();
|
|
98
|
+
process.exit(0);
|
|
99
|
+
}
|
|
100
|
+
// /kopyala — son yanıtı OSC52 ile panoya
|
|
101
|
+
if (v === '/kopyala' || v === '/copy') {
|
|
102
|
+
const last = [...history].reverse().find((m) => m.role === 'assistant');
|
|
103
|
+
if (last) {
|
|
104
|
+
process.stdout.write(`\x1b]52;c;${Buffer.from(last.content, 'utf8').toString('base64')}\x07`);
|
|
105
|
+
printItem({ kind: 'note', text: `✓ Panoya kopyalandı (${last.content.length} karakter).` });
|
|
106
|
+
}
|
|
107
|
+
else
|
|
108
|
+
printItem({ kind: 'note', text: 'Kopyalanacak yanıt yok.' });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
printItem({ kind: 'user', text: v });
|
|
112
|
+
runTurn(v);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (key && (key.name === 'backspace')) {
|
|
116
|
+
inputBuf = inputBuf.slice(0, -1);
|
|
117
|
+
renderFooter();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (key && key.name === 'escape') {
|
|
121
|
+
inputBuf = '';
|
|
122
|
+
renderFooter();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
// yazdırılabilir karakter / yapıştırma
|
|
126
|
+
if (str && !key?.ctrl && !key?.meta && str !== '\r' && str !== '\n') {
|
|
127
|
+
inputBuf += str;
|
|
128
|
+
renderFooter();
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
// resize → footer'ı yeniden çiz (geçmiş scrollback'te terminalce sarılır)
|
|
132
|
+
process.stdout.on('resize', () => renderFooter());
|
|
133
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wormclaude",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.24",
|
|
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
|
},
|