checker.mjs 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. #!/usr/bin/env node
  2. /**
  3. * fmode-bypass-permission checker — YOLO 模式体检与幂等修复
  4. * 零依赖(Node ≥18)。只动 Agent 工具自身配置,改动前备份。
  5. *
  6. * 用法:
  7. * node checker.mjs check # 体检输出 JSON
  8. * node checker.mjs enforce # 体检+修复不合规项
  9. */
  10. import fs from 'fs';
  11. import os from 'os';
  12. import path from 'path';
  13. const HOME = os.homedir();
  14. const TS = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14);
  15. function readJson(p) { try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return null; } }
  16. function backup(p) { if (fs.existsSync(p)) fs.copyFileSync(p, `${p}.bak-${TS}`); }
  17. /** 探测 Hermes config.yaml 位置 */
  18. function hermesConfigPath() {
  19. const candidates = [
  20. path.join(HOME, '.hermes', 'config.yaml'),
  21. '/opt/data/.hermes/config.yaml',
  22. path.join(HOME, '.fmode-harness-agent', 'config', 'fmodeagent', 'config.yaml'),
  23. ];
  24. for (const p of candidates) if (fs.existsSync(p)) return p;
  25. return null;
  26. }
  27. export function check() {
  28. const result = { hermes_approvals: null, hermes_yolo: null, claude_bypass: null, env_yolo: !!(process.env.HERMES_YOLO || process.env.CLAUDE_YOLO) };
  29. // Hermes yaml: 简易解析(不引依赖)
  30. const hp = hermesConfigPath();
  31. if (hp) {
  32. const t = fs.readFileSync(hp, 'utf8');
  33. const am = t.match(/approvals:[\s\S]{0,80}?mode:\s*(\w+)/);
  34. result.hermes_approvals = am ? am[1] : null;
  35. const ym = t.match(/^yolo:\s*(true|false)/m) || t.match(/^\s*yolo:\s*(true|false)/m);
  36. result.hermes_yolo = ym ? ym[1] === 'true' : null;
  37. result.hermes_config_path = hp;
  38. }
  39. // Claude settings
  40. for (const p of [path.join(HOME, '.claude', 'settings.json'), path.join(HOME, '.claude', 'settings.local.json')]) {
  41. const j = readJson(p);
  42. if (j && j.permissions && Array.isArray(j.permissions.allow)) {
  43. if (j.permissions.allow.some(x => /Bash\(\*\)|dangerously/i.test(x))) { result.claude_bypass = true; break; }
  44. }
  45. }
  46. if (result.claude_bypass === null) {
  47. // 进程参数兜底
  48. try { const cmd = fs.readFileSync(`/proc/${process.ppid}/cmdline`, 'utf8'); result.claude_bypass = cmd.includes('dangerously-skip-permissions'); } catch {}
  49. }
  50. result.compliant = (result.hermes_approvals === 'off' || result.env_yolo) &&
  51. (result.hermes_yolo === true || result.env_yolo) &&
  52. (result.claude_bypass === true || result.env_yolo);
  53. return result;
  54. }
  55. function fixHermes() {
  56. const hp = hermesConfigPath();
  57. if (!hp) { console.error('未找到 hermes config.yaml,跳过 hermes 修复'); return; }
  58. backup(hp);
  59. let t = fs.readFileSync(hp, 'utf8');
  60. if (!/approvals:/.test(t)) t += '\napprovals:\n mode: off\n';
  61. else t = t.replace(/(approvals:[\s\S]{0,80}?mode:\s*)(\w+)/, '$1off');
  62. if (!/^yolo:/m.test(t)) t += 'yolo: true\n';
  63. else t = t.replace(/^yolo:\s*false/m, 'yolo: true');
  64. fs.writeFileSync(hp, t);
  65. console.log('已修复 hermes:', hp, '(备份 .bak-' + TS + ')');
  66. }
  67. function fixClaude() {
  68. const dir = path.join(HOME, '.claude');
  69. fs.mkdirSync(dir, { recursive: true });
  70. const p = path.join(dir, 'settings.json');
  71. backup(p);
  72. const j = readJson(p) || {};
  73. j.permissions = j.permissions || {};
  74. j.permissions.allow = j.permissions.allow || [];
  75. if (!j.permissions.allow.some(x => /Bash\(\*\)/.test(x))) j.permissions.allow.push('Bash(*)');
  76. if (!j.permissions.allow.some(x => /dangerously/i.test(x))) j.permissions.allow.push('dangerously-skip-permissions');
  77. fs.writeFileSync(p, JSON.stringify(j, null, 2));
  78. console.log('已修复 claude:', p, '(备份 .bak-' + TS + ')');
  79. }
  80. function main() {
  81. const cmd = process.argv[2] || 'check';
  82. const r = check();
  83. if (cmd === 'check') { console.log(JSON.stringify(r, null, 2)); return; }
  84. if (cmd === 'enforce') {
  85. if (r.compliant) { console.log(JSON.stringify({ ok: true, message: 'YOLO 模式已合规,零动作', ...r }, null, 2)); return; }
  86. if (r.hermes_approvals !== 'off' || r.hermes_yolo !== true) fixHermes();
  87. if (r.claude_bypass !== true) fixClaude();
  88. console.log(JSON.stringify({ ok: true, fixed: true, after: check() }, null, 2));
  89. return;
  90. }
  91. console.log('用法: check | enforce');
  92. }
  93. main();