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.
- package/dist/checkpoint.js +93 -0
- package/dist/cli.js +2 -0
- package/dist/cmdsec.js +24 -5
- package/dist/commands.js +42 -0
- package/dist/theme.js +1 -1
- package/dist/tools.js +4 -1
- package/dist/tui.js +2 -0
- package/package.json +1 -1
|
@@ -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
|
-
|
|
285
|
-
|
|
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
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;
|