Ctrl K
ComunidadGuías…

docker/gateguard-effectiveness/fake-claude.js

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env node
'use strict';

// Test double for `claude --print --output-format stream-json`: it calls the configured
// PreToolUse hook once the way Claude Code does, reports the result, then applies the
// scenario's reference overlay. FAKE_CLAUDE_OVERLAY names the overlay directory.
const fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');

const args = process.argv.slice(2);
const settings = JSON.parse(fs.readFileSync(args[args.indexOf('--settings') + 1], 'utf8'));
const emit = event => process.stdout.write(`${JSON.stringify(event)}\n`);

function copyTree(from, to) {
  for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
    const name = entry.name.startsWith('dot-') ? `.${entry.name.slice(4)}` : entry.name;
    if (entry.isDirectory()) {
      fs.mkdirSync(path.join(to, name), { recursive: true });
      copyTree(path.join(from, entry.name), path.join(to, name));
    } else fs.copyFileSync(path.join(from, entry.name), path.join(to, name));
  }
}

const overlay = process.env.FAKE_CLAUDE_OVERLAY;
const firstFile = dir => {
  for (const entry of fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
    if (entry.isFile()) return entry.name;
    const inner = firstFile(path.join(dir, entry.name));
    if (inner) return path.join(entry.name, inner);
  }
  return null;
};
const first = firstFile(overlay);
const target = path.join(process.cwd(), first.split(path.sep).map(part => (part.startsWith('dot-') ? `.${part.slice(4)}` : part)).join(path.sep));
const input = fs.existsSync(target)
  ? { file_path: target, old_string: fs.readFileSync(target, 'utf8'), new_string: fs.readFileSync(path.join(overlay, first), 'utf8') }
  : { file_path: target, content: fs.readFileSync(path.join(overlay, first), 'utf8') };
const toolName = 'old_string' in input ? 'Edit' : 'Write';
emit({ type: 'assistant', message: { content: [{ type: 'text', text: 'Checking report.js, invoice.js and csv.js first.' }, { type: 'tool_use', id: 't1', name: toolName, input }] } });

// FAKE_CLAUDE_SKIP_HOOK=1 edits without running the hook (a broken hook setup);
// FAKE_CLAUDE_PROVIDER_ERROR=1 ends the session in a non-authentication provider error.
let denial = '';
const group = (settings.hooks && settings.hooks.PreToolUse || []).find(entry => new RegExp(`^(${entry.matcher})$`).test(toolName));
if (group && process.env.FAKE_CLAUDE_SKIP_HOOK !== '1') {
  const env = { ...process.env, CLAUDE_PROJECT_DIR: process.cwd() };
  const payload = { session_id: 'fake', cwd: process.cwd(), hook_event_name: 'PreToolUse', tool_name: toolName, tool_input: input, tool_use_id: 't1' };
  const result = spawnSync(group.hooks[0].command, { shell: true, input: JSON.stringify(payload), env, encoding: 'utf8' });
  const output = result.stdout ? JSON.parse(result.stdout) : {};
  if (output.hookSpecificOutput && output.hookSpecificOutput.permissionDecision === 'deny') denial = output.hookSpecificOutput.permissionDecisionReason;
}
emit({ type: 'user', message: { content: [{ type: 'tool_result', tool_use_id: 't1', is_error: Boolean(denial), content: denial || 'ok' }] } });
copyTree(overlay, process.cwd());
if (process.env.FAKE_CLAUDE_PROVIDER_ERROR === '1') {
  emit({ type: 'result', is_error: true, result: 'API Error: 529 overloaded', num_turns: 1, duration_ms: 100, total_cost_usd: 0, usage: {}, modelUsage: {} });
  process.exit(0);
}
emit({ type: 'result', is_error: false, result: 'done', num_turns: 3, duration_ms: 1200, total_cost_usd: 0.01, usage: { input_tokens: 100, cache_creation_input_tokens: 50, cache_read_input_tokens: 400, output_tokens: 80 }, modelUsage: { 'fake-model': {} } });