wormclaude 1.0.20 → 1.0.22

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/ansi.js ADDED
@@ -0,0 +1,166 @@
1
+ // Geçmiş öğelerini doğrudan terminal scrollback'ine yazmak için ANSI string üreticisi.
2
+ // ink <Static> resize'da her şeyi yeniden bastığından (kademe/çift banner), geçmiş artık
3
+ // stdout'a BİR KEZ yazılır; ink yalnız canlı footer'ı yönetir. Böylece doğal kaydırma +
4
+ // metin seçip kopyalama çalışır. Markdown + sözdizimi vurgusu burada ANSI'ye çevrilir.
5
+ import { theme } from './theme.js';
6
+ import { highlight } from './highlight.js';
7
+ import { t } from './i18n.js';
8
+ const RESET = '\x1b[0m';
9
+ function hexAnsi(hex) {
10
+ if (!hex)
11
+ return '';
12
+ const m = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);
13
+ if (!m)
14
+ return '';
15
+ return `\x1b[38;2;${parseInt(m[1], 16)};${parseInt(m[2], 16)};${parseInt(m[3], 16)}m`;
16
+ }
17
+ const paint = (s, hex, bold = false, dim = false, italic = false) => `${bold ? '\x1b[1m' : ''}${dim ? '\x1b[2m' : ''}${italic ? '\x1b[3m' : ''}${hexAnsi(hex)}${s}${RESET}`;
18
+ // ── Satır kaydırma (görsel genişliğe göre basit sarma) ──
19
+ function wrap(text, width) {
20
+ const out = [];
21
+ for (const para of String(text).split('\n')) {
22
+ if (!para) {
23
+ out.push('');
24
+ continue;
25
+ }
26
+ let line = '';
27
+ for (const word of para.split(' ')) {
28
+ const cand = line ? line + ' ' + word : word;
29
+ if (cand.length <= width) {
30
+ line = cand;
31
+ continue;
32
+ }
33
+ if (line)
34
+ out.push(line);
35
+ if (word.length > width) {
36
+ let w = word;
37
+ while (w.length > width) {
38
+ out.push(w.slice(0, width));
39
+ w = w.slice(width);
40
+ }
41
+ line = w;
42
+ }
43
+ else
44
+ line = word;
45
+ }
46
+ out.push(line);
47
+ }
48
+ return out;
49
+ }
50
+ // ── Inline markdown → ANSI (**bold** `code` *italik* [t](u)) ──
51
+ function inlineAnsi(text) {
52
+ return text
53
+ .replace(/`([^`]+)`/g, (_m, c) => paint(c, theme.green))
54
+ .replace(/\*\*([^*]+)\*\*/g, (_m, c) => paint(c, undefined, true))
55
+ .replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, a, b) => `${a}${paint(' (' + b + ')', theme.redBright)}`);
56
+ }
57
+ const WORM_ROWS = [
58
+ '██╗ ██╗ ██████╗ ██████╗ ███╗ ███╗',
59
+ '██║ ██║██╔═══██╗██╔══██╗████╗ ████║',
60
+ '██║ █╗ ██║██║ ██║██████╔╝██╔████╔██║',
61
+ '██║███╗██║██║ ██║██╔══██╗██║╚██╔╝██║',
62
+ '╚███╔███╔╝╚██████╔╝██║ ██║██║ ╚═╝ ██║',
63
+ ' ╚══╝╚══╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝',
64
+ ];
65
+ const CLAUDE_ROWS = [
66
+ ' ██████╗██╗ █████╗ ██╗ ██╗██████╗ ███████╗',
67
+ '██╔════╝██║ ██╔══██╗██║ ██║██╔══██╗██╔════╝',
68
+ '██║ ██║ ███████║██║ ██║██║ ██║█████╗ ',
69
+ '██║ ██║ ██╔══██║██║ ██║██║ ██║██╔══╝ ',
70
+ '╚██████╗███████╗██║ ██║╚██████╔╝██████╔╝███████╗',
71
+ ' ╚═════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝',
72
+ ];
73
+ /** Banner'ı ANSI olarak üretir (dar ekranda tek kelime, geniş ekranda yan yana). */
74
+ export function bannerAnsi(cols) {
75
+ if (cols < 46)
76
+ return paint('WORMCLAUDE', theme.red, true) + '\n' + paint(' ' + t('banner.subtitle'), theme.greyDim);
77
+ const rows = cols >= 88 ? WORM_ROWS.map((w, i) => w + CLAUDE_ROWS[i]) : [...WORM_ROWS, ...CLAUDE_ROWS];
78
+ return rows.map((r) => paint(r, theme.red, true)).join('\n') + '\n' + paint(' ' + t('banner.subtitle'), theme.greyDim);
79
+ }
80
+ // ── Markdown bloğu → ANSI (kod blokları sözdizimi-vurgulu) ──
81
+ function markdownAnsi(text, cols) {
82
+ const W = Math.max(20, cols - 2);
83
+ const lines = text.split('\n');
84
+ const out = [];
85
+ let inCode = false, fence = '', lang = '', code = [];
86
+ const fenceRe = /^ *(`{3,}|~{3,}) *(\w*)? *$/;
87
+ const flushCode = () => {
88
+ if (lang)
89
+ out.push(paint(' ' + lang, theme.greyDim));
90
+ for (const toks of highlight(code.join('\n'), lang)) {
91
+ out.push(paint('│ ', theme.greyDim) + (toks.length ? toks.map((tk) => paint(tk.text, tk.color)).join('') : ' '));
92
+ }
93
+ code = [];
94
+ lang = '';
95
+ fence = '';
96
+ };
97
+ for (const line of lines) {
98
+ if (inCode) {
99
+ const fm = line.match(fenceRe);
100
+ if (fm && fm[1][0] === fence[0] && fm[1].length >= fence.length) {
101
+ inCode = false;
102
+ flushCode();
103
+ }
104
+ else
105
+ code.push(line);
106
+ continue;
107
+ }
108
+ const fm = line.match(fenceRe);
109
+ if (fm) {
110
+ inCode = true;
111
+ fence = fm[1];
112
+ lang = (fm[2] || '').trim();
113
+ continue;
114
+ }
115
+ const hm = line.match(/^ *(#{1,4}) +(.*)/);
116
+ const um = line.match(/^([ \t]*)([-*+]) +(.*)/);
117
+ const om = line.match(/^([ \t]*)(\d+)\. +(.*)/);
118
+ if (hm) {
119
+ const lvl = hm[1].length, inner = inlineAnsi(hm[2]);
120
+ out.push(lvl <= 2 ? paint(inner, theme.redBright, true) : lvl === 3 ? paint(inner, theme.white, true) : paint(inner, theme.grey, false, false, true));
121
+ }
122
+ else if (um || om) {
123
+ const [, ws, marker, itemText] = (um || om);
124
+ const prefix = om ? `${marker}. ` : '• ';
125
+ out.push(' '.repeat(ws.length) + paint(prefix, theme.grey) + inlineAnsi(itemText));
126
+ }
127
+ else if (line.trim() === '') {
128
+ out.push('');
129
+ }
130
+ else {
131
+ for (const w of wrap(line, W))
132
+ out.push(inlineAnsi(w));
133
+ }
134
+ }
135
+ if (inCode)
136
+ flushCode();
137
+ return out.join('\n');
138
+ }
139
+ /** Bir geçmiş öğesini scrollback'e yazılacak ANSI string'e çevirir (üstte 1 boş satır). */
140
+ export function itemAnsi(it, cols) {
141
+ const W = Math.max(24, cols - 2);
142
+ if (it.kind === 'banner')
143
+ return '\n' + bannerAnsi(cols);
144
+ if (it.kind === 'user') {
145
+ const inner = W - 4;
146
+ const top = paint('╭' + '─'.repeat(W - 2) + '╮', theme.greyDim);
147
+ const bot = paint('╰' + '─'.repeat(W - 2) + '╯', theme.greyDim);
148
+ const body = wrap('› ' + (it.text || ''), inner).map((ln) => paint('│ ', theme.greyDim) + paint(ln.padEnd(inner), theme.white) + paint(' │', theme.greyDim));
149
+ return '\n' + [top, ...body, bot].join('\n');
150
+ }
151
+ if (it.kind === 'assistant') {
152
+ const md = markdownAnsi(it.text || '', cols).split('\n');
153
+ return '\n' + md.map((ln, i) => (i === 0 ? paint('⏺ ', theme.redBright, true) + ln : ' ' + ln)).join('\n');
154
+ }
155
+ if (it.kind === 'tool') {
156
+ const n = (it.result || '').split('\n').length, chars = (it.result || '').length;
157
+ const head = paint('⏺ ', theme.redBright, true) + paint(it.label || '', theme.white);
158
+ const sub = paint(' ⎿ ', theme.greyDim) + (it.ok
159
+ ? paint(`${n} ${t('common.lines') || 'satır'} (${chars})`, theme.grey)
160
+ : paint('✗ ' + (it.result || '').slice(0, 160), theme.errorRed));
161
+ return '\n' + head + '\n' + sub;
162
+ }
163
+ if (it.kind === 'note')
164
+ return '\n' + wrap(it.text || '', W).map((ln) => paint(ln, theme.greyDim)).join('\n');
165
+ return '';
166
+ }
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, useApp, useInput } from 'ink';
3
+ import { render, Box, Text, Static, 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';
@@ -85,18 +85,10 @@ const _initHistory = () => {
85
85
  sys.push({ role: 'system', content: _memCtx });
86
86
  return sys;
87
87
  };
88
- // FULLSCREEN (alternate screen) — WormClaude'un yöntemi: tüm ekranı ink yönetir,
89
- // scrollback YOK, resize'da HER ŞEY yeniden çizilir → sarmalanma/kaskad olmaz.
90
- // ?1049h alt-screen · ?1007h alternate-scroll (fare tekerleği → ok tuşu; seçim bozulmaz)
91
- try {
92
- process.stdout.write('\x1b[?1049h\x1b[?1007h\x1b[2J\x1b[H');
93
- }
94
- catch { }
95
- const _leaveAlt = () => { try {
96
- process.stdout.write('\x1b[?1007l\x1b[?1049l');
97
- }
98
- catch { } };
99
- process.on('exit', _leaveAlt);
88
+ // NORMAL buffer (alt-screen YOK) — transcript <Static> ile gerçek terminal scrollback'ine
89
+ // basılır. Böylece terminalin doğal kaydırması (fare tekerleği / Shift+PageUp) ve metin
90
+ // seçip kopyalama tam çalışır. Eskiden ?1049h alt-screen kullanılıyordu; o, scrollback'i ve
91
+ // kopyalamayı engelliyordu — kaldırıldı.
100
92
  function useDimensions() {
101
93
  const [d, setD] = useState({
102
94
  cols: process.stdout.columns || 80,
@@ -448,25 +440,9 @@ function App() {
448
440
  else if (key.downArrow)
449
441
  setCmdSel((s) => (s + 1) % n);
450
442
  });
451
- // Geçmişte kaydırma: PageUp/PageDown her zaman; boş input'ta ok tuşları (fare
452
- // tekerleği alternate-scroll ile ok tuşu yollar tekerlekle kaydırma).
453
- useInput((_inp, key) => {
454
- if (!started || perm || ask || lang === null)
455
- return;
456
- // satır-temelli; üst sınır render'da off=min(scroll,maxScroll) ile kırpılır
457
- if (key.pageUp)
458
- setScroll((s) => s + 10);
459
- else if (key.pageDown)
460
- setScroll((s) => Math.max(0, s - 10));
461
- else if (input === '' && !busy) {
462
- if (key.upArrow)
463
- setScroll((s) => s + 3);
464
- else if (key.downArrow)
465
- setScroll((s) => Math.max(0, s - 3));
466
- }
467
- });
468
- // Yeni mesaj gelince en alta dön
469
- useEffect(() => { setScroll(0); }, [items.length]);
443
+ // NOT: Transcript artık <Static> ile terminal scrollback'ine basılıyor; kaydırma
444
+ // terminalin kendi işi (fare tekerleği / Shift+PageUp). Tuşları YAKALAMIYORUZ ki
445
+ // doğal kaydırma + metin seçip kopyalama bozulmasın.
470
446
  // Başlangıçta güven seviyesini (Doğrulanmış Araştırmacı Programı) çek → StatusLine rozeti
471
447
  useEffect(() => {
472
448
  if (!started)
@@ -812,10 +788,9 @@ function App() {
812
788
  runAgent(v);
813
789
  }
814
790
  const { cols, rows } = useDimensions();
815
- // İlk açılış: dil seçimi
791
+ // İlk açılış: dil seçimi (banner stdout'a basıldı; burada yalnız seçim footer'ı)
816
792
  if (lang === null) {
817
- return (React.createElement(Box, { flexDirection: "column", height: rows, width: cols },
818
- React.createElement(Banner, null),
793
+ return (React.createElement(Box, { flexDirection: "column", width: cols },
819
794
  React.createElement(Box, { flexDirection: "column", marginTop: 1, paddingX: 1 },
820
795
  React.createElement(Text, { color: theme.redBright, bold: true }, t('lang.title')),
821
796
  React.createElement(Box, { flexDirection: "column", marginTop: 1 },
@@ -827,14 +802,11 @@ function App() {
827
802
  t('lang.en'))),
828
803
  React.createElement(Text, { color: theme.greyDim },
829
804
  " ",
830
- t('lang.hint'))),
831
- React.createElement(Box, { flexGrow: 1 }),
832
- React.createElement(StatusLine, { model: config.model, ctxTokens: 0 })));
805
+ t('lang.hint')))));
833
806
  }
834
- // Açılış: klasöre güven sorusu (WormClaude tarzı)
807
+ // Açılış: klasöre güven sorusu (banner stdout'ta; burada yalnız soru footer'ı)
835
808
  if (!started) {
836
- return (React.createElement(Box, { flexDirection: "column", height: rows, width: cols },
837
- React.createElement(Banner, null),
809
+ return (React.createElement(Box, { flexDirection: "column", width: cols },
838
810
  React.createElement(Box, { flexDirection: "column", marginTop: 1, paddingX: 1 },
839
811
  React.createElement(Text, { color: theme.redBright, bold: true }, t('trust.accessing')),
840
812
  React.createElement(Text, null, " "),
@@ -852,37 +824,14 @@ function App() {
852
824
  t('trust.no'))),
853
825
  React.createElement(Text, { color: theme.greyDim },
854
826
  " ",
855
- t('trust.hint'))),
856
- React.createElement(Box, { flexGrow: 1 }),
857
- React.createElement(StatusLine, { model: config.model, ctxTokens: 0 })));
827
+ t('trust.hint')))));
858
828
  }
