wormclaude 1.0.145 → 1.0.147

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
@@ -167,6 +167,23 @@ const DANGER = [
167
167
  { re: /\bmv\s+[^\n]*\s+\/dev\/null\b/, reason: 'Veriyi /dev/null\'a taşıma' },
168
168
  { re: />\s*\/dev\/null\s+2>&1\s*;\s*rm/, reason: 'Gizli silme' },
169
169
  ];
170
+ // ── Windows tehlikeli komutlar (HARD DENY) — kullanıcı Windows'ta ──────────
171
+ // Yalnız SİSTEM / sürücü-kökü hedefleri engellenir; proje altı (C:\Users\...\build)
172
+ // silmeleri meşrudur → onay akışına düşer, hard-deny EDİLMEZ.
173
+ const WIN_SYS = '(?:[a-zA-Z]:\\\\?(?:windows\\b|program\\s?files(?:\\s?\\(x86\\))?\\b|programdata\\b)' + // C:\Windows, C:\Program Files, C:\ProgramData
174
+ '|[a-zA-Z]:\\\\?(?:["\'\\s]|$)' + // C:\ (sürücü kökü)
175
+ '|system32\\b|%(?:systemroot|windir|systemdrive)%|\\$env:(?:windir|systemroot|systemdrive))';
176
+ const WIN_DANGER = [
177
+ // del / erase / rd / rmdir /s → sistem ya da sürücü kökü
178
+ { re: new RegExp('\\b(?:del|erase|rd|rmdir)\\b(?=[\\s\\S]*\\s/s\\b)[\\s\\S]*?' + WIN_SYS, 'i'),
179
+ reason: 'Windows sistem/sürücü kökünde özyinelemeli silme (del/rd /s)' },
180
+ // Remove-Item (alias ri/rm/del) -Recurse [-Force] → sistem ya da sürücü kökü
181
+ { re: new RegExp('\\b(?:remove-item|ri|rm|rmdir|rd|del)\\b(?=[\\s\\S]*-(?:recurse|r)\\b)[\\s\\S]*?' + WIN_SYS, 'i'),
182
+ reason: 'Windows kök/sistem yolunda Remove-Item -Recurse' },
183
+ { re: /\bformat\b\s+[a-zA-Z]:/i, reason: 'Disk biçimlendirme (format)' },
184
+ { re: /\bFormat-Volume\b/i, reason: 'Disk biçimlendirme (Format-Volume)' },
185
+ { re: /\b(?:Stop-Computer|Restart-Computer|shutdown(?:\.exe)?\b|Clear-Disk|Remove-Partition)\b/i, reason: 'Sistemi kapatma/disk temizleme' },
186
+ ];
170
187
  // ── Read-only komut tespiti (shellReadOnlyChecker.js'ten — sağlam) ──────────
