wormclaude 1.0.146 → 1.0.148

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,93 @@
1
+ // Checkpoint / geri-al (rewind) — her Write/Edit ÖNCESİ dosyanın eski hali saklanır;
2
+ // /rewind ile son değişiklik (ya da belirli bir checkpoint'e kadar) geri alınır.
3
+ // Snapshot Write/Edit handler'ında, yolu resolveWs ile çözülmüş HALİYLE alınır (doğru dosya).
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ const MAX_SNAP = 5 * 1024 * 1024; // 5MB üstü dosyanın içeriğini saklama (bellek koruması)
7
+ const MAX_KEEP = 300; // en fazla bu kadar checkpoint tut
8
+ let _cps = [];
9
+ let _seq = 0;
10
+ let _storePath = '';
11
+ function _save() {
12
+ if (!_storePath)
13
+ return;
14
+ try {
15
+ fs.mkdirSync(path.dirname(_storePath), { recursive: true });
16
+ fs.writeFileSync(_storePath, JSON.stringify({ seq: _seq, cps: _cps.slice(-MAX_KEEP) }));
17
+ }
18
+ catch { }
19
+ }
20
+ /** Oturum başında çağrılır — checkpoint deposunu (workspace/.wormclaude/checkpoints.json)
21
+ * belirler ve varsa yükler (çökme/resume sonrası geri-al hâlâ çalışsın). */
22
+ export function setCheckpointStore(workspaceDir) {
23
+ _storePath = path.join(workspaceDir, '.wormclaude', 'checkpoints.json');
24
+ try {
25
+ const raw = JSON.parse(fs.readFileSync(_storePath, 'utf8'));
26
+ if (Array.isArray(raw.cps)) {
27
+ _cps = raw.cps;
28
+ _seq = Number(raw.seq) || _cps.length;
29
+ }
30
+ }
31
+ catch {
32
+ _cps = [];
33
+ _seq = 0;
34
+ }
35
+ }
36
+ /** Write/Edit BAŞARIYLA yazdıktan sonra çağrılır. before=null → dosya yeni oluşturuldu. */
37
+ export function recordCheckpoint(file, before, label) {
38
+ const tooBig = before != null && before.length > MAX_SNAP;
39
+ _cps.push({ id: ++_seq, file, before: tooBig ? null : before, tooBig, time: Date.now(), label });
40
+ if (_cps.length > MAX_KEEP)
41
+ _cps = _cps.slice(-MAX_KEEP);
42
+ _save();
43
+ }
44
+ export function listCheckpoints() { return _cps.slice(); }
45
+ export function clearCheckpoints() { _cps = []; _seq = 0; _save(); }
46
+ function _restore(cp) {
47
+ if (cp.tooBig)
48
+ return { ok: false, msg: `çok büyük dosya, geri alınamadı: ${cp.file}` };
49
+ try {
50
+ if (cp.before == null) {
51
+ try {
52
+ fs.unlinkSync(cp.file);
53
+ }
54
+ catch { }
55
+ return { ok: true, msg: `silindi (yeni oluşturulmuştu): ${cp.file}` };
56
+ }
57
+ fs.mkdirSync(path.dirname(cp.file), { recursive: true });
58
+ fs.writeFileSync(cp.file, cp.before);
59
+ return { ok: true, msg: `geri yüklendi: ${cp.file}` };
60
+ }
61
+ catch (e) {
62
+ return { ok: false, msg: `hata (${cp.file}): ${e?.message || e}` };
63
+ }
64
+ }
65
+ /** Son dosya değişikliğini geri al. */
66
+ export function rewindLast() {
67
+ const cp = _cps.pop();
68
+ if (!cp)
69
+ return { ok: false, msg: 'Geri alınacak dosya değişikliği yok.' };
70
+ const r = _restore(cp);
71
+ _save();
72
+ return { ok: r.ok, msg: `#${cp.id} (${cp.label}) ${r.msg}` };
73
+ }
74
+ /** Verilen id ve ondan SONRAKİ tüm değişiklikleri (en yeniden eskiye) geri al. */
75
+ export function rewindTo(id) {
76
+ const idx = _cps.findIndex((c) => c.id === id);
77
+ if (idx < 0)
78
+ return { ok: false, msg: `Checkpoint #${id} bulunamadı (/checkpoints ile listele).` };
79
+ const undo = _cps.splice(idx); // idx..son
80
+ const msgs = [];
81
+ for (let i = undo.length - 1; i >= 0; i--) { // en yeniden eskiye → dosya en eski haline döner
82
+ const r = _restore(undo[i]);
83
+ msgs.push((r.ok ? '✓ ' : '✗ ') + `#${undo[i].id} ${r.msg}`);
84
+ }
85
+ _save();
86
+ return { ok: true, msg: `${undo.length} değişiklik geri alındı (#${id}'e kadar):\n ` + msgs.join('\n ') };
87
+ }
88
+ /** Tüm değişiklikleri geri al (oturum başındaki hale). */
89
+ export function rewindAll() {
90
+ if (!_cps.length)
91
+ return { ok: false, msg: 'Geri alınacak değişiklik yok.' };
92
+ return rewindTo(_cps[0].id);
93
+ }
package/dist/cli.js CHANGED
@@ -39,6 +39,7 @@ import { stripInlineToolCalls, stripEchoBlocks } from './inlinetools.js';
39
39
  import { newTrace, flushTelemetry } from './telemetry.js';
