reverse-skills-max: initial public release (restored, redacted, hardened)

This commit is contained in:
baradok609hecl1pq6qi
2026-10-20 02:17:15 +03:00
commit da24c2bd20
2227 changed files with 443931 additions and 0 deletions

View File

@@ -0,0 +1,396 @@
#!/usr/bin/env node
'use strict';
const fs = require('fs');
const path = require('path');
function parseArgs(argv) {
const args = {
caseDir: null,
dryRun: false,
force: false,
includeProfiles: false,
json: false,
markdown: false,
pruneEmpty: true,
};
for (let i = 2; i < argv.length; i++) {
const a = argv[i];
if (a === '--case-dir' || a === '--dir' || a === '-d') args.caseDir = argv[++i];
else if (a === '--dry-run') args.dryRun = true;
else if (a === '--force') args.force = true;
else if (a === '--include-profiles') args.includeProfiles = true;
else if (a === '--no-prune-empty') args.pruneEmpty = false;
else if (a === '--json') args.json = true;
else if (a === '--markdown') args.markdown = true;
else if (a === '--help' || a === '-h') args.help = true;
else throw new Error(`未知参数:${a}`);
}
if (!args.json && !args.markdown) args.markdown = true;
return args;
}
function usage() {
return `用法:
node scripts/clean_case.js --case-dir case --dry-run --markdown
node scripts/clean_case.js --case-dir case --force --markdown
node scripts/clean_case.js --case-dir case --force --include-profiles --markdown
node scripts/clean_case.js --case-dir case --force --no-prune-empty --json
说明:清理 case 内测试文件、临时文件、缓存文件、中间产物和空目录。默认不删除疑似登录态 Profile / Cookie / IndexedDB。`;
}
function exists(p) {
try { fs.accessSync(p); return true; } catch { return false; }
}
function stat(p) {
try { return fs.statSync(p); } catch { return null; }
}
function isDangerousDir(p) {
const root = path.parse(p).root;
const normalized = path.resolve(p);
return normalized === root || normalized.length <= root.length + 2;
}
function normalizeSlash(p) {
return String(p).replace(/\\/g, '/');
}
function relPath(caseDir, p) {
const rel = path.relative(caseDir, p) || '.';
return normalizeSlash(rel);
}
function isInside(parent, child) {
const rel = path.relative(parent, child);
return !!rel && !rel.startsWith('..') && !path.isAbsolute(rel);
}
function isFirefoxProfileDir(p) {
const st = stat(p);
if (!st || !st.isDirectory()) return false;
let names = [];
try { names = fs.readdirSync(p).map(name => name.toLowerCase()); } catch { return false; }
const nameSet = new Set(names);
const markerHits = [
'prefs.js',
'cookies.sqlite',
'places.sqlite',
'storage',
'cache2',
'cert9.db',
'key4.db',
'logins.db',
'webappsstore.sqlite',
'parent.lock',
'sessionstore-backups',
].filter(name => nameSet.has(name)).length;
return markerHits >= 2;
}
function isProfilePath(p) {
const lower = normalizeSlash(p).toLowerCase();
if (/(^|\/)(cloak-profile|camoufox-profile|camoufox-user-data|browser-profile|user-data-dir|user-data|firefox-profile|chrome-profile|ruyipage-profile|profile)(\/|$)/.test(lower)
|| /(^|\/)[^/]*profile[^/]*(\/|$)/.test(lower)
|| /(^|\/)(cookies|local storage|indexeddb|session storage)(\/|$)/.test(lower)
|| /\b(cookie|localstorage|sessionstorage|authorization|token)\b/i.test(lower)) return true;
let cur = path.resolve(p);
const st = stat(cur);
if (!st || !st.isDirectory()) cur = path.dirname(cur);
while (true) {
if (isFirefoxProfileDir(cur)) return true;
const parent = path.dirname(cur);
if (parent === cur) break;
cur = parent;
}
return false;
}
function isDisposableDirName(name) {
const n = name.toLowerCase();
return [
'tmp', '.tmp', 'temp', '.temp', 'cache', '.cache',
'__pycache__', '.pytest_cache', '.mypy_cache', '.ruff_cache',
'browser-temp', 'camoufox-trace', 'camoufox-values', 'failed', '.downloads', '.tmp-downloads',
].includes(n);
}
function isDisposableFileName(name) {
const n = name.toLowerCase();
if (['.ds_store', 'thumbs.db', 'desktop.ini'].includes(n)) return true;
if (/\.(tmp|temp|bak|old|orig|retry|partial|download|crdownload|cache)$/i.test(n)) return true;
if (/^(env-trace\.jsonl|missing-env\.json|node-output\.json|run-output\.json)$/i.test(n)) return true;
if (/^(fingerprint-hook|.*-fingerprint-hook|hook-fingerprint).*\.(js|mjs|cjs)$/i.test(n)) return true;
if (/^(test-|tmp-|temp-|debug-|scratch-).+\.(js|mjs|cjs|json|jsonl|log|txt|md|html|png|jpg|jpeg|webp|har)$/i.test(n)) return true;
if (/(\.test-output|\.debug-output|\.tmp-output)\./i.test(n)) return true;
return false;
}
function listTree(p, out = []) {
if (!exists(p)) return out;
const st = stat(p);
if (!st) return out;
if (st.isDirectory()) {
let names = [];
try { names = fs.readdirSync(p); } catch { names = []; }
for (const name of names) listTree(path.join(p, name), out);
}
out.push(p);
return out;
}
function listDirsDeepFirst(p) {
const out = [];
function visit(dir) {
if (!exists(dir)) return;
let names = [];
try { names = fs.readdirSync(dir); } catch { names = []; }
for (const name of names) {
const child = path.join(dir, name);
const st = stat(child);
if (st && st.isDirectory()) visit(child);
}
out.push(dir);
}
visit(p);
return out.sort((a, b) => b.length - a.length);
}
function hasProfileInside(p) {
return listTree(p).some(isProfilePath);
}
function isProfileProtectedContainer(p, includeProfiles) {
if (includeProfiles) return false;
const st = stat(p);
return !!st && st.isDirectory() && !isProfilePath(p) && hasProfileInside(p);
}
function hasOnlyProfileChildren(dir) {
const st = stat(dir);
if (!st || !st.isDirectory()) return false;
let names = [];
try { names = fs.readdirSync(dir); } catch { return false; }
if (names.length === 0) return false;
return names.every(name => {
const child = path.join(dir, name);
return isProfilePath(child) || hasProfileInside(child);
});
}
function addAction(actions, action, target, reason) {
actions.push({ action, path: target, reason: reason || '' });
}
function removePath(target, dryRun, recursive = true) {
if (dryRun) return;
const st = stat(target);
if (!st) return;
if (st.isDirectory() && recursive) fs.rmSync(target, { recursive: true, force: true });
else if (st.isDirectory()) fs.rmdirSync(target);
else fs.rmSync(target, { force: true });
}
function cleanDisposableDir(caseDir, dir, args, actions) {
if (!exists(dir)) return;
const st = stat(dir);
if (!st || !st.isDirectory()) return;
if (isProfilePath(dir) && !args.includeProfiles) {
addAction(actions, 'skip-profile', dir, '疑似登录态或浏览器 Profile,默认保留');
return;
}
if (isProfileProtectedContainer(dir, args.includeProfiles)) {
addAction(actions, 'keep-profile-container', dir, '目录内包含疑似 Profile,仅清理非敏感子项');
let names = [];
try { names = fs.readdirSync(dir); } catch { names = []; }
for (const name of names) {
const child = path.join(dir, name);
if (isProfilePath(child) || hasProfileInside(child)) {
addAction(actions, 'skip-profile', child, '疑似登录态或浏览器 Profile,默认保留');
continue;
}
addAction(actions, args.dryRun ? 'would-delete' : 'delete', child, '清理临时目录中的非敏感子项');
removePath(child, args.dryRun, true);
}
return;
}
addAction(actions, args.dryRun ? 'would-delete' : 'delete', dir, '清理临时 / 缓存 / 中间产物目录');
removePath(dir, args.dryRun, true);
}
function isTempLikePath(caseDir, p, includeProfiles) {
if (p === caseDir) return false;
if (!includeProfiles && isProfilePath(p)) return false;
const rel = relPath(caseDir, p).toLowerCase();
const base = path.basename(p).toLowerCase();
const tempLike = rel.split('/').some(part => isDisposableDirName(part)) || isDisposableFileName(base);
if (!tempLike) return false;
const st = stat(p);
if (!includeProfiles && st && st.isDirectory() && isProfileProtectedContainer(p, includeProfiles) && hasOnlyProfileChildren(p)) {
return false;
}
return true;
}
function collectRemainingTempLike(caseDir, includeProfiles) {
return listTree(caseDir).filter(p => exists(p) && isTempLikePath(caseDir, p, includeProfiles));
}
function retryRemainingCleanup(caseDir, args, actions) {
if (args.dryRun) return;
for (let round = 0; round < 2; round++) {
const remaining = collectRemainingTempLike(caseDir, args.includeProfiles);
if (!remaining.length) return;
for (const p of remaining.sort((a, b) => b.length - a.length)) {
if (!exists(p) || p === caseDir || !isInside(caseDir, p)) continue;
const st = stat(p);
if (!st) continue;
if (!args.includeProfiles && isProfilePath(p)) continue;
if (!args.includeProfiles && st.isDirectory() && isProfileProtectedContainer(p, args.includeProfiles)) {
cleanDisposableDir(caseDir, p, args, actions);
continue;
}
if (st.isDirectory()) {
addAction(actions, 'delete', p, '二次清理残留临时目录');
removePath(p, false, true);
} else if (isDisposableFileName(path.basename(p)) || relPath(caseDir, p).toLowerCase().split('/').some(part => isDisposableDirName(part))) {
addAction(actions, 'delete', p, '二次清理残留临时文件');
removePath(p, false, false);
}
}
pruneEmptyDirs(caseDir, args, actions);
}
}
function collectDisposableDirs(caseDir) {
const dirs = [];
const direct = [
'tmp', '.tmp', 'temp', '.temp', 'browser-temp', 'cache', '.cache',
'downloads/failed', 'downloads/.tmp', 'downloads/.cache',
'logs/tmp', 'logs/.tmp', 'trace/tmp', 'trace/.tmp',
'ruyi-trace/tmp', 'ruyi-trace/.tmp', 'camoufox-trace', 'camoufox-values',
'camoufox-trace/tmp', 'camoufox-values/tmp', 'screenshots/tmp', 'screenshots/.tmp',
];
for (const rel of direct) dirs.push(path.join(caseDir, rel));
for (const p of listTree(caseDir)) {
const st = stat(p);
if (!st || !st.isDirectory()) continue;
if (p === caseDir) continue;
if (isDisposableDirName(path.basename(p))) dirs.push(p);
}
return Array.from(new Set(dirs.map(p => path.resolve(p)))).sort((a, b) => b.length - a.length);
}
function cleanDisposableFiles(caseDir, args, actions) {
for (const p of listTree(caseDir)) {
if (!exists(p)) continue;
const st = stat(p);
if (!st || !st.isFile()) continue;
if (isProfilePath(p) && !args.includeProfiles) {
addAction(actions, 'skip-sensitive-file', p, '疑似 Cookie / token / 登录态文件,默认保留');
continue;
}
const rel = relPath(caseDir, p).toLowerCase();
const inTempLikeDir = rel.split('/').some(part => isDisposableDirName(part));
const shouldDelete = isDisposableFileName(path.basename(p)) || (st.size === 0 && inTempLikeDir);
if (!shouldDelete) continue;
addAction(actions, args.dryRun ? 'would-delete' : 'delete', p, st.size === 0 ? '清理空临时文件' : '清理临时 / 测试 / 缓存文件');
removePath(p, args.dryRun, false);
}
}
function pruneEmptyDirs(caseDir, args, actions) {
if (!args.pruneEmpty) return;
for (const dir of listDirsDeepFirst(caseDir)) {
if (dir === caseDir) continue;
if (!exists(dir)) continue;
if (isProfilePath(dir) && !args.includeProfiles) continue;
let names = [];
try { names = fs.readdirSync(dir); } catch { continue; }
if (names.length !== 0) continue;
addAction(actions, args.dryRun ? 'would-remove-empty-dir' : 'remove-empty-dir', dir, '清理空目录');
removePath(dir, args.dryRun, false);
}
}
function cleanup(args) {
if (!args.caseDir) throw new Error('必须提供 --case-dir');
const caseDir = path.resolve(args.caseDir);
if (!exists(caseDir)) throw new Error(`case 目录不存在:${caseDir}`);
const caseStat = stat(caseDir);
if (!caseStat || !caseStat.isDirectory()) throw new Error(`case 路径不是目录:${caseDir}`);
if (isDangerousDir(caseDir)) throw new Error(`拒绝清理危险目录:${caseDir}`);
if (!args.dryRun && !args.force) throw new Error('未提供 --force,拒绝删除;请先使用 --dry-run 预览');
const actions = [];
const disposableDirs = collectDisposableDirs(caseDir);
for (const dir of disposableDirs) {
if (!exists(dir)) continue;
if (dir === caseDir || !isInside(caseDir, dir)) continue;
cleanDisposableDir(caseDir, dir, args, actions);
}
cleanDisposableFiles(caseDir, args, actions);
pruneEmptyDirs(caseDir, args, actions);
retryRemainingCleanup(caseDir, args, actions);
const remainingTempLike = collectRemainingTempLike(caseDir, args.includeProfiles);
return {
caseDir,
dryRun: args.dryRun,
includeProfiles: args.includeProfiles,
pruneEmpty: args.pruneEmpty,
actions,
remainingTempLike,
clean: remainingTempLike.length === 0,
};
}
function renderMarkdown(result) {
const label = {
'skip-profile': '跳过 Profile',
'skip-sensitive-file': '跳过敏感文件',
'keep-profile-container': '保留 Profile 容器目录',
'would-delete': '将删除',
'delete': '已删除',
'would-remove-empty-dir': '将删除空目录',
'remove-empty-dir': '已删除空目录',
};
const lines = ['# 清理结果', '', `case 目录:${result.caseDir}`, `dry-run:${result.dryRun ? '是' : '否'}`, `是否包含 Profile:${result.includeProfiles ? '是' : '否'}`, `是否清理空目录:${result.pruneEmpty ? '是' : '否'}`, ''];
if (result.actions.length) {
lines.push('## 操作列表');
for (const a of result.actions) {
lines.push(`- ${label[a.action] || a.action}:${a.path}${a.reason ? `(${a.reason})` : ''}`);
}
} else {
lines.push('## 操作列表', '- 没有需要清理的内容');
}
lines.push('', '## 清理后检查');
lines.push(`- 是否仍存在普通临时 / 缓存 / 中间产物:${result.clean ? '否' : '是'}`);
if (result.remainingTempLike.length) {
for (const p of result.remainingTempLike) lines.push(` - ${p}`);
}
return lines.join('\n') + '\n';
}
try {
const args = parseArgs(process.argv);
if (args.help) { console.log(usage()); process.exit(0); }
const result = cleanup(args);
if (args.json) console.log(JSON.stringify(result, null, 2));
if (args.markdown) process.stdout.write(renderMarkdown(result));
} catch (err) {
console.error(err.message || String(err));
console.error(usage());
process.exit(1);
}