859
- return (React.createElement(Box, { flexDirection: "column", height: rows, width: cols },
860
- React.createElement(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden" }, (() => {
861
- // Satır-temelli kaydırma (Claude tarzı): tüm öğeleri satıra çevir, pencere göster
862
- const all = buildLines(items, cols);
863
- const reserve = busy ? 4 : 6;
864
- const avail = Math.max(4, rows - reserve - (streaming ? 3 : 0) - 2); // -2: göstergeler
865
- const maxScroll = Math.max(0, all.length - avail);
866
- const off = Math.min(scroll, maxScroll); // alttan kaç satır yukarı
867
- const startLine = Math.max(0, all.length - avail - off);
868
- const view = all.slice(startLine, startLine + avail);
869
- return (React.createElement(React.Fragment, null,
870
- startLine > 0 ? React.createElement(Text, { color: theme.greyDim },
871
- " \u2191 ",
872
- startLine,
873
- " sat\u0131r \u00B7 PageUp") : null,
874
- view.map((segs, i) => (React.createElement(Text, { key: i }, segs.length === 0
875
- ? ' '
876
- : segs.map((s, j) => (React.createElement(Text, { key: j, color: s.dim ? theme.greyDim : s.color, bold: s.bold }, linkify(s.text))))))),
877
- off > 0 ? React.createElement(Text, { color: theme.greyDim },
878
- " \u2193 ",
879
- off,
880
- " sat\u0131r \u00B7 PageDown / Son") : null,
881
- streaming && off === 0 ? (React.createElement(Box, { flexDirection: "row" },
882
- React.createElement(Text, { color: theme.redBright, bold: true }, "\u23FA "),
883
- React.createElement(Box, { flexDirection: "column" },
884
- React.createElement(Text, { color: theme.white }, streaming)))) : null));
885
- })()),
829
+ return (React.createElement(Box, { flexDirection: "column", width: cols },
830
+ React.createElement(Static, { items: items }, (item, i) => React.createElement(RenderItem, { key: i, item: item })),
831
+ streaming ? (React.createElement(Box, { marginTop: 1, flexDirection: "row" },
832
+ React.createElement(Text, { color: theme.redBright, bold: true }, "\u23FA "),
833
+ React.createElement(Box, { flexDirection: "column", flexGrow: 1 },
834
+ React.createElement(MarkdownDisplay, { text: streaming })))) : null,
886
835
  perm ? (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.red, paddingX: 1, marginTop: 1 },
887
836
  React.createElement(Text, { color: theme.redBright, bold: true }, t('perm.title', perm.label)),
888
837
  permMode === 'feedback' ? (React.createElement(React.Fragment, null,
@@ -0,0 +1,132 @@
1
+ // Bağımlılıksız terminal sözdizimi vurgusu (lowlight/highlight.js yerine hafif tokenizer).
2
+ // Kod bloklarını dile göre token'lara böler; her token'a tema rengi atar. Asla patlamaz —
3
+ // tanımadığı dilde ortak kurallarla (string/sayı/yorum/anahtar kelime) renklendirir.
4
+ import { theme } from './theme.js';
5
+ // Dil bazlı anahtar kelime setleri (ortak + spesifik).
6
+ const KW = {
7
+ js: ['const', 'let', 'var', 'function', 'return', 'if', 'else', 'for', 'while', 'do', 'switch', 'case', 'break', 'continue', 'new', 'class', 'extends', 'super', 'this', 'typeof', 'instanceof', 'in', 'of', 'await', 'async', 'yield', 'try', 'catch', 'finally', 'throw', 'import', 'export', 'from', 'default', 'delete', 'void', 'null', 'undefined', 'true', 'false'],
8
+ ts: ['interface', 'type', 'enum', 'implements', 'public', 'private', 'protected', 'readonly', 'abstract', 'namespace', 'declare', 'as', 'keyof', 'infer', 'satisfies'],
9
+ py: ['def', 'class', 'return', 'if', 'elif', 'else', 'for', 'while', 'break', 'continue', 'import', 'from', 'as', 'with', 'try', 'except', 'finally', 'raise', 'lambda', 'yield', 'global', 'nonlocal', 'pass', 'assert', 'del', 'in', 'is', 'not', 'and', 'or', 'None', 'True', 'False', 'self', 'async', 'await'],
10
+ bash: ['if', 'then', 'else', 'elif', 'fi', 'for', 'while', 'do', 'done', 'case', 'esac', 'function', 'in', 'return', 'export', 'local', 'echo', 'cd', 'source', 'set', 'unset'],
11
+ go: ['func', 'package', 'import', 'var', 'const', 'type', 'struct', 'interface', 'map', 'chan', 'go', 'defer', 'return', 'if', 'else', 'for', 'range', 'switch', 'case', 'select', 'break', 'continue', 'nil', 'true', 'false'],
12
+ rust: ['fn', 'let', 'mut', 'const', 'struct', 'enum', 'impl', 'trait', 'pub', 'use', 'mod', 'match', 'if', 'else', 'for', 'while', 'loop', 'return', 'self', 'Self', 'where', 'async', 'await', 'move', 'ref', 'true', 'false'],
13
+ java: ['public', 'private', 'protected', 'class', 'interface', 'extends', 'implements', 'static', 'final', 'void', 'new', 'return', 'if', 'else', 'for', 'while', 'switch', 'case', 'break', 'continue', 'try', 'catch', 'finally', 'throw', 'throws', 'import', 'package', 'this', 'super', 'null', 'true', 'false'],
14
+ css: ['important', 'inherit', 'initial', 'auto', 'none', 'flex', 'grid', 'block', 'inline'],
15
+ };
16
+ function langKeywords(lang) {
17
+ const l = (lang || '').toLowerCase();
18
+ const base = [];
19
+ if (l === 'ts' || l === 'tsx' || l === 'typescript')
20
+ base.push(...KW.js, ...KW.ts);
21
+ else if (l === 'js' || l === 'jsx' || l === 'javascript' || l === 'json' || l === 'node')
22
+ base.push(...KW.js);
23
+ else if (l === 'py' || l === 'python')
24
+ base.push(...KW.py);
25
+ else if (l === 'sh' || l === 'bash' || l === 'shell' || l === 'zsh')
26
+ base.push(...KW.bash);
27
+ else if (l === 'go' || l === 'golang')
28
+ base.push(...KW.go);
29
+ else if (l === 'rs' || l === 'rust')
30
+ base.push(...KW.rust);
31
+ else if (l === 'java')
32
+ base.push(...KW.java);
33
+ else if (l === 'css' || l === 'scss')
34
+ base.push(...KW.css);
35
+ else
36
+ base.push(...KW.js, ...KW.py); // bilinmeyen dil → en yaygın iki set
37
+ return new Set(base);
38
+ }
39
+ const lineComment = (lang) => {
40
+ const l = (lang || '').toLowerCase();
41
+ if (['py', 'python', 'sh', 'bash', 'shell', 'zsh', 'yaml', 'yml', 'rb', 'ruby', 'toml'].includes(l))
42
+ return '#';
43
+ return '//';
44
+ };
45
+ // Bir satırı token'lara böl. Basit ama sağlam bir durum makinesi.
46
+ function tokenizeLine(line, kw, lc) {
47
+ const toks = [];
48
+ let i = 0;
49
+ const n = line.length;
50
+ const push = (text, color) => { if (text)
51
+ toks.push({ text, color }); };
52
+ while (i < n) {
53
+ const ch = line[i];
54
+ // satır yorumu (// veya #)
55
+ if (line.startsWith(lc, i)) {
56
+ push(line.slice(i), theme.synComment);
57
+ break;
58
+ }
59
+ // dizgi: " ' `
60
+ if (ch === '"' || ch === "'" || ch === '`') {
61
+ let j = i + 1;
62
+ while (j < n && line[j] !== ch) {
63
+ if (line[j] === '\\')
64
+ j++;
65
+ j++;
66
+ }
67
+ push(line.slice(i, Math.min(j + 1, n)), theme.synString);
68
+ i = j + 1;
69
+ continue;
70
+ }
71
+ // sayı
72
+ if (/[0-9]/.test(ch) && (i === 0 || !/[A-Za-z_]/.test(line[i - 1]))) {
73
+ let j = i;
74
+ while (j < n && /[0-9a-fA-FxX._]/.test(line[j]))
75
+ j++;
76
+ push(line.slice(i, j), theme.synNumber);
77
+ i = j;
78
+ continue;
79
+ }
80
+ // tanımlayıcı / anahtar kelime
81
+ if (/[A-Za-z_$@]/.test(ch)) {
82
+ let j = i;
83
+ while (j < n && /[A-Za-z0-9_$]/.test(line[j]))
84
+ j++;
85
+ const word = line.slice(i, j);
86
+ const after = line[j];
87
+ if (kw.has(word))
88
+ push(word, theme.synKeyword);
89
+ else if (after === '(')
90
+ push(word, theme.synFunc); // fonksiyon çağrısı
91
+ else if (/^[A-Z]/.test(word))
92
+ push(word, theme.synType); // Tip/Sınıf
93
+ else
94
+ push(word); // düz
95
+ i = j;
96
+ continue;
97
+ }
98
+ // tek karakter (operatör/noktalama) — düz
99
+ push(ch);
100
+ i++;
101
+ }
102
+ return toks;
103
+ }
104
+ /** Kodu satır-satır token dizilerine çevirir. Her satır Tok[]. */
105
+ export function highlight(code, lang = '') {
106
+ try {
107
+ const kw = langKeywords(lang);
108
+ const lc = lineComment(lang);
109
+ let blockComment = false; // /* ... */ için
110
+ return code.split('\n').map((line) => {
111
+ // çok-satırlı /* */ yorum bloğu (basit destek)
112
+ if (blockComment) {
113
+ const end = line.indexOf('*/');
114
+ if (end === -1)
115
+ return [{ text: line, color: theme.synComment }];
116
+ blockComment = false;
117
+ const rest = tokenizeLine(line.slice(end + 2), kw, lc);
118
+ return [{ text: line.slice(0, end + 2), color: theme.synComment }, ...rest];
119
+ }
120
+ const start = line.indexOf('/*');
121
+ if (start !== -1 && line.indexOf('*/', start) === -1) {
122
+ blockComment = true;
123
+ const pre = tokenizeLine(line.slice(0, start), kw, lc);
124
+ return [...pre, { text: line.slice(start), color: theme.synComment }];
125
+ }
126
+ return tokenizeLine(line, kw, lc);
127
+ });
128
+ }
129
+ catch {
130
+ return code.split('\n').map((l) => [{ text: l, color: theme.synString }]);
131
+ }
132
+ }
package/dist/markdown.js CHANGED
@@ -4,6 +4,18 @@ import React from 'react';
4
4
  import { Text, Box } from 'ink';
5
5
  import stringWidth from 'string-width';
6
6
  import { theme } from './theme.js';
7
+ import { highlight } from './highlight.js';
8
+ // Kod bloğunu sözdizimi-vurgulu render eder (sol kenarda ince kırmızı çubuk + dil etiketi).
9
+ function CodeBlock({ code, lang }) {
10
+ const hl = highlight(code.join('\n'), lang);
11
+ return (React.createElement(Box, { flexDirection: "column", marginY: 1 },
12
+ lang ? React.createElement(Text, { color: theme.greyDim },
13
+ ' ',
14
+ lang) : null,
15
+ hl.map((toks, i) => (React.createElement(Box, { key: i },
16
+ React.createElement(Text, { color: theme.greyDim }, "\u2502 "),
17
+ React.createElement(Text, null, toks.length ? toks.map((tk, j) => React.createElement(Text, { key: j, color: tk.color }, tk.text)) : ' '))))));
18
+ }
7
19
  // ── Inline markdown: **bold** *italik* ~~strike~~ `code` [text](url) <u>u</u> url ──
8
20
  const INLINE_RE = /(\*\*.*?\*\*|\*.*?\*|_.*?_|~~.*?~~|\[.*?\]\(.*?\)|`+.+?`+|<u>.*?<\/u>|https?:\/\/\S+)/g;
9
21
  export function RenderInline({ text }) {
@@ -122,7 +134,7 @@ export function MarkdownDisplay({ text, width }) {
122
134
  const blocks = [];
123
135
  let key = 0;
124
136
  const push = (n) => blocks.push(React.createElement(React.Fragment, { key: key++ }, n));
125
- let inCode = false, codeFence = '', codeLines = [];
137
+ let inCode = false, codeFence = '', codeLang = '', codeLines = [];
126
138
  let inTable = false, tHeaders = [], tRows = [];
127
139
  const flushTable = () => { if (tHeaders.length)
128
140
  push(React.createElement(Table, { headers: tHeaders, rows: tRows, width: W })); inTable = false; tHeaders = []; tRows = []; };
@@ -137,9 +149,10 @@ export function MarkdownDisplay({ text, width }) {
137
149
  if (inCode) {
138
150
  const fm = line.match(fenceRe);
139
151
  if (fm && fm[1][0] === codeFence[0] && fm[1].length >= codeFence.length) {
140
- push(React.createElement(Box, { flexDirection: "column", marginY: 1 }, codeLines.map((c, i) => React.createElement(Text, { key: i, color: theme.green }, c))));
152
+ push(React.createElement(CodeBlock, { code: codeLines, lang: codeLang }));
141
153
  inCode = false;
142
154
  codeFence = '';
155
+ codeLang = '';
143
156
  codeLines = [];
144
157
  }
145
158
  else
@@ -153,6 +166,7 @@ export function MarkdownDisplay({ text, width }) {
153
166
  flushTable();
154
167
  inCode = true;
155
168
  codeFence = fm[1];
169
+ codeLang = (fm[2] || '').trim();
156
170
  return;
157
171
  }
158
172
  if (rm && !inTable) {
@@ -214,7 +228,7 @@ export function MarkdownDisplay({ text, width }) {
214
228
  React.createElement(RenderInline, { text: line })));
215
229
  });
216
230
  if (inCode)
217
- push(React.createElement(Box, { flexDirection: "column", marginY: 1 }, codeLines.map((c, i) => React.createElement(Text, { key: i, color: theme.green }, c))));
231
+ push(React.createElement(CodeBlock, { code: codeLines, lang: codeLang }));
218
232
  if (inTable)
219
233
  flushTable();
220
234
  return React.createElement(Box, { flexDirection: "column" }, blocks);
package/dist/theme.js CHANGED
@@ -7,5 +7,13 @@ export const theme = {
7
7
  greyDim: '#666666',
8
8
  green: '#4ade80',
9
9
  errorRed: '#ff6b6b',
10
+ // ── Sözdizimi vurgusu (kod blokları) ──
11
+ synKeyword: '#ff5c5c', // anahtar kelimeler (if, function, def…) — marka kırmızısı
12
+ synString: '#4ade80', // dizgiler "…" '…' `…`
13
+ synNumber: '#f0b429', // sayılar
14
+ synComment: '#666666', // yorumlar
15
+ synFunc: '#4fc3f7', // fonksiyon/metot adları
16
+ synType: '#a78bfa', // tip/sınıf adları, sabitler
17
+ synProp: '#e0e0e0', // özellik/anahtar adları
10
18
  };
11
- export const VERSION = '1.0.19';
19
+ export const VERSION = '1.0.22';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wormclaude",
3
- "version": "1.0.20",
3
+ "version": "1.0.22",
4
4
  "description": "WormClaude CLI - uncensored security+code assistant (ink TUI, Claude-style)",
5
5
  "type": "module",
6
6
  "bin": {