40
40
  import { tier } from './program.js';
41
41
  import { allToolSchemas, executeToolCalls, executeTool, toolLabel, setToolConfig, setBashCwd } from './tools.js';
42
+ import { setCheckpointStore } from './checkpoint.js';
42
43
  import { sanitizeError, sanitizeOutput } from './errorsan.js';
43
44
  import { cleanModelText } from './textclean.js';
44
45
  import { MarkdownDisplay } from './markdown.js';
@@ -159,6 +160,7 @@ if (_needLogin) {
159
160
  }
160
161
  }
161
162
  setToolConfig(config); // Agent/alt-agent araçları aynı config'i kullanır
163
+ setCheckpointStore(process.cwd()); // rewind deposu (/rewind, /checkpoints)
162
164
  const MAX_TURNS = Number(process.env.WORMCLAUDE_MAX_TURNS) || 90; // tur limiti
163
165
  setLang(loadLang() ?? 'tr'); // kayıtlı dili yükle (yoksa tr)
164
166
  loadSkills(); // .wormclaude/skills/*.md yükle
package/dist/cmdsec.js CHANGED
@@ -191,6 +191,9 @@ const READONLY_ROOTS = new Set([
191
191
  'ripgrep', 'sed', 'sort', 'stat', 'tail', 'tree', 'uniq', 'wc', 'which', 'where', 'whoami',
192
192
  'id', 'hostname', 'uname', 'date', 'cal', 'uptime', 'file', 'realpath', 'nl', 'tac', 'cmp',
193
193
  'md5sum', 'sha256sum', 'seq', 'test', 'true', 'false',
194
+ // Windows read-only komutları (PowerShell/cmd) — listeleme/okuma, yazma yok
195
+ 'dir', 'type', 'findstr', 'ver', 'tasklist', 'ipconfig', 'systeminfo',
196
+ 'get-content', 'get-childitem', 'gci', 'select-string', 'get-location', 'get-date', 'get-process',
194
197
  'node', 'python', 'python3', 'java', 'go', 'rustc', 'php', 'ruby', // yalnız --version
195
198
  'npm', 'pnpm', 'yarn', 'pip', 'pip3', 'docker', 'kubectl', 'cargo', // yalnız read-only alt-komut
196
199
  ]);
package/dist/commands.js CHANGED
@@ -9,6 +9,8 @@ import { connectMcpServers, getMcpServers, getMcpConfigPath } from './mcp.js';
9
9
  import * as usage from './usage.js';
