File size: 10,426 Bytes
95eb75a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// ─── 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;
}