wormclaude 1.0.15 → 1.0.16

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.
@@ -0,0 +1,117 @@
1
+ // Sohbet girişindeki @<yol> referanslarını çözer: dosya içeriğini / dizin listesini mesaja enjekte eder.
2
+ // Gemini atCommandProcessor parser'ından uyarlandı; sadeleştirildi (data-file/workspace yok). bashCwd'ye göreli çözer.
3
+ import * as fs from 'node:fs';
4
+ import * as path from 'node:path';
5
+ import { getBashCwd } from './tools.js';
6
+ const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', '.wormclaude', 'venv', '.next', 'build']);
7
+ const MAX_FILE_BYTES = 256 * 1024;
8
+ const MAX_INJECT_CHARS = 60000;
9
+ /** Metindeki kaçışsız @<yol>'ları ayrıştırır (boşluk/noktalamada biter, '.' uzantı-farkında). */
10
+ export function parseAtPaths(text) {
11
+ const out = [];
12
+ let i = 0;
13
+ while (i < text.length) {
14
+ // sonraki kaçışsız @
15
+ let at = -1;
16
+ for (let j = i; j < text.length; j++) {
17
+ if (text[j] === '@' && (j === 0 || text[j - 1] !== '\\')) {
18
+ at = j;
19
+ break;
20
+ }
21
+ }
22
+ if (at === -1)
23
+ break;
24
+ let end = at + 1;
25
+ let esc = false;
26
+ while (end < text.length) {
27
+ const c = text[end];
28
+ if (esc)
29
+ esc = false;
30
+ else if (c === '\\')
31
+ esc = true;
32
+ else if (/[\s,;!?()[\]{}]/.test(c))
33
+ break;
34
+ else if (c === '.') {
35
+ const n = text[end + 1] || '';
36
+ if (n === '' || /\s/.test(n))
37
+ break;
38
+ }
39
+ end++;
40
+ }
41
+ const raw = text.slice(at + 1, end).replace(/\\(.)/g, '$1');
42
+ if (raw)
43
+ out.push(raw);
44
+ i = end;
45
+ }
46
+ return out;
47
+ }
48
+ function fuzzyFind(root, name, depth = 0, budget = { n: 0 }) {
49
+ if (depth > 4 || budget.n > 3000)
50
+ return null;
51
+ let entries;
52
+ try {
53
+ entries = fs.readdirSync(root, { withFileTypes: true });
54
+ }
55
+ catch {
56
+ return null;
57
+ }
58
+ // önce bu dizindeki dosyalar
59
+ for (const e of entries) {
60
+ budget.n++;
61
+ if (e.isFile() && e.name.toLowerCase().includes(name.toLowerCase()))
62
+ return path.join(root, e.name);
63
+ }
64
+ for (const e of entries) {
65
+ if (e.isDirectory() && !SKIP_DIRS.has(e.name) && !e.name.startsWith('.')) {
66
+ const hit = fuzzyFind(path.join(root, e.name), name, depth + 1, budget);
67
+ if (hit)
68
+ return hit;
69
+ }
70
+ }
71
+ return null;
72
+ }
73
+ /**
74
+ * @-mention'ları çözer. Dosya bulunursa içeriği, dizinse listesi mesaj sonuna eklenir.
75
+ * @returns { augmented: modele gidecek (içerik eklenmiş) metin, files: çözülen yollar }
76
+ */
77
+ export function resolveAtMentions(text) {
78
+ const paths = parseAtPaths(text);
79
+ if (!paths.length)
80
+ return { augmented: text, files: [] };
81
+ const cwd = getBashCwd();
82
+ const blocks = [];
83
+ const found = [];
84
+ for (const p of paths) {
85
+ let target = path.resolve(cwd, p);
86
+ if (!fs.existsSync(target)) {
87
+ const hit = fuzzyFind(cwd, path.basename(p));
88
+ if (!hit)
89
+ continue;
90
+ target = hit;
91
+ }
92
+ try {
93
+ const st = fs.statSync(target);
94
+ const rel = path.relative(cwd, target) || target;
95
+ if (st.isDirectory()) {
96
+ const list = fs.readdirSync(target).slice(0, 100).join('\n');
97
+ blocks.push(`--- Dizin: ${rel} ---\n${list}`);
98
+ found.push(rel);
99
+ }
100
+ else if (st.size <= MAX_FILE_BYTES) {
101
+ const content = fs.readFileSync(target, 'utf8').slice(0, MAX_INJECT_CHARS);
102
+ blocks.push(`--- Dosya: ${rel} ---\n${content}`);
103
+ found.push(rel);
104
+ }
105
+ else {
106
+ blocks.push(`--- ${rel}: çok büyük (${Math.round(st.size / 1024)}KB), atlandı ---`);
107
+ }
108
+ }
109
+ catch { /* erişilemedi, atla */ }
110
+ }
111
+ if (!blocks.length)
112
+ return { augmented: text, files: [] };
113
+ return {
114
+ augmented: `${text}\n\n[Kullanıcının @ ile referansladığı içerik:]\n${blocks.join('\n\n')}`,
115
+ files: found,
116
+ };
117
+ }
package/dist/cli.js CHANGED
@@ -8,6 +8,8 @@ import { loadConfig, streamChat } from './api.js';
8
8
  import { allToolSchemas, executeToolCalls, executeTool, toolLabel, setToolConfig } from './tools.js';