10
10
  import { runCompact, contextPercent, autoCompactThreshold } from './compact.js';
11
11
  import { cmdDesc, setLang, saveLang, getLang } from './i18n.js';
12
+ import { listCheckpoints, rewindLast, rewindTo, rewindAll } from './checkpoint.js';
13
+ import { setPlanMode } from './tools.js';
12
14
  import { loadSkills, getSkills, getSkillsDir, installSkill, updateSkill, removeSkill, getRegistry } from './skills.js';
13
15
  import { getApprovedCommands, approveCommands, unapproveCommands, clearApproved } from './cmdsec.js';
14
16
  import { isLearnEnabled, setLearnEnabled, getLearnFile, getLearnCount } from './learn.js';
@@ -22,6 +24,9 @@ export const COMMANDS = [
22
24
  { name: '/kopyala', desc: 'son yanıtı panoya kopyala (/kopyala hepsi · tüm sohbet)' },
23
25
  { name: '/cd', desc: 'çalışma klasörünü değiştir — dosyalar buraya yazılır: /cd <yol>' },
24
26
  { name: '/resume', desc: 'bu klasördeki önceki oturumu geri yükle (kesinti/çökme sonrası devam)' },
27
+ { name: '/rewind', desc: 'dosya değişikliğini geri al: /rewind (son) · /rewind <id> · /rewind all' },
28
+ { name: '/checkpoints', desc: 'geri alınabilir dosya değişikliklerini (checkpoint) listele' },
29
+ { name: '/plan', desc: 'plan modu: önce araştır+plan yaz, yazma/komut engelli (/plan · /plan off)' },
25
30
  { name: '/compact', desc: 'geçmişi modelle özetleyip bağlamı küçült' },
26
31
  { name: '/context', desc: 'bağlam / token kullanımını göster' },
27
32
  { name: '/cost', desc: 'oturum token tahminini göster' },
@@ -223,6 +228,62 @@ export async function runSlashCommand(input, ctx) {
223
228
  case '/clear':
224
229
  ctx.clearConv();
225
230
  return true;
231
+ case '/checkpoints': {
232
+ const cps = listCheckpoints();
233
+ if (!cps.length) {
234
+ ctx.note('Henüz geri alınabilir dosya değişikliği yok.');
235
+ return true;
236
+ }
237
+ const fmtT = (ms) => { try {
238
+ return new Date(ms).toLocaleTimeString('tr-TR');
239
+ }
240
+ catch {
241
+ return '';
242
+ } };
243
+ const wsRel = (f) => { try {
244
+ return path.relative(process.cwd(), f) || f;
245
+ }
246
+ catch {
247
+ return f;
248
+ } };
249
+ const lines = cps.slice(-30).map((c) => ` #${String(c.id).padStart(3)} ${c.label.padEnd(5)} ${fmtT(c.time)} ${c.before == null ? '(yeni)' : c.tooBig ? '(büyük·geri alınamaz)' : ''} ${wsRel(c.file)}`);
250
+ ctx.note('Geri alınabilir değişiklikler (en yeni 30):\n' + lines.join('\n') +
251
+ '\n\n/rewind = sonu geri al · /rewind <id> = o noktaya dön · /rewind all = hepsi');
252
+ return true;
253
+ }
254
+ case '/rewind': {
255
+ const a = arg.toLowerCase();
256
+ let r;
257
+ if (!a)
258
+ r = rewindLast();
259
+ else if (a === 'all' || a === 'hepsi')
260
+ r = rewindAll();
261
+ else if (/^#?\d+$/.test(a))
262
+ r = rewindTo(parseInt(a.replace('#', ''), 10));
263
+ else {
264
+ ctx.note('Kullanım: /rewind (son) · /rewind <id> · /rewind all');
265
+ return true;
266
+ }
267
+ ctx.note((r.ok ? '↩ Geri alındı — ' : '⚠ ') + r.msg);
268
+ return true;
269
+ }
270
+ case '/plan': {
271
+ const a = arg.toLowerCase();
272
+ const h = ctx.getHistory();
273
+ if (a === 'off' || a === 'kapat' || a === 'çık' || a === 'cik') {
274
+ setPlanMode(false);
275
+ h.push({ role: 'user', content: '[PLAN MODE OFF] Plan onaylandı/kapatıldı — artık dosya yazıp komut çalıştırabilirsin.' });
276
+ ctx.setHistory(h);
277
+ ctx.note('📝 Plan modu KAPALI — uygulamaya geçebilirsin.');
278
+ }
279
+ else {
280
+ setPlanMode(true);
281
+ h.push({ role: 'user', content: '[PLAN MODE ON] You are now in PLAN MODE. Investigate ONLY with read-only tools (Read, Glob, Grep, WebSearch/WebFetch) and then propose a clear, concise step-by-step plan. Do NOT write or edit files and do NOT run shell commands yet — those tools are blocked until the user types /plan off. Reply in the user\'s language.' });
282
+ ctx.setHistory(h);
283
+ ctx.note('📝 Plan modu AÇIK — model araştırıp plan yazacak; yazma/komut engelli. Onaylayınca /plan off ile uygula.');
284
+ }
285
+ return true;
286
+ }
226
287
  case '/help':
227
288
  ctx.note((getLang() === 'en' ? 'Commands:\n' : 'Komutlar:\n') +
228
289
  COMMANDS.map((c) => ` ${c.name.padEnd(10)} ${cmdDesc(c.name)}`).join('\n') +
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.146';
19
+ export const VERSION = '1.0.148';
package/dist/tools.js CHANGED
@@ -10,12 +10,13 @@ import { loadConfig } from './api.js';
10
10
  import { runAgentLoop } from './agent.js';
11
11
  import { resolveSubagent, subagentTypesHint, platformNote } from './subagents.js';
12
12
  import { saveMemoryFact } from './memory.js';
13
+ import { recordCheckpoint } from './checkpoint.js';
13
14
  import { emit as emitSpan } from './telemetry.js';
14
15
  import { tasks } from './tasks.js';
15
16
  import { getMcpToolSchemas, callMcpTool } from './mcp.js';
16
17
  import { getSkills, getSkill, buildSkillPrompt } from './skills.js';
17
18
  import { getExcludedTools } from './extensions.js';
18
- import { checkCommand } from './cmdsec.js';
19
+ import { checkCommand, isShellCommandReadOnly } from './cmdsec.js';
19
20
  import * as Diff from 'diff';
20
21
  import * as computer from './computer.js';
21
22
  // Agent/alt-agent araçlarının backend'e ulaşması için config. cli.tsx başlangıçta
@@ -633,6 +634,7 @@ const TOOL_META = {
633
634
  // Plan modu: açıkken yazma/komut araçları engellenir (ExitPlanMode onayına kadar).
634
635
  let planMode = false;
635
636
  export function isPlanMode() { return planMode; }
637
+ export function setPlanMode(on) { planMode = !!on; }
636
638
  const MAX_TOOL_CONCURRENCY = Number(process.env.WORMCLAUDE_MAX_TOOL_CONCURRENCY) || 10;
637
639
  export function isConcurrencySafe(name) {
638
640
  return TOOL_META[name]?.concurrencySafe ?? false; // bilinmeyen/MCP → sıralı (güvenli taraf)
@@ -688,9 +690,13 @@ async function execOne(call, hooks) {
688
690
  }
689
691
  return { ok: true, output: 'Plan reddedildi — plan modunda kal ve düzelt.', args };
690
692
  }
691
- // 3) Plan modu zorlaması: yazma/komut araçları engelli
693
+ // 3) Plan modu zorlaması: yazma araçları engelli. Bash/PowerShell yalnız READ-ONLY komutsa
694
+ // (ls/cat/git status gibi) araştırma için izinli; yazan komut engelli.
692
695
  if (planMode && !isReadOnly(call.name)) {
693
- return { ok: false, output: 'Plan modunda — yazma/komut engellendi. Önce ExitPlanMode ile planı onaylat.', args };
696
+ const _roCmd = (call.name === 'Bash' || call.name === 'PowerShell') && args?.command && isShellCommandReadOnly(String(args.command));
697
+ if (!_roCmd) {
698
+ return { ok: false, output: 'Plan modunda — yazma/komut engellendi (yalnız okuma/araştırma). Planı yaz; uygulamak için kullanıcı /plan off demeli.', args };
699
+ }
694
700
  }
695
701
  // 3.5) Komut güvenliği (Bash/PowerShell) — cmdsec: deny→blokla, allow→izinsiz, confirm→izin akışı
696
702
  if ((call.name === 'Bash' || call.name === 'PowerShell') && args && args.command) {
@@ -1073,6 +1079,7 @@ export async function executeTool(name, args) {
1073
1079
  catch {
1074
1080
  return '';
1075
1081
  } })();
1082
+ recordCheckpoint(fp, _existed ? _wold : null, 'Write'); // rewind: eski hal (yoksa null=sil)
1076
1083
  fs.mkdirSync(path.dirname(path.resolve(fp)), { recursive: true });
1077
1084
  fs.writeFileSync(fp, _wnew);
1078
1085
  readFiles.add(norm(fp));
@@ -1099,6 +1106,7 @@ export async function executeTool(name, args) {
1099
1106
  output: `Error: old_string is not unique (${count} matches). Provide more surrounding context or set replace_all: true.`,
1100
1107
  };
1101
1108
  const _ebefore = c;
1109
+ recordCheckpoint(fp, _ebefore, 'Edit'); // rewind: düzenleme öncesi tam içerik
1102
1110
  c = args.replace_all ? c.split(oldStr).join(newStr) : c.replace(oldStr, newStr);
1103
1111
  fs.writeFileSync(fp, c);
1104
1112
  return { ok: true, output: `Edited ${fp}${args.replace_all ? ` (${count} occurrences)` : ''}${diffStat(_ebefore, c)}` };
package/dist/tui.js CHANGED
@@ -7,6 +7,8 @@ import readline from 'node:readline';
7
7
  import stringWidth from 'string-width';
8
8
  import { loadConfig, streamChat, fetchAccount } from './api.js';
9
9
  import { allToolSchemas, executeToolCalls, executeTool, toolLabel, setToolConfig, getBashCwd } from './tools.js';
10
+ import { setCheckpointStore } from './checkpoint.js';
11
+ import { extractImages, visionMessages, VISION_MODEL } from './vision.js';
10
12
  import * as path from 'node:path';
11
13
  import { sanitizeOutput } from './errorsan.js';
12
14
  import { itemAnsi, markdownAnsi } from './ansi.js';
@@ -170,6 +172,7 @@ export async function runTui() {
170
172
  const scheduleFooter = () => { if (_fpending)
171
173
  return; _fpending = true; setImmediate(() => { _fpending = false; drawFooter(); }); };
172
174
  setToolConfig(config); // araçlar (Write/Bash/Edit…) aynı config'i kullansın
175
+ setCheckpointStore(process.cwd()); // rewind deposu (workspace/.wormclaude/checkpoints.json) — çökme/resume sonrası geri-al çalışsın
173
176
  // İzin (araç onayı) + soru (AskUserQuestion) durumu
174
177
  let perm = null;
175
178
  let ask = null;
@@ -311,6 +314,53 @@ export async function runTui() {
311
314
  const printItem = (it) => { displayItems.push(it); process.stdout.write(itemAnsi(it, cols()) + '\n'); refresh(); };
312
315
  // ── Agent döngüsü (M2: araçlar + izin). Model tool çağırırsa izin sorulup çalıştırılır,
313
316
  // sonuç geçmişe eklenip döngü devam eder; tool yoksa biter. ──
317
+ // Görsel (vision) turu — sürükle-bırak resim. Ana tool-loop'a girmez; VL modeline gider.
318
+ // Geçmişe base64 DEĞİL kısa "[görsel: ad]" etiketi yazılır (bağlam şişmesin).
319
+ async function runVisionTurn(question, images, displayText) {
320
+ busy = true;
321
+ streamChars = 0;
322
+ taskStart = Date.now();
323
+ turnAbort = new AbortController();
324
+ const _ml = _detectMsgLang(question);
325
+ if (_ml && _ml !== getLang())
326
+ setLang(_ml);
327
+ printItem({ kind: 'user', text: displayText });
328
+ printItem({ kind: 'note', text: `🖼 ${images.length} görsel inceleniyor (${images.map((i) => i.name).join(', ')})…` });
329
+ const timer = setInterval(() => { spin++; if (busy)
330
+ refresh(); }, 120);
331
+ let answer = '';
332
+ try {
333
+ const msgs = visionMessages(question, images);
334
+ for await (const ev of streamChat(msgs, [], { ...config, model: VISION_MODEL }, turnAbort?.signal)) {
335
+ if (ev.type === 'text') {
336
+ answer += ev.text;
337
+ streamChars = answer.length;
338
+ }
339
+ else if (ev.type === 'error') {
340
+ answer += `\n[hata: ${ev.error}]`;
341
+ }
342
+ else if (ev.type === 'done') {
343
+ const u = ev.usage;
344
+ if (u && u.input)
345
+ ctxUsed = u.input;
346
+ }
347
+ }
348
+ }
349
+ catch (e) {
350
+ answer += `\n[hata: ${e?.message || e}]`;
351
+ }
352
+ clearInterval(timer);
353
+ answer = stripEchoBlocks(stripInlineToolCalls(cleanModelText(answer))).trim();
354
+ if (answer)
355
+ printItem({ kind: 'assistant', text: answer });
356
+ // Geçmiş: base64 saklama → kısa etiket + soru, sonra cevap (sonraki turda metin modeli devam etsin)
357
+ const tag = images.map((i) => `[görsel: ${i.name}]`).join(' ');
358
+ history.push({ role: 'user', content: `${tag} ${question}`.trim() });
359
+ history.push({ role: 'assistant', content: answer || '(görsel yanıtı alınamadı)' });
360
+ busy = false;
361
+ turnAbort = null;
362
+ refresh();
363
+ }
314
364
  async function runTurn(userText, displayText) {
315
365
  busy = true;
316
366
  streamChars = 0;
@@ -816,6 +866,12 @@ export async function runTui() {
816
866
  runCommand(v);
817
867
  return;
818
868
  }
869
+ // 🖼 Görsel sürükle-bırak — girdide resim yolu varsa VL modeline gönder (vision turu)
870
+ const _vis = extractImages(v);
871
+ if (_vis.images.length) {
872
+ runVisionTurn(_vis.text, _vis.images, v);
873
+ return;
874
+ }
819
875
  // @dosya mention — referanslanan dosya içeriğini modele enjekte et, kullanıcıya orijinali göster
820
876
  const at = resolveAtMentions(v);
821
877
  if (at.files.length) {
package/dist/vision.js ADDED
@@ -0,0 +1,61 @@
1
+ // Görsel (vision) girdisi — terminale dosya SÜRÜKLE-BIRAK yapınca yol metne yazılır.
2
+ // Bu modül girdideki görsel dosya yollarını bulur, okur, base64'ler ve metinden ayıklar.
3
+ // Vision isteği gateway'e model='wormclaude-vision' ile gider (VL modeline yönlenir).
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ const IMG_EXT = '(?:png|jpe?g|gif|webp|bmp)';
7
+ const MIME = {
8
+ png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', webp: 'image/webp', bmp: 'image/bmp',
9
+ };
10
+ const MAX_BYTES = 12 * 1024 * 1024; // 12MB/görsel
11
+ const MAX_IMAGES = 4;
12
+ /** Girdideki görsel dosya yollarını bulur, okur, base64'ler; metinden çıkarır.
13
+ * Sürükle-bırak biçimleri: "C:\...\a.png" (tırnaklı) · C:\...\a.png · ./a.png · a.png · PowerShell '& path'. */
14
+ export function extractImages(input) {
15
+ const images = [];
16
+ const seen = new Set();
17
+ let text = input || '';
18
+ const tryAdd = (raw) => {
19
+ if (images.length >= MAX_IMAGES)
20
+ return false;
21
+ let p = (raw || '').trim().replace(/^["'`]+|["'`]+$/g, '');
22
+ if (!p)
23
+ return false;
24
+ const abs = path.isAbsolute(p) ? p : path.resolve(process.cwd(), p);
25
+ const key = abs.toLowerCase();
26
+ if (seen.has(key))
27
+ return true; // zaten eklendi → metinden yine de çıkar
28
+ try {
29
+ if (!fs.existsSync(abs) || !fs.statSync(abs).isFile())
30
+ return false;
31
+ const buf = fs.readFileSync(abs);
32
+ if (!buf.length || buf.length > MAX_BYTES)
33
+ return false;
34
+ const ext = (abs.split('.').pop() || '').toLowerCase();
35
+ seen.add(key);
36
+ images.push({ name: path.basename(abs), path: abs, data: buf.toString('base64'), media_type: MIME[ext] || 'image/png' });
37
+ return true;
38
+ }
39
+ catch {
40
+ return false;
41
+ }
42
+ };
43
+ // 1) Tırnaklı yollar: "..." / '...' / `...`
44
+ text = text.replace(new RegExp('(["\'`])([^"\'`]+?\\.' + IMG_EXT + ')\\1', 'gi'), (m, _q, p) => (tryAdd(p) ? ' ' : m));
45
+ // 2) Tırnaksız mutlak/göreli yol (boşluksuz): C:\..\a.png · \\unc\a.png · /a.png · ./a.png · ~/a.png
46
+ text = text.replace(new RegExp('(?:^|\\s)&?\\s*((?:[A-Za-z]:[\\\\/]|\\\\\\\\|/|\\./|~/)[^\\s"\'`]*?\\.' + IMG_EXT + ')(?=\\s|$)', 'gi'), (m, p) => (tryAdd(p) ? ' ' : m));
47
+ // 3) Çıplak dosya adı (çalışma klasöründe): foto.png
48
+ text = text.replace(new RegExp('(?:^|\\s)([\\w.\\-]+\\.' + IMG_EXT + ')(?=\\s|$)', 'gi'), (m, p) => (tryAdd(p) ? ' ' : m));
49
+ return { images, text: text.replace(/\s{2,}/g, ' ').trim() };
50
+ }
51
+ /** Vision sistem promptu (kısa, dil-duyarlı) + içerik dizisi kurar. */
52
+ export const VISION_MODEL = 'wormclaude-vision';
53
+ export function visionMessages(question, images) {
54
+ const content = [{ type: 'text', text: question || 'Bu görselde ne var? Kullanıcının dilinde yanıtla.' }];
55
+ for (const im of images)
56
+ content.push({ type: 'image_url', image_url: { url: `data:${im.media_type};base64,${im.data}` } });
57
+ const system = 'You are WormClaude, a careful vision assistant. Look closely at the image(s) and answer the user\'s '
58
+ + 'question concretely and accurately, IN THE USER\'S LANGUAGE (Turkish if they wrote Turkish). Describe only what '
59
+ + 'you actually see — text, UI, objects, code, errors. If it is the WormClaude app, call it WormClaude (never "bot").';
60
+ return [{ role: 'system', content: system }, { role: 'user', content }];
61
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wormclaude",
3
- "version": "1.0.146",
3
+ "version": "1.0.148",
4
4
  "description": "WormClaude CLI - uncensored security+code assistant (ink TUI, Claude-style)",
5
5
  "type": "module",
6
6
  "bin": {