// ─── fileToolsAdvanced.ts ───────────────────────────────────────────────────── // S742: estratto da fileTools.ts — handler per 6 tool avanzati S706: // copy_file · file_info · file_diff · append_to_file · clone_url_to_vfs · batch_file_op // Ogni tool restituisce string | null. // Importato da fileTools.ts tramite tryExecuteAdvanced(). // ───────────────────────────────────────────────────────────────────────────── import { vfsAsync, fileBackupsDb } from "../vfsDb"; export type AdvancedToolArgs = Record; export async function tryExecuteAdvanced( name: string, args: AdvancedToolArgs, signal?: AbortSignal ): Promise { // ── S706: copy_file ────────────────────────────────────────────────────────── if (name === "copy_file") { const { from, to } = args as { from: string; to: string }; const content = await vfsAsync.read(from); if (content === null) { const all = await vfsAsync.list() as Array<{ path: string }>; return `❌ copy_file: sorgente "${from}" non trovata.\nDisponibili: ${all.map(f => f.path).join(", ") || "nessuno"}`; } const existing = await vfsAsync.read(to); if (existing !== null) return `❌ copy_file: destinazione "${to}" esiste già. Cancellala con delete_file prima.`; const files2 = await vfsAsync.list() as Array<{ path: string; type?: string }>; const mime2 = (files2.find(f => f.path === from) as { type?: string } | undefined)?.type ?? "text/plain"; await vfsAsync.write(to, content, mime2); return `✅ Copiato: \`${from}\` → \`${to}\` (${content.length} chars)`; } // ── S706: file_info ────────────────────────────────────────────────────────── if (name === "file_info") { const { path: fiPath } = args as { path: string }; const content = await vfsAsync.read(fiPath); if (content === null) { const all = await vfsAsync.list() as Array<{ path: string }>; return `❌ file_info: "${fiPath}" non trovato.\nDisponibili: ${all.map(f => f.path).join(", ") || "nessuno"}`; } const allF = await vfsAsync.list() as Array<{ path: string; type?: string; size?: number }>; const meta = allF.find(f => f.path === fiPath); const isText = !content.startsWith("data:"); const lines = isText ? content.split("\n").length : null; const words = isText ? content.split(/\s+/).filter(Boolean).length : null; const ext = fiPath.split(".").pop()?.toLowerCase() ?? ""; const info: string[] = [ `📄 **Info: \`${fiPath}\`**`, `- **Dimensione**: ${content.length.toLocaleString()} chars${meta?.size ? ` (${meta.size.toLocaleString()} bytes)` : ""}`, `- **Tipo MIME**: ${meta?.type ?? "text/plain"}`, `- **Estensione**: .${ext}`, ]; if (lines !== null) info.push(`- **Righe**: ${lines.toLocaleString()}`); if (words !== null) info.push(`- **Parole**: ${words.toLocaleString()}`); if (content.startsWith("data:")) { const mimeFromUrl = content.match(/^data:([^;]+)/)?.[1] ?? "unknown"; info.push(`- **Contenuto**: file binario/media (${mimeFromUrl})`); } else { const hasNonAscii = /[^\x00-\x7F]/.test(content.slice(0, 1000)); info.push(`- **Encoding**: ${hasNonAscii ? "UTF-8 (con caratteri non-ASCII)" : "ASCII/UTF-8"}`); } const backups = await fileBackupsDb.list(fiPath); if (backups.length) info.push(`- **Backup disponibili**: ${backups.length}`); return info.join("\n"); } // ── S706: file_diff ────────────────────────────────────────────────────────── if (name === "file_diff") { const { path_a, path_b } = args as { path_a: string; path_b: string }; const [ca, cb2] = await Promise.all([vfsAsync.read(path_a), vfsAsync.read(path_b)]); if (ca === null) return `❌ file_diff: "${path_a}" non trovato nel VFS.`; if (cb2 === null) return `❌ file_diff: "${path_b}" non trovato nel VFS.`; const linesA = ca.split("\n"), linesB = cb2.split("\n"); const patch: string[] = []; let i = 0, j = 0; while (i < linesA.length || j < linesB.length) { if (i < linesA.length && j < linesB.length && linesA[i] === linesB[j]) { patch.push(` ${linesA[i]}`); i++; j++; } else if (j >= linesB.length || (i < linesA.length && linesA[i] !== linesB[j])) { patch.push(`-${linesA[i] ?? ""}`); i++; } else { patch.push(`+${linesB[j]}`); j++; } } const added = patch.filter(l => l.startsWith("+")).length; const removed = patch.filter(l => l.startsWith("-")).length; if (added === 0 && removed === 0) return `✅ file_diff: **"${path_a}"** e **"${path_b}"** sono identici (${linesA.length} righe).`; const preview = patch.slice(0, 100).join("\n"); return `📊 **Diff: \`${path_a}\` ↔ \`${path_b}\`**\n+${added} righe aggiunte / -${removed} rimosse\n\n\`\`\`diff\n${preview}${patch.length > 100 ? `\n…(+${patch.length - 100} righe)` : ""}\n\`\`\``; } // ── S706: append_to_file ───────────────────────────────────────────────────── if (name === "append_to_file") { const { path: apPath, content: apContent, separator = "\n" } = args as { path: string; content: string; separator?: string }; const existing = await vfsAsync.read(apPath); const newContent = existing !== null ? existing + separator + apContent : apContent; await vfsAsync.write(apPath, newContent); const _wr = await vfsAsync.read(apPath); if (_wr === null) return `⚠️ append_to_file: file non salvato (quota localStorage esaurita).`; return `✅ Append a \`${apPath}\` — +${apContent.length} chars, totale ${newContent.length} chars`; } // ── S706: clone_url_to_vfs ─────────────────────────────────────────────────── if (name === "clone_url_to_vfs") { const { url: cuUrl, save_path: cuPath, format: cuFmt = "auto" } = args as { url: string; save_path: string; format?: string }; if (!cuUrl || !cuPath) return "❌ clone_url_to_vfs: url e save_path sono obbligatori."; try { const PROXIES = [ (u: string) => `https://api.allorigins.win/raw?url=${encodeURIComponent(u)}`, (u: string) => `https://corsproxy.io/?${encodeURIComponent(u)}`, (u: string) => u, ]; let fetchedOk = false, mimeType = "text/plain", savedAs = ""; for (const proxy of PROXIES) { try { const resp = await fetch(proxy(cuUrl), { signal }); if (!resp.ok) continue; const ct = resp.headers.get("content-type") ?? ""; const isBinary = cuFmt === "binary" || (!ct.includes("text") && !ct.includes("json") && !ct.includes("xml") && !ct.includes("svg") && cuFmt !== "text"); if (isBinary) { const blob = await resp.blob(); const dr: string = await new Promise((res, rej) => { const fr = new FileReader(); fr.onload = () => res(fr.result as string); fr.onerror = rej; fr.readAsDataURL(blob); }); mimeType = ct.split(";")[0].trim() || "application/octet-stream"; await vfsAsync.write(cuPath, dr, mimeType); savedAs = `binario (${blob.size} bytes, ${mimeType})`; } else { const text = await resp.text(); mimeType = ct.split(";")[0].trim() || "text/plain"; await vfsAsync.write(cuPath, text, mimeType); savedAs = `testo (${text.length} chars)`; } fetchedOk = true; break; } catch { continue; } } if (!fetchedOk) return `❌ clone_url_to_vfs: impossibile scaricare "${cuUrl}" (tutti i proxy CORS falliti).`; return `✅ Download → \`${cuPath}\` ← ${cuUrl}\n${savedAs}`; } catch (e) { return `❌ clone_url_to_vfs: ${e instanceof Error ? e.message : String(e)}`; } } // ── S706: batch_file_op ────────────────────────────────────────────────────── if (name === "batch_file_op") { const { operation: bOp, pattern: bPat, replacement: bRep, paths: bPaths, dry_run = false } = args as { operation: "copy" | "delete" | "rename" | "list_matches"; pattern: string; replacement?: string; paths?: string[]; dry_run?: boolean; }; const allBatch = await vfsAsync.list() as Array<{ path: string }>; let bRx: RegExp; try { bRx = new RegExp(bPat, "i"); } catch { return `❌ batch_file_op: pattern regex non valido: "${bPat}"`; } const bTargets = (bPaths ? allBatch.filter(f => bPaths.includes(f.path)) : allBatch).filter(f => bRx.test(f.path)); if (!bTargets.length) return `📂 batch_file_op: nessun file corrisponde a \`${bPat}\`.`; if (bOp === "list_matches") return `📂 **${bTargets.length} file corrispondono a \`${bPat}\`:**\n${bTargets.map(f => `• \`${f.path}\``).join("\n")}`; const bResults: string[] = []; for (const f of bTargets) { if (bOp === "delete") { if (!dry_run) await vfsAsync.delete(f.path); bResults.push(`${dry_run ? "🔍" : "🗑️"} ${f.path}`); } else if ((bOp === "copy" || bOp === "rename") && bRep !== undefined) { const dest = f.path.replace(bRx, bRep); if (!dry_run) { const c = await vfsAsync.read(f.path); if (c !== null) { await vfsAsync.write(dest, c); if (bOp === "rename") await vfsAsync.delete(f.path); } } bResults.push(`${dry_run ? "🔍" : bOp === "copy" ? "📋" : "🔄"} ${f.path} → ${dest}`); } } return `✅ **batch_file_op (${bOp})${dry_run ? " — DRY RUN" : ""}**\n${bResults.join("\n")}`; } return null; }