Módulo 6: Hooks Avanzados y SDK Headless
5. SDK Headless — TypeScript: Claude Code en el Ecosistema Node.js
5. SDK Headless — TypeScript: Claude Code en el Ecosistema Node.js
Descripción
Si tu stack principal es JavaScript o TypeScript, no necesitas cambiar a Python para automatizar Claude Code. Todo lo que aprendiste en la cápsula anterior — modo headless con -p, output JSON, --allowedTools — funciona igual desde Node.js. Pero el ecosistema TypeScript agrega ventajas: tipado estático para parsear respuestas de Claude, integración directa con build tools como esbuild y Vite, y el paquete @anthropic-ai/claude-code que ofrece una API nativa sin pasar por subprocess.
Esta cápsula cubre dos enfoques: subprocess (usando child_process de Node.js, similar a Python) y el paquete SDK (@anthropic-ai/claude-code). El primer enfoque funciona en cualquier entorno donde Claude CLI esté instalado. El segundo ofrece una API más limpia y tipada, pero requiere instalar el paquete.
Al terminar sabrás ejecutar Claude Code desde scripts TypeScript, integrar la automatización con tu tooling de Node.js (build scripts, deploy scripts, monitoring), y elegir entre Python y TypeScript según el caso de uso.
Enfoque 1: subprocess con child_process
El patrón básico
Node.js tiene child_process como equivalente de subprocess de Python:
import { execSync } from "child_process";
const result = execSync(
'claude -p "¿Cuántos archivos TypeScript hay en src/?" --output-format json --allowedTools "Read,Glob,Grep"',
{ encoding: "utf-8", timeout: 60000 }
);
const output = JSON.parse(result);
console.log(output.result);
console.log(`Cost: $${output.cost_usd?.toFixed(4)}`);
execSync vs exec vs spawn
| Método | Tipo | Cuándo usarlo |
|---|---|---|
execSync | Síncrono, bloquea | Scripts simples, tareas cortas |
exec | Async con callback | Tareas de duración media |
spawn | Async con streams | Ejecuciones largas, streaming |
Función reutilizable con tipado
import { execSync } from "child_process";
interface ClaudeResult {
type: string;
subtype: string;
is_error: boolean;
result: string;
session_id: string;
cost_usd: number;
duration_ms: number;
num_turns: number;
}
interface ClaudeError {
is_error: true;
error: string;
error_type: "timeout" | "not_found" | "parse_error" | "exit_code";
}
type ClaudeOutput = ClaudeResult | ClaudeError;
function runClaude(
prompt: string,
tools: string[] = [],
timeoutMs: number = 300000
): ClaudeOutput {
const toolsArg = tools.length > 0
? `--allowedTools "${tools.join(",")}"`
: "";
const cmd = `claude -p "${prompt.replace(/"/g, '\\"')}" --output-format json ${toolsArg}`;
try {
const result = execSync(cmd, {
encoding: "utf-8",
timeout: timeoutMs,
maxBuffer: 10 * 1024 * 1024,
});
return JSON.parse(result) as ClaudeResult;
} catch (error: any) {
if (error.killed) {
return { is_error: true, error: "Timeout", error_type: "timeout" };
}
if (error.code === "ENOENT") {
return { is_error: true, error: "claude not found", error_type: "not_found" };
}
return {
is_error: true,
error: error.message || "Unknown error",
error_type: "exit_code",
};
}
}
const output = runClaude(
"Lista las funciones exportadas en src/api/",
["Read", "Grep", "Glob"]
);
if (output.is_error) {
console.error(`Error: ${(output as ClaudeError).error}`);
} else {
const result = output as ClaudeResult;
console.log(result.result);
console.log(`Cost: $${result.cost_usd.toFixed(4)}`);
}
Versión async con exec
Para scripts que necesitan ejecutar múltiples invocaciones sin bloquear:
import { exec } from "child_process";
import { promisify } from "util";
const execAsync = promisify(exec);
async function runClaudeAsync(
prompt: string,
tools: string[] = [],
timeoutMs: number = 300000
): Promise<ClaudeResult | ClaudeError> {
const toolsArg = tools.length > 0
? `--allowedTools "${tools.join(",")}"`
: "";
const cmd = `claude -p "${prompt.replace(/"/g, '\\"')}" --output-format json ${toolsArg}`;
try {
const { stdout } = await execAsync(cmd, {
encoding: "utf-8",
timeout: timeoutMs,
maxBuffer: 10 * 1024 * 1024,
});
return JSON.parse(stdout) as ClaudeResult;
} catch (error: any) {
return {
is_error: true,
error: error.message || "Unknown error",
error_type: "exit_code",
};
}
}
async function main() {
const result = await runClaudeAsync(
"Analiza src/components/ y lista todos los componentes React",
["Read", "Glob", "Grep"]
);
if (!result.is_error) {
console.log((result as ClaudeResult).result);
}
}
main();
Enfoque 2: Paquete @anthropic-ai/claude-code
Instalación
npm install @anthropic-ai/claude-code
API básica
El paquete ofrece una API más limpia que subprocess:
import { claude } from "@anthropic-ai/claude-code";
async function main() {
const result = await claude({
prompt: "Analiza src/ y reporta la estructura del proyecto",
allowedTools: ["Read", "Glob", "Grep"],
options: {
maxTurns: 10,
},
});
console.log(result.text);
console.log(`Cost: $${result.costUsd.toFixed(4)}`);
}
main();
Tipado nativo
La ventaja principal del paquete es el tipado:
import { claude, ClaudeResult } from "@anthropic-ai/claude-code";
async function analyzeModule(path: string): Promise<ClaudeResult> {
return claude({
prompt: `Analiza ${path} y reporta: estructura, dependencias, issues`,
allowedTools: ["Read", "Glob", "Grep"],
options: {
maxTurns: 15,
},
});
}
async function main() {
const result = await analyzeModule("src/api/");
if (result.isError) {
console.error("Analysis failed:", result.text);
return;
}
console.log("Analysis:", result.text);
console.log("Turns used:", result.numTurns);
console.log("Duration:", result.durationMs, "ms");
}
main();
Streaming con el paquete
import { claude } from "@anthropic-ai/claude-code";
async function main() {
const stream = claude.stream({
prompt: "Genera un reporte de calidad de código para src/",
allowedTools: ["Read", "Glob", "Grep"],
});
for await (const event of stream) {
if (event.type === "text") {
process.stdout.write(event.content);
}
}
const result = await stream.finalResult();
console.log(`\n\nCost: $${result.costUsd.toFixed(4)}`);
}
main();
Scripts de Automatización en TypeScript
Script 1: Generador de Documentación de Componentes
#!/usr/bin/env npx tsx
/**
* Genera documentación automática para componentes React.
* Uso: npx tsx scripts/gen-component-docs.ts
*/
import { execSync } from "child_process";
import * as fs from "fs";
import * as path from "path";
interface ClaudeResult {
result: string;
is_error: boolean;
cost_usd: number;
}
function runClaude(prompt: string, tools: string[]): ClaudeResult | null {
try {
const toolsStr = tools.join(",");
const result = execSync(
`claude -p "${prompt.replace(/"/g, '\\"')}" --output-format json --allowedTools "${toolsStr}"`,
{ encoding: "utf-8", timeout: 120000 }
);
return JSON.parse(result);
} catch {
return null;
}
}
function getComponentFiles(): string[] {
const result = execSync(
'find src/components -name "*.tsx" -not -name "*.test.*" -not -name "*.spec.*"',
{ encoding: "utf-8" }
);
return result.trim().split("\n").filter(Boolean);
}
function main() {
const components = getComponentFiles();
console.log(`Found ${components.length} components\n`);
const docs: string[] = ["# Component Documentation\n"];
let totalCost = 0;
for (const file of components) {
console.log(`Documenting: ${file}`);
const output = runClaude(
`Lee ${file} y genera documentación para el componente React que contiene.
Incluye: nombre, props (con tipos), descripción, ejemplo de uso.
Formato Markdown. Sé conciso.`,
["Read"]
);
if (output && !output.is_error) {
docs.push(output.result);
docs.push("\n---\n");
totalCost += output.cost_usd || 0;
}
}
const outputPath = "docs/components.md";
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
fs.writeFileSync(outputPath, docs.join("\n"));
console.log(`\nDocs written to ${outputPath}`);
console.log(`Total cost: $${totalCost.toFixed(4)}`);
}
main();
Script 2: Build Validator
#!/usr/bin/env npx tsx
/**
* Valida el build y pide a Claude que corrija errores.
* Uso: npx tsx scripts/build-validator.ts
*/
import { execSync } from "child_process";
interface ClaudeResult {
result: string;
is_error: boolean;
cost_usd: number;
}
function runBuild(): { success: boolean; output: string } {
try {
const output = execSync("npm run build 2>&1", { encoding: "utf-8" });
return { success: true, output };
} catch (error: any) {
return { success: false, output: error.stdout || error.message };
}
}
function fixWithClaude(buildErrors: string): ClaudeResult | null {
const prompt = `El build de TypeScript está fallando con estos errores:
${buildErrors}
Lee los archivos mencionados en los errores y corrígelos.
Solo corrige errores de TypeScript (types, imports, syntax).
No cambies la lógica de negocio.`;
try {
const result = execSync(
`claude -p "${prompt.replace(/"/g, '\\"').replace(/\n/g, "\\n")}" --output-format json --allowedTools "Read,Write,Edit,Grep,Glob"`,
{ encoding: "utf-8", timeout: 300000 }
);
return JSON.parse(result);
} catch {
return null;
}
}
function main() {
const maxAttempts = 3;
let totalCost = 0;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
console.log(`\n--- Build attempt ${attempt}/${maxAttempts} ---`);
const { success, output } = runBuild();
if (success) {
console.log("Build successful!");
console.log(`Total fix cost: $${totalCost.toFixed(4)}`);
return;
}
console.log("Build failed. Asking Claude to fix...");
const fix = fixWithClaude(output.slice(0, 3000));
if (fix && !fix.is_error) {
totalCost += fix.cost_usd || 0;
console.log(`Fix applied ($${(fix.cost_usd || 0).toFixed(4)})`);
} else {
console.log("Claude fix failed");
}
}
console.log(`Build still failing after ${maxAttempts} attempts`);
console.log(`Total cost: $${totalCost.toFixed(4)}`);
process.exit(1);
}
main();
Script 3: Monitoring y Reporte de Proyecto
#!/usr/bin/env npx tsx
/**
* Genera un reporte de salud del proyecto.
* Uso: npx tsx scripts/project-health.ts
*/
import { execSync } from "child_process";
import * as fs from "fs";
interface ClaudeResult {
result: string;
is_error: boolean;
cost_usd: number;
duration_ms: number;
}
function runClaude(prompt: string, tools: string[]): ClaudeResult | null {
try {
const result = execSync(
`claude -p "${prompt.replace(/"/g, '\\"')}" --output-format json --allowedTools "${tools.join(",")}"`,
{ encoding: "utf-8", timeout: 300000, maxBuffer: 10 * 1024 * 1024 }
);
return JSON.parse(result);
} catch {
return null;
}
}
interface HealthCheck {
name: string;
prompt: string;
tools: string[];
}
const checks: HealthCheck[] = [
{
name: "Code Quality",
prompt: "Analiza src/ para code quality: complejidad, duplicación, naming. Score 1-10 con justificación breve.",
tools: ["Read", "Grep", "Glob"],
},
{
name: "Security",
prompt: "Busca en src/ problemas de seguridad: secrets, injection, eval, unsafe patterns. Lista findings con severidad.",
tools: ["Read", "Grep", "Glob"],
},
{
name: "Dependencies",
prompt: "Analiza package.json y busca: deps desactualizadas, deps sin usar, vulnerabilidades conocidas. Reporta findings.",
tools: ["Read", "Grep", "Glob"],
},
];
async function main() {
console.log("Project Health Report\n");
const results: { name: string; result: string; cost: number }[] = [];
let totalCost = 0;
for (const check of checks) {
console.log(`Running: ${check.name}...`);
const output = runClaude(check.prompt, check.tools);
if (output && !output.is_error) {
results.push({
name: check.name,
result: output.result,
cost: output.cost_usd || 0,
});
totalCost += output.cost_usd || 0;
} else {
results.push({
name: check.name,
result: "Check failed",
cost: 0,
});
}
}
const report = [
`# Project Health Report — ${new Date().toISOString().split("T")[0]}`,
"",
...results.map((r) => [
`## ${r.name}`,
"",
r.result,
"",
`*Cost: $${r.cost.toFixed(4)}*`,
"",
"---",
"",
]).flat(),
`**Total cost: $${totalCost.toFixed(4)}**`,
].join("\n");
fs.mkdirSync("reports", { recursive: true });
const reportPath = `reports/health-${new Date().toISOString().split("T")[0]}.md`;
fs.writeFileSync(reportPath, report);
console.log(`\nReport saved: ${reportPath}`);
console.log(`Total cost: $${totalCost.toFixed(4)}`);
}
main();
Ejecución Paralela en TypeScript
Promise.all para análisis concurrente
import { execSync } from "child_process";
interface ClaudeResult {
result: string;
is_error: boolean;
cost_usd: number;
}
async function analyzeModuleAsync(modulePath: string): Promise<{
path: string;
analysis: ClaudeResult | null;
}> {
return new Promise((resolve) => {
try {
const output = execSync(
`claude -p "Analiza ${modulePath}: estructura, exports, issues. Conciso." --output-format json --allowedTools "Read,Grep,Glob"`,
{ encoding: "utf-8", timeout: 120000 }
);
resolve({ path: modulePath, analysis: JSON.parse(output) });
} catch {
resolve({ path: modulePath, analysis: null });
}
});
}
async function main() {
const modules = ["src/api/", "src/components/", "src/services/"];
console.log(`Analyzing ${modules.length} modules in parallel...\n`);
const results = await Promise.all(
modules.map((m) => analyzeModuleAsync(m))
);
let totalCost = 0;
for (const { path, analysis } of results) {
console.log(`\n## ${path}`);
if (analysis && !analysis.is_error) {
console.log(analysis.result.slice(0, 300));
totalCost += analysis.cost_usd || 0;
} else {
console.log("Analysis failed");
}
}
console.log(`\nTotal cost: $${totalCost.toFixed(4)}`);
}
main();
Comparación: Python SDK vs TypeScript SDK
Cuándo usar Python
| Escenario | Por qué Python |
|---|---|
| Scripts de CI/CD | Python es el estándar en DevOps y CI pipelines |
| Data processing | Pandas, numpy para analizar resultados |
| ML/AI workflows | Integración con ecosistema de ML |
| Backend automation | Scripts de migración, seeding, mantenimiento |
| Quick scripts | Menor boilerplate para scripts one-off |
Cuándo usar TypeScript
| Escenario | Por qué TypeScript |
|---|---|
| Build tools | Integración con esbuild, Vite, webpack |
| Frontend tooling | Scripts que interactúan con el codebase frontend |
| Type safety | Tipado estático para parsear respuestas de Claude |
| Node.js ecosystem | npm scripts, herramientas de desarrollo JS |
| Full-stack JS projects | Consistencia de lenguaje en todo el stack |
Comparación directa
| Aspecto | Python | TypeScript |
|---|---|---|
| Invocación | subprocess.run() | execSync() / exec() |
| Async | asyncio / ThreadPoolExecutor | Promise.all() / async/await |
| JSON parsing | json.loads() | JSON.parse() |
| Tipado | Opcional (type hints) | Nativo y estricto |
| Paquete SDK | subprocess approach | @anthropic-ai/claude-code |
| Boilerplate | Menor | Mayor (tipos, interfaces) |
| Error handling | try/except | try/catch |
| Streaming | Popen + line iteration | spawn + event listeners |
| Ecosistema CI | Más común | Menos común |
| Script runner | python script.py | npx tsx script.ts |
La regla de decisión
¿Tu proyecto es JS/TS? ──→ TypeScript
¿Es un script de CI/CD? ──→ Python
¿Necesitas tipado fuerte para los resultados? ──→ TypeScript
¿Es un script one-off rápido? ──→ Python
¿Integra con build tools JS? ──→ TypeScript
¿Integra con data pipelines? ──→ Python
Si no estás seguro: usa el lenguaje que más usas en tu día a día. Ambos funcionan igual de bien para invocar Claude Code en modo headless.
Integración con npm scripts
package.json
{
"scripts": {
"review": "npx tsx scripts/code-review.ts",
"docs:generate": "npx tsx scripts/gen-component-docs.ts",
"health": "npx tsx scripts/project-health.ts",
"build:fix": "npx tsx scripts/build-validator.ts",
"changelog": "npx tsx scripts/changelog.ts"
},
"devDependencies": {
"tsx": "^4.0.0",
"typescript": "^5.0.0"
}
}
npm run review
npm run docs:generate
npm run health
npm run build:fix
Esto integra los scripts de automatización directamente en tu flujo de trabajo habitual de npm.
Troubleshooting
"Cannot find module '@anthropic-ai/claude-code'"
Causa: El paquete no está instalado.
Solución:
npm install @anthropic-ai/claude-code
Si el paquete no está disponible en tu versión de npm o no existe aún como paquete público, usa el enfoque de subprocess que funciona con cualquier instalación de Claude CLI.
"execSync: command not found: claude"
Causa: Claude CLI no está en el PATH cuando Node.js ejecuta el subprocess.
Solución:
import { execSync } from "child_process";
const claudePath = execSync("which claude", { encoding: "utf-8" }).trim();
const result = execSync(
`${claudePath} -p "prompt" --output-format json`,
{ encoding: "utf-8" }
);
"MaxBuffer exceeded"
Causa: El output de Claude excede el buffer por defecto de execSync (1MB).
Solución:
const result = execSync(cmd, {
encoding: "utf-8",
maxBuffer: 10 * 1024 * 1024, // 10MB
});
"SyntaxError: Unexpected token in JSON"
Causa: El output incluye texto no-JSON (warnings, stderr mezclado).
Solución:
const raw = execSync(cmd, { encoding: "utf-8" });
const jsonStart = raw.indexOf("{");
const jsonEnd = raw.lastIndexOf("}") + 1;
if (jsonStart >= 0 && jsonEnd > jsonStart) {
const output = JSON.parse(raw.slice(jsonStart, jsonEnd));
}
"Timeout con npx tsx"
Causa: npx tsx agrega overhead de startup. El timeout de execSync puede no ser suficiente.
Solución: Incrementa el timeout o compila el script a JavaScript primero:
npx tsc scripts/review.ts --outDir dist/
node dist/review.js
Ejercicios
Ejercicio 1: "Hello World" headless en TypeScript (Fácil)
Escribe un script TypeScript que ejecute Claude en modo headless para contar archivos .ts en src/, parsee el JSON, e imprima resultado y costo.
Ver solución
#!/usr/bin/env npx tsx
import { execSync } from "child_process";
const result = execSync(
'claude -p "¿Cuántos archivos .ts hay en src/?" --output-format json --allowedTools "Glob"',
{ encoding: "utf-8", timeout: 60000 }
);
const output = JSON.parse(result);
console.log(`Resultado: ${output.result}`);
console.log(`Costo: $${(output.cost_usd || 0).toFixed(4)}`);
Ejercicio 2: Función tipada reutilizable (Fácil)
Crea una función askClaude con tipos TypeScript para input y output que encapsule la invocación headless con error handling completo.
Ver solución
import { execSync } from "child_process";
interface ClaudeInput {
prompt: string;
tools?: string[];
timeoutMs?: number;
}
interface ClaudeSuccess {
is_error: false;
result: string;
cost_usd: number;
duration_ms: number;
}
interface ClaudeFailure {
is_error: true;
error: string;
}
type ClaudeOutput = ClaudeSuccess | ClaudeFailure;
function askClaude(input: ClaudeInput): ClaudeOutput {
const { prompt, tools = [], timeoutMs = 300000 } = input;
const toolsArg = tools.length ? `--allowedTools "${tools.join(",")}"` : "";
const cmd = `claude -p "${prompt.replace(/"/g, '\\"')}" --output-format json ${toolsArg}`;
try {
const raw = execSync(cmd, {
encoding: "utf-8",
timeout: timeoutMs,
maxBuffer: 10 * 1024 * 1024,
});
const parsed = JSON.parse(raw);
return { is_error: false, result: parsed.result, cost_usd: parsed.cost_usd || 0, duration_ms: parsed.duration_ms || 0 };
} catch (error: any) {
return { is_error: true, error: error.message || "Unknown error" };
}
}
const output = askClaude({ prompt: "Lista exports de src/api/", tools: ["Read", "Grep"] });
if (!output.is_error) {
console.log(output.result);
} else {
console.error(output.error);
}
Ejercicio 3: Generador de docs para componentes (Medio)
Escribe un script que lea todos los archivos .tsx en src/components/, pida a Claude que documente cada uno, y genere un archivo docs/components.md.
Ver solución
#!/usr/bin/env npx tsx
import { execSync } from "child_process";
import * as fs from "fs";
function runClaude(prompt: string, tools: string[]): string | null {
try {
const result = execSync(
`claude -p "${prompt.replace(/"/g, '\\"')}" --output-format json --allowedTools "${tools.join(",")}"`,
{ encoding: "utf-8", timeout: 120000, maxBuffer: 5 * 1024 * 1024 }
);
const parsed = JSON.parse(result);
return parsed.is_error ? null : parsed.result;
} catch {
return null;
}
}
const files = execSync('find src/components -name "*.tsx" -not -name "*.test.*"', { encoding: "utf-8" })
.trim()
.split("\n")
.filter(Boolean);
const docs: string[] = ["# Components\n"];
for (const file of files) {
console.log(`Documenting: ${file}`);
const doc = runClaude(`Lee ${file}. Documenta: nombre, props, descripción, ejemplo. Markdown conciso.`, ["Read"]);
if (doc) {
docs.push(doc, "\n---\n");
}
}
fs.mkdirSync("docs", { recursive: true });
fs.writeFileSync("docs/components.md", docs.join("\n"));
console.log("Done: docs/components.md");
Ejercicio 4: Build fixer con retry (Medio)
Escribe un script TypeScript que: (1) corra npm run build, (2) si falla, pase los errores a Claude para que corrija, (3) repita hasta 3 veces, (4) reporte si tuvo éxito y el costo total.
Ver solución
#!/usr/bin/env npx tsx
import { execSync } from "child_process";
function build(): { ok: boolean; output: string } {
try {
return { ok: true, output: execSync("npm run build 2>&1", { encoding: "utf-8" }) };
} catch (e: any) {
return { ok: false, output: e.stdout || e.message };
}
}
function fixWithClaude(errors: string): number {
try {
const prompt = errors.slice(0, 3000).replace(/"/g, '\\"').replace(/\n/g, "\\n");
const result = execSync(
`claude -p "Fix these TypeScript build errors:\\n${prompt}" --output-format json --allowedTools "Read,Write,Edit,Grep,Glob"`,
{ encoding: "utf-8", timeout: 300000 }
);
return JSON.parse(result).cost_usd || 0;
} catch {
return 0;
}
}
let totalCost = 0;
for (let i = 1; i <= 3; i++) {
console.log(`\nAttempt ${i}/3`);
const { ok, output } = build();
if (ok) {
console.log(`Build passed! Cost: $${totalCost.toFixed(4)}`);
process.exit(0);
}
totalCost += fixWithClaude(output);
}
console.log(`Build still failing. Cost: $${totalCost.toFixed(4)}`);
process.exit(1);
Ejercicio 5: Análisis paralelo con Promise.all (Difícil)
Escribe un script que analice 4 directorios en paralelo, cada uno con su propia invocación headless, usando Promise.all. Consolida los resultados en un reporte Markdown.
Ver solución
#!/usr/bin/env npx tsx
import { exec } from "child_process";
import { promisify } from "util";
import * as fs from "fs";
const execAsync = promisify(exec);
async function analyze(dir: string): Promise<{ dir: string; result: string; cost: number }> {
try {
const { stdout } = await execAsync(
`claude -p "Analiza ${dir}: archivos, exports, issues. Conciso." --output-format json --allowedTools "Read,Grep,Glob"`,
{ encoding: "utf-8", timeout: 120000, maxBuffer: 5 * 1024 * 1024 }
);
const parsed = JSON.parse(stdout);
return { dir, result: parsed.result || "No result", cost: parsed.cost_usd || 0 };
} catch (e: any) {
return { dir, result: `Error: ${e.message}`, cost: 0 };
}
}
async function main() {
const dirs = ["src/api/", "src/components/", "src/services/", "src/utils/"];
console.log(`Analyzing ${dirs.length} modules in parallel...\n`);
const results = await Promise.all(dirs.map(analyze));
const totalCost = results.reduce((sum, r) => sum + r.cost, 0);
const report = [
`# Module Analysis — ${new Date().toISOString().split("T")[0]}`,
"",
...results.flatMap((r) => [`## ${r.dir}`, "", r.result, "", "---", ""]),
`**Total cost: $${totalCost.toFixed(4)}**`,
].join("\n");
fs.mkdirSync("reports", { recursive: true });
fs.writeFileSync("reports/analysis.md", report);
console.log(`Report: reports/analysis.md ($${totalCost.toFixed(4)})`);
}
main();
Resumen
- TypeScript ofrece dos enfoques para SDK headless:
child_process(subprocess) y@anthropic-ai/claude-code(paquete nativo) execSyncpara scripts simples,exec(promisified) para async,spawnpara streaming- El tipado estático de TypeScript da seguridad al parsear respuestas JSON de Claude
- Scripts se ejecutan con
npx tsxpara soporte directo de TypeScript sin compilación Promise.allhabilita análisis paralelo de múltiples módulos- Integración directa con npm scripts (
npm run review,npm run docs:generate) - Python vs TypeScript: Python para CI/CD y data, TypeScript para build tools y proyectos JS
- El paquete SDK (
@anthropic-ai/claude-code) ofrece API más limpia pero requiere instalación — subprocess funciona siempre maxBufferdebe incrementarse (10MB+) para outputs grandes de Claude
Recursos Adicionales
- Claude Code CLI Reference — Flags
-p,--output-format,--allowedTools - Node.js child_process — Documentación oficial de exec, execSync, spawn
- tsx (TypeScript Execute) — Runner de TypeScript para scripts
- TypeScript Handbook — Referencia de tipos e interfaces
- Claude Code Best Practices — Buenas prácticas de automatización
- npm Scripts — Integración con npm
- Claude Code Hooks — Hooks que complementan el SDK
- Claude Code Overview — Contexto general de Claude Code
Siguiente cápsula: En la cápsula 06 (proyecto) integras todo: hooks de todos los eventos + un script SDK que orquesta el pipeline completo. SessionStart configura, PreToolUse valida, PostToolUse lintea, SubagentStop reporta, y un script Python dispara y procesa todo el flujo. El proyecto cierra el módulo demostrando que hooks + SDK convierten Claude Code en un sistema automatizado de desarrollo.