9
9
  import { sanitizeError, sanitizeOutput } from './errorsan.js';
10
10
  import { cleanModelText } from './textclean.js';
11
+ import { MarkdownDisplay } from './markdown.js';
12
+ import { resolveAtMentions } from './atmention.js';
11
13
  import { summarizeTools } from './toolSummary.js';
12
14
  import { pickTipId, tipText } from './tips.js';
13
15
  import { t, cmdDesc, setLang, saveLang, loadLang, getLang } from './i18n.js';
@@ -221,8 +223,8 @@ function RenderItem({ item }) {
221
223
  if (item.kind === 'assistant') {
222
224
  return (React.createElement(Box, { marginTop: 1, flexDirection: "row" },
223
225
  React.createElement(Text, { color: theme.redBright, bold: true }, "\u23FA "),
224
- React.createElement(Box, { flexDirection: "column" },
225
- React.createElement(Text, { color: theme.white }, linkify(item.text)))));
226
+ React.createElement(Box, { flexDirection: "column", flexGrow: 1 },
227
+ React.createElement(MarkdownDisplay, { text: item.text }))));
226
228
  }
227
229
  if (item.kind === 'tool') {
228
230
  const lines = item.result.split('\n').length;
@@ -665,6 +667,29 @@ function App() {
665
667
  setCmdSel(0);
666
668
  if (!v)
667
669
  return;
670
+ // ! shell modu — LLM'siz doğrudan shell komutu (cmdsec ile gate'li). Sonucu modele bağlam olarak ekle.
671
+ if (v.startsWith('!') && v.length > 1) {
672
+ const cmd = v.slice(1).trim();
673
+ if (!cmd)
674
+ return;
675
+ push({ kind: 'user', text: v });
676
+ setBusy(true);
677
+ setThinking(false);
678
+ setPhase(t('phase.toolRun', 'shell'));
679
+ try {
680
+ const res = await executeTool('Bash', { command: cmd });
681
+ push({ kind: 'tool', label: `! ${cmd.slice(0, 60)}`, result: sanitizeOutput(res.output), ok: res.ok });
682
+ historyRef.current = [...historyRef.current, {
683
+ role: 'user',
684
+ content: `Şu shell komutunu çalıştırdım:\n\`\`\`\n${cmd}\n\`\`\`\nÇıktı:\n\`\`\`\n${(res.output || '').slice(0, 4000)}\n\`\`\``,
685
+ }];
686
+ }
687
+ catch (e) {
688
+ push({ kind: 'note', text: t('note.cmdErr', e?.message || e) });
689
+ }
690
+ setBusy(false);
691
+ return;
692
+ }
668
693
  if (v.startsWith('/')) {
669
694
  // Seçili menü öğesini çalıştır (tam eşleşme yoksa ve argüman yoksa)
670
695
  const firstTok = v.split(' ')[0];
@@ -760,7 +785,12 @@ function App() {
760
785
  setBusy(false);
761
786
  return;
762
787
  }
763
- runAgent(v);
788
+ // @dosya mention — referanslanan dosya/dizin içeriğini modele enjekte et, kullanıcıya orijinali göster
789
+ const at = resolveAtMentions(v);
790
+ if (at.files.length)
791
+ runAgent(at.augmented, v);
792
+ else
793
+ runAgent(v);
764
794
  }
765
795
  const { cols, rows } = useDimensions();
766
796
  // İlk açılış: dil seçimi
package/dist/commands.js CHANGED
@@ -12,6 +12,7 @@ import { cmdDesc, setLang, saveLang, getLang } from './i18n.js';
12
12
  import { loadSkills, getSkills, getSkillsDir, installSkill, updateSkill, removeSkill, getRegistry } from './skills.js';
13
13
  import { getApprovedCommands, approveCommands, unapproveCommands, clearApproved } from './cmdsec.js';
14
14
  import { isLearnEnabled, setLearnEnabled, getLearnFile, getLearnCount } from './learn.js';
15
+ import { saveMemoryFact, getMemoryPath, loadMemoryContext } from './memory.js';
15
16
  export const COMMANDS = [
16
17
  { name: '/help', desc: 'komutları ve ipuçlarını göster' },
17
18
  { name: '/clear', desc: 'sohbeti ve geçmişi temizle' },
@@ -24,7 +25,7 @@ export const COMMANDS = [
24
25
  { name: '/commit', desc: 'stage edilmiş değişiklikleri modelle commit et' },
25
26
  { name: '/review', desc: 'mevcut diff’i modelle incele' },
26
27
  { name: '/init', desc: 'projeyi tarayıp WORMCLAUDE.md üret' },
27
- { name: '/memory', desc: 'WORMCLAUDE.md hafızasını oku / satır ekle' },
28
+ { name: '/memory', desc: 'hafızayı göster / ekle / temizle (/memory <metin> · --global · temizle)' },
28
29
  { name: '/doctor', desc: 'sistem ve backend sağlık kontrolü' },
29
30
  { name: '/lang', desc: 'arayüz dilini değiştir (tr/en)' },
30
31
  { name: '/skills', desc: 'skill\'leri listele / indir / yeniden yükle' },
@@ -245,6 +246,11 @@ export async function runSlashCommand(input, ctx) {
245
246
  return true;
246
247
  }
247
248
  case '/init': {
249
+ const initDest = path.join(process.cwd(), 'WORMCLAUDE.md');
250
+ if (fs.existsSync(initDest) && fs.readFileSync(initDest, 'utf8').trim() && (arg || '').trim() !== 'force') {
251
+ ctx.note('WORMCLAUDE.md zaten var ve dolu. Üzerine yazmak için: /init force');
252
+ return true;
253
+ }
248
254
  const files = [];
249
255
  const walk = (d, depth = 0) => {
250
256
  if (depth > 3 || files.length > 400)
@@ -283,15 +289,45 @@ export async function runSlashCommand(input, ctx) {
283
289
  return true;
284
290
  }
285
291
  case '/memory': {
286
- const dest = path.join(process.cwd(), 'WORMCLAUDE.md');
287
- if (arg) {
288
- const prefix = fs.existsSync(dest) ? fs.readFileSync(dest, 'utf8').replace(/\s*$/, '') + '\n' : '';
289
- fs.writeFileSync(dest, prefix + `- ${arg}\n`);
290
- ctx.note(`Hafızaya eklendi: ${arg}`);
292
+ // .wormclaude/memory.md (oturumlar-arası hatıralar) — SaveMemory tool'u + oto-hafıza ile AYNI yer.
293
+ const a = (arg || '').trim();
294
+ // /memory temizle hatıra dosyasını sıfırla
295
+ if (a === 'temizle' || a === 'clear') {
296
+ try {
297
+ fs.writeFileSync(getMemoryPath(), '');
298
+ ctx.note('Hatıralar temizlendi.');
299
+ }
300
+ catch (e) {
301
+ ctx.note('Temizlenemedi: ' + (e?.message || e));
302
+ }
303
+ return true;
291
304
  }
292
- else {
293
- ctx.note(fs.existsSync(dest) ? fs.readFileSync(dest, 'utf8').slice(0, 6000) : 'WORMCLAUDE.md yok. /init ile oluştur veya /memory <metin> ile ekle.');
305
+ // /memory ekle <metin> veya /memory <metin> veya /memory --global <metin>
306
+ let scope = 'project';
307
+ let fact = a;
308
+ if (fact.startsWith('ekle '))
309
+ fact = fact.slice(5).trim();
310
+ if (fact.startsWith('--global ')) {
311
+ scope = 'global';
312
+ fact = fact.slice(9).trim();
313
+ }
314
+ else if (fact.startsWith('--project ')) {
315
+ scope = 'project';
316
+ fact = fact.slice(10).trim();
317
+ }
318
+ if (fact) {
319
+ try {
320
+ const file = saveMemoryFact(fact, scope);
321
+ ctx.note(`Hafızaya eklendi (${scope}): ${fact} → ${file}`);
322
+ }
323
+ catch (e) {
324
+ ctx.note('Eklenemedi: ' + (e?.message || e));
325
+ }
326
+ return true;
294
327
  }
328
+ // arg yok → mevcut hafızayı göster (memory.md + WORMCLAUDE.md birlikte)
329
+ const ctxStr = loadMemoryContext();
330
+ ctx.note(ctxStr ? ctxStr.slice(0, 6000) : 'Hafıza boş. /memory <metin> ile ekle, /init ile WORMCLAUDE.md üret.');
295
331
  return true;
296
332
  }
297
333
  case '/doctor': {
@@ -0,0 +1,221 @@
1
+ // Terminal markdown render (ink). Gemini MarkdownDisplay + InlineMarkdownRenderer + TableRenderer'dan
2
+ // uyarlandı; WormClaude theme.ts'e bağlandı; lowlight YOK (kod blokları tek accent renk — görsel cila v1).
3
+ import React from 'react';
4
+ import { Text, Box } from 'ink';
5
+ import stringWidth from 'string-width';
6
+ import { theme } from './theme.js';
7
+ // ── Inline markdown: **bold** *italik* ~~strike~~ `code` [text](url) <u>u</u> url ──
8
+ const INLINE_RE = /(\*\*.*?\*\*|\*.*?\*|_.*?_|~~.*?~~|\[.*?\]\(.*?\)|`+.+?`+|<u>.*?<\/u>|https?:\/\/\S+)/g;
9
+ export function RenderInline({ text }) {
10
+ if (!/[*_~`<[]|https?:/.test(text))
11
+ return React.createElement(Text, null, text);
12
+ const nodes = [];
13
+ let last = 0;
14
+ let m;
15
+ INLINE_RE.lastIndex = 0;
16
+ while ((m = INLINE_RE.exec(text)) !== null) {
17
+ if (m.index > last)
18
+ nodes.push(React.createElement(Text, { key: `t${last}` }, text.slice(last, m.index)));
19
+ const f = m[0];
20
+ const key = `m${m.index}`;
21
+ let node = null;
22
+ if (f.startsWith('**') && f.endsWith('**') && f.length > 4) {
23
+ node = React.createElement(Text, { key: key, bold: true }, f.slice(2, -2));
24
+ }
25
+ else if (f.length > 2 && ((f.startsWith('*') && f.endsWith('*')) || (f.startsWith('_') && f.endsWith('_'))) &&
26
+ // path-güvenli: kelime/yol-ayracı bitişiğinde italik sayma (dosya_adı, a/b_c bozulmasın)
27
+ !/\w/.test(text.substring(m.index - 1, m.index)) &&
28
+ !/\w/.test(text.substring(INLINE_RE.lastIndex, INLINE_RE.lastIndex + 1)) &&
29
+ !/\S[./\\]/.test(text.substring(m.index - 2, m.index)) &&
30
+ !/[./\\]\S/.test(text.substring(INLINE_RE.lastIndex, INLINE_RE.lastIndex + 2))) {
31
+ node = React.createElement(Text, { key: key, italic: true }, f.slice(1, -1));
32
+ }
33
+ else if (f.startsWith('~~') && f.endsWith('~~') && f.length > 4) {
34
+ node = React.createElement(Text, { key: key, strikethrough: true }, f.slice(2, -2));
35
+ }
36
+ else if (f.startsWith('`') && f.endsWith('`')) {
37
+ const cm = f.match(/^(`+)(.+?)\1$/s);
38
+ if (cm && cm[2])
39
+ node = React.createElement(Text, { key: key, color: theme.green }, cm[2]);
40
+ }
41
+ else if (f.startsWith('[') && f.includes('](') && f.endsWith(')')) {
42
+ const lm = f.match(/\[(.*?)\]\((.*?)\)/);
43
+ if (lm)
44
+ node = React.createElement(Text, { key: key },
45
+ lm[1],
46
+ React.createElement(Text, { color: theme.redBright },
47
+ " (",
48
+ lm[2],
49
+ ")"));
50
+ }
51
+ else if (f.startsWith('<u>') && f.endsWith('</u>')) {
52
+ node = React.createElement(Text, { key: key, underline: true }, f.slice(3, -4));
53
+ }
54
+ else if (/^https?:\/\//.test(f)) {
55
+ node = React.createElement(Text, { key: key, color: theme.redBright }, f);
56
+ }
57
+ nodes.push(node ?? React.createElement(Text, { key: key }, f));
58
+ last = INLINE_RE.lastIndex;
59
+ }
60
+ if (last < text.length)
61
+ nodes.push(React.createElement(Text, { key: `t${last}` }, text.slice(last)));
62
+ return React.createElement(Text, null, nodes);
63
+ }
64
+ // markdown'ı soyup görüntü genişliği (tablo hizalama)
65
+ export function plainLen(text) {
66
+ const clean = text
67
+ .replace(/\*\*(.*?)\*\*/g, '$1').replace(/\*(.*?)\*/g, '$1').replace(/_(.*?)_/g, '$1')
68
+ .replace(/~~(.*?)~~/g, '$1').replace(/`(.*?)`/g, '$1').replace(/<u>(.*?)<\/u>/g, '$1')
69
+ .replace(/\[(.*?)\]\(.*?\)/g, '$1');
70
+ return stringWidth(clean);
71
+ }
72
+ // ── Tablo (box-drawing) ──────────────────────────────────────────────────────
73
+ function Table({ headers, rows, width }) {
74
+ const cols = headers.map((h, i) => Math.max(plainLen(h), ...rows.map((r) => plainLen(r[i] || ''))) + 2);
75
+ const total = cols.reduce((s, w) => s + w + 1, 1);
76
+ const scale = total > width ? width / total : 1;
77
+ const w = cols.map((c) => Math.max(3, Math.floor(c * scale)));
78
+ const cell = (c, width, header) => {
79
+ const cw = Math.max(0, width - 2);
80
+ let s = c;
81
+ if (plainLen(c) > cw) {
82
+ let lo = 0, hi = c.length, best = '';
83
+ while (lo <= hi) {
84
+ const mid = (lo + hi) >> 1;
85
+ const cand = c.slice(0, mid);
86
+ if (plainLen(cand) <= cw - 1) {
87
+ best = cand;
88
+ lo = mid + 1;
89
+ }
90
+ else
91
+ hi = mid - 1;
92
+ }
93
+ s = best + '…';
94
+ }
95
+ const pad = ' '.repeat(Math.max(0, cw - plainLen(s)));
96
+ return header ? React.createElement(Text, { bold: true, color: theme.redBright },
97
+ React.createElement(RenderInline, { text: s }),
98
+ pad) : React.createElement(Text, null,
99
+ React.createElement(RenderInline, { text: s }),
100
+ pad);
101
+ };
102
+ const border = (l, mid, r) => (React.createElement(Text, { color: theme.greyDim }, l + w.map((x) => '─'.repeat(x)).join(mid) + r));
103
+ const row = (cells, header) => (React.createElement(Text, { color: theme.greyDim },
104
+ "\u2502 ",
105
+ w.map((x, i) => (React.createElement(React.Fragment, { key: i },
106
+ cell(cells[i] || '', x, header),
107
+ i < w.length - 1 ? ' │ ' : ''))),
108
+ " \u2502"));
109
+ return (React.createElement(Box, { flexDirection: "column", marginY: 1 },
110
+ border('┌', '┬', '┐'),
111
+ row(headers, true),
112
+ border('├', '┼', '┤'),
113
+ rows.map((r, i) => React.createElement(React.Fragment, { key: i }, row(r, false))),
114
+ border('└', '┴', '┘')));
115
+ }
116
+ // ── Blok parser ──────────────────────────────────────────────────────────────
117
+ export function MarkdownDisplay({ text, width }) {
118
+ if (!text)
119
+ return null;
120
+ const W = Math.max(20, (width ?? (process.stdout.columns || 80)) - 4);
121
+ const lines = text.split('\n');
122
+ const blocks = [];
123
+ let key = 0;
124
+ const push = (n) => blocks.push(React.createElement(React.Fragment, { key: key++ }, n));
125
+ let inCode = false, codeFence = '', codeLines = [];
126
+ let inTable = false, tHeaders = [], tRows = [];
127
+ const flushTable = () => { if (tHeaders.length)
128
+ push(React.createElement(Table, { headers: tHeaders, rows: tRows, width: W })); inTable = false; tHeaders = []; tRows = []; };
129
+ const headerRe = /^ *(#{1,4}) +(.*)/;
130
+ const fenceRe = /^ *(`{3,}|~{3,}) *(\w*)? *$/;
131
+ const ulRe = /^([ \t]*)([-*+]) +(.*)/;
132
+ const olRe = /^([ \t]*)(\d+)\. +(.*)/;
133
+ const hrRe = /^ *([-*_] *){3,} *$/;
134
+ const rowRe = /^\s*\|(.+)\|\s*$/;
135
+ const sepRe = /^\s*\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)+\|?\s*$/;
136
+ lines.forEach((line, idx) => {
137
+ if (inCode) {
138
+ const fm = line.match(fenceRe);
139
+ 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))));
141
+ inCode = false;
142
+ codeFence = '';
143
+ codeLines = [];
144
+ }
145
+ else
146
+ codeLines.push(line);
147
+ return;
148
+ }
149
+ const fm = line.match(fenceRe);
150
+ const rm = line.match(rowRe);
151
+ if (fm) {
152
+ if (inTable)
153
+ flushTable();
154
+ inCode = true;
155
+ codeFence = fm[1];
156
+ return;
157
+ }
158
+ if (rm && !inTable) {
159
+ if (idx + 1 < lines.length && sepRe.test(lines[idx + 1])) {
160
+ inTable = true;
161
+ tHeaders = rm[1].split('|').map((c) => c.trim());
162
+ tRows = [];
163
+ return;
164
+ }
165
+ push(React.createElement(Text, { color: theme.white },
166
+ React.createElement(RenderInline, { text: line })));
167
+ return;
168
+ }
169
+ if (inTable && sepRe.test(line))
170
+ return;
171
+ if (inTable && rm) {
172
+ const cells = rm[1].split('|').map((c) => c.trim());
173
+ while (cells.length < tHeaders.length)
174
+ cells.push('');
175
+ cells.length = tHeaders.length;
176
+ tRows.push(cells);
177
+ return;
178
+ }
179
+ if (inTable && !rm)
180
+ flushTable();
181
+ const hm = line.match(headerRe);
182
+ const um = line.match(ulRe);
183
+ const om = line.match(olRe);
184
+ if (hrRe.test(line) && line.trim().length >= 3) {
185
+ push(React.createElement(Text, { color: theme.greyDim }, '─'.repeat(Math.min(W, 40))));
186
+ return;
187
+ }
188
+ if (hm) {
189
+ const lvl = hm[1].length;
190
+ const inner = React.createElement(RenderInline, { text: hm[2] });
191
+ if (lvl <= 2)
192
+ push(React.createElement(Text, { bold: true, color: theme.redBright }, inner));
193
+ else if (lvl === 3)
194
+ push(React.createElement(Text, { bold: true, color: theme.white }, inner));
195
+ else
196
+ push(React.createElement(Text, { italic: true, color: theme.grey }, inner));
197
+ return;
198
+ }
199
+ if (um || om) {
200
+ const [, ws, marker, itemText] = (um || om);
201
+ const prefix = om ? `${marker}. ` : '• ';
202
+ push(React.createElement(Box, { paddingLeft: ws.length },
203
+ React.createElement(Text, { color: theme.grey }, prefix),
204
+ React.createElement(Box, { flexGrow: 1 },
205
+ React.createElement(Text, { color: theme.white },
206
+ React.createElement(RenderInline, { text: itemText })))));
207
+ return;
208
+ }
209
+ if (line.trim().length === 0) {
210
+ push(React.createElement(Box, { height: 1 }));
211
+ return;
212
+ }
213
+ push(React.createElement(Text, { color: theme.white, wrap: "wrap" },
214
+ React.createElement(RenderInline, { text: line })));
215
+ });
216
+ if (inCode)
217
+ push(React.createElement(Box, { flexDirection: "column", marginY: 1 }, codeLines.map((c, i) => React.createElement(Text, { key: i, color: theme.green }, c))));
218
+ if (inTable)
219
+ flushTable();
220
+ return React.createElement(Box, { flexDirection: "column" }, blocks);
221
+ }
package/dist/theme.js CHANGED
@@ -8,4 +8,4 @@ export const theme = {
8
8
  green: '#4ade80',
9
9
  errorRed: '#ff6b6b',
10
10
  };
11
- export const VERSION = '1.0.14';
11
+ export const VERSION = '1.0.16';
package/dist/tools.js CHANGED
@@ -27,6 +27,47 @@ export function getTodos() { return todosStore; }
27
27
  const MAX_LINES_TO_READ = 2000;
28
28
  const DEFAULT_BASH_TIMEOUT_MS = 120000;
29
29
  const MAX_BASH_TIMEOUT_MS = 600000;
30
+ // Bash çalışma dizini — oturum boyunca kalıcı. execSync her çağrıda taze shell açtığı için
31
+ // `cd` normalde kaybolur; burada cwd'yi takip edip her komuta geçiriyoruz.
32
+ let bashCwd;
33
+ export function getBashCwd() { return bashCwd || process.cwd(); }
34
+ export function setBashCwd(dir) { bashCwd = dir; }
35
+ // Komutu çalıştırır ve sonrasında cwd değişikliğini (cd) yakalayıp bashCwd'yi günceller.
36
+ // POSIX: pwd'yi temp dosyaya yazan, çıkış kodunu KORUYAN sarmalayıcı (hata propagasyonu bozulmaz).
37
+ // Windows: komutu olduğu gibi çalıştır, sonra baştaki `cd <hedef>`'i regex ile yakala (best-effort).
38
+ function runBashCapturingCwd(command, timeout) {
39
+ const cwd = getBashCwd();
40
+ const opts = { encoding: 'utf8', timeout, maxBuffer: 10 * 1024 * 1024, windowsHide: true, cwd };
41
+ if (process.platform === 'win32') {
42
+ const out = execSync(command, opts);
43
+ // best-effort: "cd <hedef>" / "cd /d <hedef>" (zincirsiz tek komut)
44
+ const m = /^\s*cd\s+(?:\/d\s+)?"?([^"&|<>]+?)"?\s*$/i.exec(command);
45
+ if (m) {
46
+ const nd = path.resolve(cwd, m[1].trim());
47
+ try {
48
+ if (fs.statSync(nd).isDirectory())
49
+ bashCwd = nd;
50
+ }
51
+ catch { /* yok say */ }
52
+ }
53
+ return out;
54
+ }
55
+ // POSIX
56
+ const pwdFile = path.join(os.tmpdir(), `wc_pwd_${process.pid}_${Date.now()}.tmp`);
57
+ const wrapped = `{ ${command}\n}; __wc=$?; pwd > '${pwdFile}' 2>/dev/null; exit $__wc`;
58
+ try {
59
+ return execSync(wrapped, { ...opts, shell: '/bin/sh' });
60
+ }
61
+ finally {
62
+ try {
63
+ const p = fs.readFileSync(pwdFile, 'utf8').trim();
64
+ if (p)
65
+ bashCwd = p;
66
+ fs.unlinkSync(pwdFile);
67
+ }
68
+ catch { /* dosya yoksa yok say */ }
69
+ }
70
+ }
30
71
  // WormClaude davranışı: bir dosyayı düzenlemeden/üzerine yazmadan önce
31
72
  // en az bir kez okumuş olman gerekir. Okunan dosyaları burada izliyoruz.
32
73
  const readFiles = new Set();
@@ -880,7 +921,7 @@ export async function executeTool(name, args) {
880
921
  if (name === 'Bash') {
881
922
  if (args.run_in_background) {
882
923
  const task = tasks.create('shell', String(args.command).slice(0, 60));
883
- const child = spawn(String(args.command), { shell: true, windowsHide: true });
924
+ const child = spawn(String(args.command), { shell: true, windowsHide: true, cwd: getBashCwd() });
884
925
  task.child = child;
885
926
  child.stdout?.on('data', (d) => tasks.append(task.id, d.toString()));
886
927
  child.stderr?.on('data', (d) => tasks.append(task.id, d.toString()));
@@ -894,9 +935,7 @@ export async function executeTool(name, args) {
894
935
  let timeout = Number(args.timeout) || DEFAULT_BASH_TIMEOUT_MS;
895
936
  if (timeout > MAX_BASH_TIMEOUT_MS)
896
937
  timeout = MAX_BASH_TIMEOUT_MS;
897
- const out = execSync(String(args.command), {
898
- encoding: 'utf8', timeout, maxBuffer: 10 * 1024 * 1024, windowsHide: true,
899
- });
938
+ const out = runBashCapturingCwd(String(args.command), timeout);
900
939
  return { ok: true, output: (out || '(no output)').slice(0, 20000) };
901
940
  }
902
941
  if (name === 'Agent') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wormclaude",
3
- "version": "1.0.15",
3
+ "version": "1.0.16",
4
4
  "description": "WormClaude CLI - uncensored security+code assistant (ink TUI, Claude-style)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,7 +19,8 @@
19
19
  "ink": "^5.0.1",
20
20
  "ink-spinner": "^5.0.0",
21
21
  "ink-text-input": "^6.0.0",
22
- "react": "^18.3.1"
22
+ "react": "^18.3.1",
23
+ "string-width": "^7.2.0"
23
24
  },
24
25
  "devDependencies": {
25
26
  "@types/node": "^22.10.2",