171
188
  const READONLY_ROOTS = new Set([
172
189
  'awk', 'basename', 'cat', 'cd', 'column', 'cut', 'df', 'dirname', 'du', 'echo', 'env', 'find',
@@ -278,15 +295,17 @@ export function isShellCommandReadOnly(command) {
278
295
  return segs.length > 0 && segs.every(cmdIsReadOnly);
279
296
  }
280
297
  // ── Asıl güvenlik motoru ───────────────────────────────────────────────────
281
- export function checkCommand(rawCommand) {
298
+ export function checkCommand(rawCommand, opts) {
282
299
  const command = stripShellWrapper(String(rawCommand || ''));
283
300
  const roots = getCommandRoots(command);
284
- // 1) Command substitution -> HARD DENY
285
- if (detectCommandSubstitution(command)) {
301
+ const shell = opts?.shell;
302
+ // 1) Command substitution -> HARD DENY (yalnız bash/sh için; PowerShell'de `$()` ve backtick
303
+ // NORMAL sözdizimidir — alt-ifade / escape — bash komut-ikamesi değil → atla).
304
+ if (shell !== 'powershell' && detectCommandSubstitution(command)) {
286
305
  return { decision: 'deny', reason: 'Komut ikamesi ($(), <(), backtick) güvenlik nedeniyle engellendi', roots };
287
306
  }
288
- // 2) Tehlikeli blocklist -> HARD DENY
289
- for (const d of DANGER) {
307
+ // 2) Tehlikeli blocklist (POSIX + Windows) -> HARD DENY
308
+ for (const d of [...DANGER, ...WIN_DANGER]) {
290
309
  try {
291
310
  if (d.re instanceof RegExp && d.re.test(command))
292
311
  return { decision: 'deny', reason: d.reason, roots };
package/dist/commands.js CHANGED
@@ -9,6 +9,7 @@ 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';
12
13
  import { loadSkills, getSkills, getSkillsDir, installSkill, updateSkill, removeSkill, getRegistry } from './skills.js';
13
14
  import { getApprovedCommands, approveCommands, unapproveCommands, clearApproved } from './cmdsec.js';
14
15
  import { isLearnEnabled, setLearnEnabled, getLearnFile, getLearnCount } from './learn.js';
@@ -22,6 +23,8 @@ export const COMMANDS = [
22
23
  { name: '/kopyala', desc: 'son yanıtı panoya kopyala (/kopyala hepsi · tüm sohbet)' },
23
24
  { name: '/cd', desc: 'çalışma klasörünü değiştir — dosyalar buraya yazılır: /cd <yol>' },
24
25
  { name: '/resume', desc: 'bu klasördeki önceki oturumu geri yükle (kesinti/çökme sonrası devam)' },
26
+ { name: '/rewind', desc: 'dosya değişikliğini geri al: /rewind (son) · /rewind <id> · /rewind all' },
27
+ { name: '/checkpoints', desc: 'geri alınabilir dosya değişikliklerini (checkpoint) listele' },
25
28
  { name: '/compact', desc: 'geçmişi modelle özetleyip bağlamı küçült' },
26
29
  { name: '/context', desc: 'bağlam / token kullanımını göster' },
27
30
  { name: '/cost', desc: 'oturum token tahminini göster' },
@@ -223,6 +226,45 @@ export async function runSlashCommand(input, ctx) {
223
226
  case '/clear':
224
227
  ctx.clearConv();
225
228
  return true;
229
+ case '/checkpoints': {
230
+ const cps = listCheckpoints();
231
+ if (!cps.length) {
232
+ ctx.note('Henüz geri alınabilir dosya değişikliği yok.');
233
+ return true;
234
+ }
235
+ const fmtT = (ms) => { try {
236
+ return new Date(ms).toLocaleTimeString('tr-TR');
237
+ }
238
+ catch {
239
+ return '';
240
+ } };
241
+ const wsRel = (f) => { try {
242
+ return path.relative(process.cwd(), f) || f;
243
+ }
244
+ catch {
245
+ return f;
246
+ } };
247
+ 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)}`);
248
+ ctx.note('Geri alınabilir değişiklikler (en yeni 30):\n' + lines.join('\n') +
249
+ '\n\n/rewind = sonu geri al · /rewind <id> = o noktaya dön · /rewind all = hepsi');
250
+ return true;
251
+ }
252
+ case '/rewind': {
253
+ const a = arg.toLowerCase();
254
+ let r;
255
+ if (!a)
256
+ r = rewindLast();
257
+ else if (a === 'all' || a === 'hepsi')
258
+ r = rewindAll();
259
+ else if (/^#?\d+$/.test(a))
260
+ r = rewindTo(parseInt(a.replace('#', ''), 10));
261
+ else {
262
+ ctx.note('Kullanım: /rewind (son) · /rewind <id> · /rewind all');
263
+ return true;
264
+ }
265
+ ctx.note((r.ok ? '↩ Geri alındı — ' : '⚠ ') + r.msg);
266
+ return true;
267
+ }
226
268
  case '/help':
227
269
  ctx.note((getLang() === 'en' ? 'Commands:\n' : 'Komutlar:\n') +
228
270
  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.145';
19
+ export const VERSION = '1.0.147';
package/dist/tools.js CHANGED
@@ -10,6 +10,7 @@ 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';
@@ -694,7 +695,7 @@ async function execOne(call, hooks) {
694
695
  }
695
696
  // 3.5) Komut güvenliği (Bash/PowerShell) — cmdsec: deny→blokla, allow→izinsiz, confirm→izin akışı
696
697
  if ((call.name === 'Bash' || call.name === 'PowerShell') && args && args.command) {
697
- const chk = checkCommand(String(args.command));
698
+ const chk = checkCommand(String(args.command), { shell: call.name === 'PowerShell' ? 'powershell' : 'bash' });
698
699
  if (chk.decision === 'deny') {
699
700
  return { ok: false, output: `⛔ Güvenlik: komut engellendi — ${chk.reason || 'tehlikeli komut'}`, args };
700
701
  }
@@ -1073,6 +1074,7 @@ export async function executeTool(name, args) {
1073
1074
  catch {
1074
1075
  return '';
1075
1076
  } })();
1077
+ recordCheckpoint(fp, _existed ? _wold : null, 'Write'); // rewind: eski hal (yoksa null=sil)
1076
1078
  fs.mkdirSync(path.dirname(path.resolve(fp)), { recursive: true });
1077
1079
  fs.writeFileSync(fp, _wnew);
1078
1080
  readFiles.add(norm(fp));
@@ -1099,6 +1101,7 @@ export async function executeTool(name, args) {
1099
1101
  output: `Error: old_string is not unique (${count} matches). Provide more surrounding context or set replace_all: true.`,
1100
1102
  };
1101
1103
  const _ebefore = c;
1104
+ recordCheckpoint(fp, _ebefore, 'Edit'); // rewind: düzenleme öncesi tam içerik
1102
1105
  c = args.replace_all ? c.split(oldStr).join(newStr) : c.replace(oldStr, newStr);
1103
1106
  fs.writeFileSync(fp, c);
1104
1107
  return { ok: true, output: `Edited ${fp}${args.replace_all ? ` (${count} occurrences)` : ''}${diffStat(_ebefore, c)}` };
package/dist/tui.js CHANGED
@@ -7,6 +7,7 @@ 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';
10
11
  import * as path from 'node:path';
11
12
  import { sanitizeOutput } from './errorsan.js';
12
13
  import { itemAnsi, markdownAnsi } from './ansi.js';
@@ -170,6 +171,7 @@ export async function runTui() {
170
171
  const scheduleFooter = () => { if (_fpending)
171
172
  return; _fpending = true; setImmediate(() => { _fpending = false; drawFooter(); }); };
172
173
  setToolConfig(config); // araçlar (Write/Bash/Edit…) aynı config'i kullansın
174
+ setCheckpointStore(process.cwd()); // rewind deposu (workspace/.wormclaude/checkpoints.json) — çökme/resume sonrası geri-al çalışsın
173
175
  // İzin (araç onayı) + soru (AskUserQuestion) durumu
174
176
  let perm = null;
175
177
  let ask = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wormclaude",
3
- "version": "1.0.145",
3
+ "version": "1.0.147",
4
4
  "description": "WormClaude CLI - uncensored security+code assistant (ink TUI, Claude-style)",
5
5
  "type": "module",
6
6
  "bin": {