Spaces:
Sleeping
Sleeping
| // ─── 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<string, unknown>; | |
| export async function tryExecuteAdvanced( | |
| name: string, | |
| args: AdvancedToolArgs, | |
| signal?: AbortSignal | |
| ): Promise<string | null> { | |
| // ── 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; | |
| } | |