strapi-plugin-mcp-chat 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -83,13 +83,13 @@ var audio_default = ({ strapi }) => ({
83
83
  });
84
84
 
85
85
  // server/src/controllers/frontend.ts
86
- var import_node_fs7 = __toESM(require("node:fs"));
87
- var import_node_path7 = __toESM(require("node:path"));
86
+ var import_node_fs8 = __toESM(require("node:fs"));
87
+ var import_node_path8 = __toESM(require("node:path"));
88
88
  var import_jszip = __toESM(require("jszip"));
89
89
 
90
90
  // server/src/provision/orchestrate.ts
91
- var import_node_fs4 = __toESM(require("node:fs"));
92
- var import_node_path4 = __toESM(require("node:path"));
91
+ var import_node_fs5 = __toESM(require("node:fs"));
92
+ var import_node_path5 = __toESM(require("node:path"));
93
93
 
94
94
  // server/src/provision/manifest.ts
95
95
  var import_utils = require("@strapi/utils");
@@ -267,8 +267,8 @@ function validateManifest(raw) {
267
267
  const parsed = manifestSchema.safeParse(raw);
268
268
  if (parsed.success) return { ok: true, data: parsed.data };
269
269
  const errors = parsed.error.issues.map((i) => {
270
- const path9 = i.path.length ? `${i.path.join(".")}: ` : "";
271
- return `${path9}${i.message}`;
270
+ const path10 = i.path.length ? `${i.path.join(".")}: ` : "";
271
+ return `${path10}${i.message}`;
272
272
  });
273
273
  return { ok: false, errors };
274
274
  }
@@ -980,8 +980,8 @@ function buildPreviewConfig(manifest, framework = manifest.framework) {
980
980
  routes[apiUid(ct.singularName)] = ct.preview?.route ?? "/";
981
981
  }
982
982
  const routesJson = JSON.stringify(routes, null, 2);
983
- const isNext = framework === "next";
984
- const urlBranch = isNext ? ` // Next.js: rota de draft mode que seta o cookie e redireciona p/ \`path\`.
983
+ const isNext2 = framework === "next";
984
+ const urlBranch = isNext2 ? ` // Next.js: rota de draft mode que seta o cookie e redireciona p/ \`path\`.
985
985
  const qs = new URLSearchParams({ secret, status: status ?? 'draft', path: pathname });
986
986
  return \`\${clientUrl}/api/preview?\${qs.toString()}\`;` : ` // SPA (Vite/TanStack): abre a p\xE1gina direta; o front l\xEA ?preview/status.
987
987
  const qs = new URLSearchParams({ preview: '1', status: status ?? 'draft', secret });
@@ -1185,6 +1185,347 @@ function linkFrontend(manifest, opts) {
1185
1185
  return result;
1186
1186
  }
1187
1187
 
1188
+ // server/src/provision/wire.ts
1189
+ var import_node_fs4 = __toESM(require("node:fs"));
1190
+ var import_node_path4 = __toESM(require("node:path"));
1191
+ var OPENAI_URL = "https://api.openai.com/v1/chat/completions";
1192
+ var MODEL = process.env.OPENAI_CHAT_MODEL || "gpt-4o";
1193
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
1194
+ "node_modules",
1195
+ ".git",
1196
+ "dist",
1197
+ ".next",
1198
+ ".output",
1199
+ ".vinxi",
1200
+ ".tanstack",
1201
+ "build",
1202
+ "coverage",
1203
+ ".turbo",
1204
+ ".cache",
1205
+ "public"
1206
+ ]);
1207
+ function ensureInside2(base, target) {
1208
+ const b = import_node_path4.default.resolve(base);
1209
+ const t = import_node_path4.default.resolve(target);
1210
+ return t === b || t.startsWith(b + import_node_path4.default.sep);
1211
+ }
1212
+ function isNext(manifest) {
1213
+ return manifest.framework === "next";
1214
+ }
1215
+ function usesAtAlias(frontendDir) {
1216
+ for (const f of ["tsconfig.json", "tsconfig.app.json", "jsconfig.json"]) {
1217
+ try {
1218
+ const c = import_node_fs4.default.readFileSync(import_node_path4.default.join(frontendDir, f), "utf8");
1219
+ if (/"@\/\*"\s*:/.test(c)) return true;
1220
+ } catch {
1221
+ }
1222
+ }
1223
+ return false;
1224
+ }
1225
+ function strapiClientSrc(manifest) {
1226
+ const envExpr = isNext(manifest) ? "(typeof process !== 'undefined' ? process.env.NEXT_PUBLIC_STRAPI_URL : undefined)" : "(import.meta as any).env?.VITE_STRAPI_URL";
1227
+ return `// Gerado pelo mcp-chat \u2014 camada de acesso \xE0 Strapi (REST, flat, sem nesting).
1228
+ // N\xE3o edite \xE0 m\xE3o: \xE9 regenerado ao religar o frontend.
1229
+ export const STRAPI_URL =
1230
+ (${envExpr} || "http://localhost:1337").replace(/\\/$/, "");
1231
+
1232
+ export type PreviewMode = { isPreview: boolean; status: "draft" | "published" };
1233
+
1234
+ /** L\xEA o modo de preview da URL (?preview=1 / ?status=draft). */
1235
+ export function getPreviewMode(): PreviewMode {
1236
+ if (typeof window === "undefined") return { isPreview: false, status: "published" };
1237
+ const p = new URLSearchParams(window.location.search);
1238
+ const status = p.get("status");
1239
+ const isPreview = p.get("preview") === "1" || p.has("preview") || status === "draft";
1240
+ return { isPreview, status: status === "draft" || isPreview ? "draft" : "published" };
1241
+ }
1242
+
1243
+ /** Busca um singleType e devolve s\xF3 os atributos (objeto). null em erro. */
1244
+ export async function fetchSection<T = Record<string, any>>(
1245
+ name: string,
1246
+ status: "draft" | "published" = "published"
1247
+ ): Promise<T | null> {
1248
+ const qs = status === "draft" ? "?status=draft" : "";
1249
+ try {
1250
+ const res = await fetch(\`\${STRAPI_URL}/api/\${name}\${qs}\`);
1251
+ if (!res.ok) return null;
1252
+ const json = await res.json();
1253
+ return (json?.data ?? null) as T | null;
1254
+ } catch {
1255
+ return null;
1256
+ }
1257
+ }
1258
+
1259
+ /** Busca uma collection (lista) pelo nome plural; ordena por sortOrder se existir. */
1260
+ export async function fetchCollection<T = Record<string, any>>(
1261
+ pluralName: string,
1262
+ status: "draft" | "published" = "published"
1263
+ ): Promise<T[]> {
1264
+ const qs = new URLSearchParams({ "sort": "sortOrder:asc", "pagination[pageSize]": "100" });
1265
+ if (status === "draft") qs.set("status", "draft");
1266
+ try {
1267
+ const res = await fetch(\`\${STRAPI_URL}/api/\${pluralName}?\${qs.toString()}\`);
1268
+ if (!res.ok) return [];
1269
+ const json = await res.json();
1270
+ return (Array.isArray(json?.data) ? json.data : []) as T[];
1271
+ } catch {
1272
+ return [];
1273
+ }
1274
+ }
1275
+ `;
1276
+ }
1277
+ function hooksSrc(manifest, libImport) {
1278
+ const clientDirective = isNext(manifest) ? `"use client";
1279
+
1280
+ ` : "";
1281
+ return `${clientDirective}// Gerado pelo mcp-chat \u2014 hooks de leitura da Strapi (sem depend\xEAncias al\xE9m do React).
1282
+ // Em modo preview faz polling curto + refetch via postMessage (reflete edi\xE7\xF5es ao vivo).
1283
+ import { useEffect, useState } from "react";
1284
+ import { fetchSection, fetchCollection, getPreviewMode } from "${libImport}";
1285
+
1286
+ export function useSection<T = Record<string, any>>(name: string): Partial<T> {
1287
+ const [data, setData] = useState<Partial<T>>({});
1288
+ useEffect(() => {
1289
+ let alive = true;
1290
+ const { isPreview, status } = getPreviewMode();
1291
+ const load = () => fetchSection<T>(name, status).then((d) => { if (alive && d) setData(d as Partial<T>); });
1292
+ load();
1293
+ if (!isPreview) return () => { alive = false; };
1294
+ const id = window.setInterval(load, 2500);
1295
+ const onMsg = () => load();
1296
+ window.addEventListener("message", onMsg);
1297
+ return () => { alive = false; window.clearInterval(id); window.removeEventListener("message", onMsg); };
1298
+ }, [name]);
1299
+ return data;
1300
+ }
1301
+
1302
+ export function useCollection<T = Record<string, any>>(pluralName: string): T[] {
1303
+ const [data, setData] = useState<T[]>([]);
1304
+ useEffect(() => {
1305
+ let alive = true;
1306
+ const { isPreview, status } = getPreviewMode();
1307
+ const load = () => fetchCollection<T>(pluralName, status).then((d) => { if (alive) setData(d); });
1308
+ load();
1309
+ if (!isPreview) return () => { alive = false; };
1310
+ const id = window.setInterval(load, 2500);
1311
+ const onMsg = () => load();
1312
+ window.addEventListener("message", onMsg);
1313
+ return () => { alive = false; window.clearInterval(id); window.removeEventListener("message", onMsg); };
1314
+ }, [pluralName]);
1315
+ return data;
1316
+ }
1317
+ `;
1318
+ }
1319
+ function writeDataLayer(frontendDir, manifest, dryRun) {
1320
+ const written = [];
1321
+ const libDir = import_node_path4.default.join(frontendDir, "src", "lib");
1322
+ const hooksDir = import_node_path4.default.join(frontendDir, "src", "hooks");
1323
+ const libFile = import_node_path4.default.join(libDir, "strapi.ts");
1324
+ const hooksFile = import_node_path4.default.join(hooksDir, "useStrapi.ts");
1325
+ if (!ensureInside2(frontendDir, libFile) || !ensureInside2(frontendDir, hooksFile)) {
1326
+ throw new Error("camada de dados fora do frontendDir");
1327
+ }
1328
+ const libImport = usesAtAlias(frontendDir) ? "@/lib/strapi" : "../lib/strapi";
1329
+ if (!dryRun) {
1330
+ import_node_fs4.default.mkdirSync(libDir, { recursive: true });
1331
+ import_node_fs4.default.mkdirSync(hooksDir, { recursive: true });
1332
+ import_node_fs4.default.writeFileSync(libFile, strapiClientSrc(manifest), "utf8");
1333
+ import_node_fs4.default.writeFileSync(hooksFile, hooksSrc(manifest, libImport), "utf8");
1334
+ }
1335
+ written.push("src/lib/strapi.ts", "src/hooks/useStrapi.ts");
1336
+ return written;
1337
+ }
1338
+ var CODE_EXT = /* @__PURE__ */ new Set([".tsx", ".jsx"]);
1339
+ var MAX_COMPONENT_CHARS = 16e3;
1340
+ function walkComponents(dir, base, out) {
1341
+ let entries;
1342
+ try {
1343
+ entries = import_node_fs4.default.readdirSync(dir, { withFileTypes: true });
1344
+ } catch {
1345
+ return;
1346
+ }
1347
+ for (const e of entries) {
1348
+ if (e.name.startsWith(".")) continue;
1349
+ const full = import_node_path4.default.join(dir, e.name);
1350
+ if (e.isDirectory()) {
1351
+ if (SKIP_DIRS.has(e.name) || e.name === "ui") continue;
1352
+ walkComponents(full, base, out);
1353
+ } else if (CODE_EXT.has(import_node_path4.default.extname(e.name))) {
1354
+ out.push(import_node_path4.default.relative(base, full));
1355
+ }
1356
+ }
1357
+ }
1358
+ function resolvePlural(strapi, singularName) {
1359
+ const real = strapi?.contentTypes?.[apiUid(singularName)]?.info?.pluralName;
1360
+ return real || `${singularName}s`;
1361
+ }
1362
+ function contentModelSummary(strapi, manifest) {
1363
+ const lines = [];
1364
+ for (const ct of manifest.contentTypes) {
1365
+ const fields = Object.keys(ct.attributes || {}).join(", ");
1366
+ if (ct.kind === "singleType") {
1367
+ lines.push(`singleType "${ct.singularName}" (useSection("${ct.singularName}")) campos: ${fields}`);
1368
+ } else {
1369
+ const plural = resolvePlural(strapi, ct.singularName);
1370
+ lines.push(`collection "${ct.singularName}" (useCollection("${plural}")) campos: ${fields}`);
1371
+ }
1372
+ }
1373
+ return lines.join("\n");
1374
+ }
1375
+ function wirePrompt(rel, source, model, hooksImport, seedSnippet) {
1376
+ return `Voc\xEA religa um componente React para ler o conte\xFAdo da Strapi, SEM quebrar nada.
1377
+
1378
+ MODELO DE CONTE\xDADO (use EXATAMENTE estes nomes de hook/campo):
1379
+ ${model}
1380
+
1381
+ DADOS SEMEADOS (para casar o texto hardcoded com o campo certo):
1382
+ ${seedSnippet}
1383
+
1384
+ REGRAS (siga \xE0 risca):
1385
+ - Importe os hooks de "${hooksImport}" (ex.: import { useSection, useCollection } from "${hooksImport}";).
1386
+ - Dentro do componente, chame os hooks necess\xE1rios (ex.: const hero = useSection("hero-section-content");).
1387
+ - Troque CADA texto hardcoded que casa com um campo por { obj.campo ?? "TEXTO ORIGINAL" } \u2014 SEMPRE mantenha o texto original como fallback no ?? .
1388
+ - Para listas (arrays hardcoded de objetos), troque o array por useCollection(...) e itere sobre ele; mantenha \xEDcones/imagens/classes/animacoes/layout EXATAMENTE como est\xE3o (n\xE3o s\xE3o conte\xFAdo).
1389
+ - N\xC3O altere imports de \xEDcones/assets, JSX estrutural, classes Tailwind, hooks de anima\xE7\xE3o, nada que n\xE3o seja texto/dado.
1390
+ - N\xC3O invente campos nem textos. Se um trecho n\xE3o casa com nenhum campo, deixe como est\xE1.
1391
+ - Mantenha o arquivo V\xC1LIDO e COMPLETO (TypeScript/TSX que compila). Responda com JSON: {"code":"<arquivo .tsx completo>"} e NADA al\xE9m disso.
1392
+
1393
+ ARQUIVO: ${rel}
1394
+ \`\`\`tsx
1395
+ ${source}
1396
+ \`\`\``;
1397
+ }
1398
+ function syntaxError(code) {
1399
+ let esbuild;
1400
+ try {
1401
+ esbuild = require("esbuild");
1402
+ } catch {
1403
+ return null;
1404
+ }
1405
+ try {
1406
+ esbuild.transformSync(code, { loader: "tsx", jsx: "automatic" });
1407
+ return "";
1408
+ } catch (e) {
1409
+ return (e?.message || "erro de sintaxe").split("\n")[0];
1410
+ }
1411
+ }
1412
+ function looksSane(original, next, hooksImport) {
1413
+ if (!next || next.length < 40) return "sa\xEDda vazia/curta demais";
1414
+ if (next.length < original.length * 0.5) return "sa\xEDda muito menor que o original (poss\xEDvel truncamento)";
1415
+ if (!/export\s+default|export\s+function|export\s+const/.test(next)) return "sem export";
1416
+ if (!next.includes(hooksImport)) return "n\xE3o importou os hooks";
1417
+ const balanced = (s, a, b) => s.split(a).length - 1 === s.split(b).length - 1;
1418
+ if (!balanced(next, "{", "}")) return "chaves desbalanceadas";
1419
+ if (!balanced(next, "(", ")")) return "par\xEAnteses desbalanceados";
1420
+ if (!balanced(next, "[", "]")) return "colchetes desbalanceados";
1421
+ if (/```/.test(next)) return "markdown na sa\xEDda";
1422
+ return null;
1423
+ }
1424
+ async function callOpenAI(apiKey, prompt) {
1425
+ const res = await fetch(OPENAI_URL, {
1426
+ method: "POST",
1427
+ headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
1428
+ body: JSON.stringify({
1429
+ model: MODEL,
1430
+ temperature: 0,
1431
+ response_format: { type: "json_object" },
1432
+ messages: [
1433
+ { role: "system", content: 'Voc\xEA religa componentes React para a Strapi e responde s\xF3 com JSON {"code": "..."} v\xE1lido.' },
1434
+ { role: "user", content: prompt }
1435
+ ]
1436
+ })
1437
+ });
1438
+ if (!res.ok) throw new Error(`OpenAI: ${await res.text()}`);
1439
+ const data = await res.json();
1440
+ const raw = JSON.parse(data.choices?.[0]?.message?.content ?? "{}");
1441
+ return typeof raw.code === "string" ? raw.code : "";
1442
+ }
1443
+ async function wireFrontend(_strapi, opts) {
1444
+ const { frontendDir, manifest } = opts;
1445
+ const result = {
1446
+ ok: false,
1447
+ dataLayer: [],
1448
+ componentsWired: [],
1449
+ componentsSkipped: [],
1450
+ warnings: [],
1451
+ errors: []
1452
+ };
1453
+ if (!import_node_path4.default.isAbsolute(frontendDir)) {
1454
+ result.errors.push("frontendDir deve ser absoluto");
1455
+ return result;
1456
+ }
1457
+ try {
1458
+ result.dataLayer = writeDataLayer(frontendDir, manifest, opts.dryRun);
1459
+ } catch (e) {
1460
+ result.errors.push(`camada de dados: ${e?.message ?? e}`);
1461
+ return result;
1462
+ }
1463
+ const apiKey = process.env.OPENAI_API_KEY;
1464
+ if (!apiKey) {
1465
+ result.warnings.push("Sem OPENAI_API_KEY: camada de dados criada, mas os componentes N\xC3O foram religados (precisa da chave).");
1466
+ result.ok = true;
1467
+ return result;
1468
+ }
1469
+ const srcDir = import_node_path4.default.join(frontendDir, "src");
1470
+ const all = [];
1471
+ walkComponents(srcDir, frontendDir, all);
1472
+ const components = all.filter((rel) => /(\/|^)(components|pages|app|routes)(\/|$)/.test(rel.replace(/\\/g, "/")));
1473
+ const model = contentModelSummary(_strapi, manifest);
1474
+ const hooksImport = usesAtAlias(frontendDir) ? "@/hooks/useStrapi" : "../hooks/useStrapi";
1475
+ const seedSnippet = JSON.stringify(manifest.seed ?? []).slice(0, 6e3);
1476
+ for (const rel of components) {
1477
+ const abs = import_node_path4.default.join(frontendDir, rel);
1478
+ if (!ensureInside2(frontendDir, abs)) continue;
1479
+ let source;
1480
+ try {
1481
+ source = import_node_fs4.default.readFileSync(abs, "utf8");
1482
+ } catch {
1483
+ continue;
1484
+ }
1485
+ if (source.length > MAX_COMPONENT_CHARS) {
1486
+ result.componentsSkipped.push({ rel, reason: "arquivo grande demais" });
1487
+ continue;
1488
+ }
1489
+ if (source.includes("useStrapi")) {
1490
+ result.componentsSkipped.push({ rel, reason: "j\xE1 religado" });
1491
+ continue;
1492
+ }
1493
+ const check = (code) => {
1494
+ const h = looksSane(source, code, hooksImport);
1495
+ if (h) return h;
1496
+ const s = syntaxError(code);
1497
+ return s ? `sintaxe: ${s}` : null;
1498
+ };
1499
+ try {
1500
+ const prompt = wirePrompt(rel, source, model, hooksImport, seedSnippet);
1501
+ let next = await callOpenAI(apiKey, prompt);
1502
+ let bad = check(next);
1503
+ if (bad) {
1504
+ const fix = `${prompt}
1505
+
1506
+ A sua tentativa anterior foi REJEITADA por: ${bad}. Corrija e responda s\xF3 com o JSON {"code":"..."} do arquivo completo e v\xE1lido.`;
1507
+ const next2 = await callOpenAI(apiKey, fix);
1508
+ const bad2 = check(next2);
1509
+ if (bad2) {
1510
+ result.componentsSkipped.push({ rel, reason: bad2 });
1511
+ continue;
1512
+ }
1513
+ next = next2;
1514
+ }
1515
+ if (!opts.dryRun) {
1516
+ const bak = abs + ".bak";
1517
+ if (!import_node_fs4.default.existsSync(bak)) import_node_fs4.default.writeFileSync(bak, source, "utf8");
1518
+ import_node_fs4.default.writeFileSync(abs, next, "utf8");
1519
+ }
1520
+ result.componentsWired.push(rel);
1521
+ } catch (e) {
1522
+ result.componentsSkipped.push({ rel, reason: `IA: ${e?.message ?? e}` });
1523
+ }
1524
+ }
1525
+ result.ok = true;
1526
+ return result;
1527
+ }
1528
+
1188
1529
  // server/src/provision/permissions.ts
1189
1530
  async function grantPublicRead(strapi, manifest) {
1190
1531
  const result = { granted: [], errors: [] };
@@ -1223,17 +1564,17 @@ var MARKER_DIR = ".mcp-chat";
1223
1564
  var MARKER_FILE = "pending-provision.json";
1224
1565
  var DONE_FILE = "last-provision.json";
1225
1566
  function markerPath(strapiAppDir) {
1226
- return import_node_path4.default.join(strapiAppDir, MARKER_DIR, MARKER_FILE);
1567
+ return import_node_path5.default.join(strapiAppDir, MARKER_DIR, MARKER_FILE);
1227
1568
  }
1228
1569
  function donePath(strapiAppDir) {
1229
- return import_node_path4.default.join(strapiAppDir, MARKER_DIR, DONE_FILE);
1570
+ return import_node_path5.default.join(strapiAppDir, MARKER_DIR, DONE_FILE);
1230
1571
  }
1231
1572
  function getProvisionStatus(strapiAppDir) {
1232
- const pending = import_node_fs4.default.existsSync(markerPath(strapiAppDir));
1573
+ const pending = import_node_fs5.default.existsSync(markerPath(strapiAppDir));
1233
1574
  let done = null;
1234
1575
  try {
1235
1576
  const dp = donePath(strapiAppDir);
1236
- if (import_node_fs4.default.existsSync(dp)) done = JSON.parse(import_node_fs4.default.readFileSync(dp, "utf8"));
1577
+ if (import_node_fs5.default.existsSync(dp)) done = JSON.parse(import_node_fs5.default.readFileSync(dp, "utf8"));
1237
1578
  } catch {
1238
1579
  }
1239
1580
  return { pending, done };
@@ -1269,10 +1610,10 @@ function stageProvision(strapi, input) {
1269
1610
  if (!input.dryRun) {
1270
1611
  try {
1271
1612
  const mp = markerPath(input.strapiAppDir);
1272
- import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(mp), { recursive: true });
1273
- import_node_fs4.default.writeFileSync(mp, JSON.stringify(marker, null, 2), "utf8");
1613
+ import_node_fs5.default.mkdirSync(import_node_path5.default.dirname(mp), { recursive: true });
1614
+ import_node_fs5.default.writeFileSync(mp, JSON.stringify(marker, null, 2), "utf8");
1274
1615
  try {
1275
- import_node_fs4.default.unlinkSync(donePath(input.strapiAppDir));
1616
+ import_node_fs5.default.unlinkSync(donePath(input.strapiAppDir));
1276
1617
  } catch {
1277
1618
  }
1278
1619
  result.staged = true;
@@ -1288,10 +1629,10 @@ function stageProvision(strapi, input) {
1288
1629
  async function runPendingProvision(strapi, strapiAppDir) {
1289
1630
  const result = { ran: false, errors: [] };
1290
1631
  const mp = markerPath(strapiAppDir);
1291
- if (!import_node_fs4.default.existsSync(mp)) return result;
1632
+ if (!import_node_fs5.default.existsSync(mp)) return result;
1292
1633
  let marker;
1293
1634
  try {
1294
- marker = JSON.parse(import_node_fs4.default.readFileSync(mp, "utf8"));
1635
+ marker = JSON.parse(import_node_fs5.default.readFileSync(mp, "utf8"));
1295
1636
  } catch (e) {
1296
1637
  result.errors.push(`marcador ileg\xEDvel: ${e?.message ?? e}`);
1297
1638
  return result;
@@ -1319,6 +1660,15 @@ async function runPendingProvision(strapi, strapiAppDir) {
1319
1660
  } catch (e) {
1320
1661
  result.errors.push(`link: ${e?.message ?? e}`);
1321
1662
  }
1663
+ try {
1664
+ result.wire = await wireFrontend(strapi, {
1665
+ frontendDir: marker.frontendDir,
1666
+ manifest: marker.manifest
1667
+ });
1668
+ if (result.wire.errors.length) result.errors.push(...result.wire.errors.map((e) => `wire: ${e}`));
1669
+ } catch (e) {
1670
+ result.errors.push(`wire: ${e?.message ?? e}`);
1671
+ }
1322
1672
  try {
1323
1673
  const previewUrl = marker.context.frontendUrl || `http://localhost:${FRONTEND_BASE_PORT}`;
1324
1674
  const done = {
@@ -1332,24 +1682,24 @@ async function runPendingProvision(strapi, strapiAppDir) {
1332
1682
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
1333
1683
  };
1334
1684
  const dp = donePath(strapiAppDir);
1335
- import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(dp), { recursive: true });
1336
- import_node_fs4.default.writeFileSync(dp, JSON.stringify(done, null, 2), "utf8");
1685
+ import_node_fs5.default.mkdirSync(import_node_path5.default.dirname(dp), { recursive: true });
1686
+ import_node_fs5.default.writeFileSync(dp, JSON.stringify(done, null, 2), "utf8");
1337
1687
  } catch (e) {
1338
1688
  result.errors.push(`resumo: ${e?.message ?? e}`);
1339
1689
  }
1340
1690
  try {
1341
- import_node_fs4.default.unlinkSync(mp);
1691
+ import_node_fs5.default.unlinkSync(mp);
1342
1692
  } catch {
1343
1693
  }
1344
1694
  return result;
1345
1695
  }
1346
1696
 
1347
1697
  // server/src/provision/infer.ts
1348
- var import_node_fs5 = __toESM(require("node:fs"));
1349
- var import_node_path5 = __toESM(require("node:path"));
1350
- var OPENAI_URL = "https://api.openai.com/v1/chat/completions";
1351
- var MODEL = process.env.OPENAI_CHAT_MODEL || "gpt-4o";
1352
- var SKIP_DIRS = /* @__PURE__ */ new Set([
1698
+ var import_node_fs6 = __toESM(require("node:fs"));
1699
+ var import_node_path6 = __toESM(require("node:path"));
1700
+ var OPENAI_URL2 = "https://api.openai.com/v1/chat/completions";
1701
+ var MODEL2 = process.env.OPENAI_CHAT_MODEL || "gpt-4o";
1702
+ var SKIP_DIRS2 = /* @__PURE__ */ new Set([
1353
1703
  "node_modules",
1354
1704
  ".git",
1355
1705
  "dist",
@@ -1363,7 +1713,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
1363
1713
  ".cache",
1364
1714
  "public"
1365
1715
  ]);
1366
- var CODE_EXT = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs"]);
1716
+ var CODE_EXT2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs"]);
1367
1717
  var MAX_FILES = 18;
1368
1718
  var MAX_TOTAL_CHARS = 6e4;
1369
1719
  var MAX_FILE_CHARS = 12e3;
@@ -1379,18 +1729,18 @@ function score(rel) {
1379
1729
  function walk(dir, base, out) {
1380
1730
  let entries;
1381
1731
  try {
1382
- entries = import_node_fs5.default.readdirSync(dir, { withFileTypes: true });
1732
+ entries = import_node_fs6.default.readdirSync(dir, { withFileTypes: true });
1383
1733
  } catch {
1384
1734
  return;
1385
1735
  }
1386
1736
  for (const e of entries) {
1387
1737
  if (e.name.startsWith(".") && e.name !== ".") continue;
1388
- const full = import_node_path5.default.join(dir, e.name);
1738
+ const full = import_node_path6.default.join(dir, e.name);
1389
1739
  if (e.isDirectory()) {
1390
- if (SKIP_DIRS.has(e.name)) continue;
1740
+ if (SKIP_DIRS2.has(e.name)) continue;
1391
1741
  walk(full, base, out);
1392
- } else if (CODE_EXT.has(import_node_path5.default.extname(e.name))) {
1393
- out.push(import_node_path5.default.relative(base, full));
1742
+ } else if (CODE_EXT2.has(import_node_path6.default.extname(e.name))) {
1743
+ out.push(import_node_path6.default.relative(base, full));
1394
1744
  }
1395
1745
  }
1396
1746
  }
@@ -1405,7 +1755,7 @@ function collectFiles(frontendDir) {
1405
1755
  for (const rel of all) {
1406
1756
  let content;
1407
1757
  try {
1408
- content = import_node_fs5.default.readFileSync(import_node_path5.default.join(frontendDir, rel), "utf8");
1758
+ content = import_node_fs6.default.readFileSync(import_node_path6.default.join(frontendDir, rel), "utf8");
1409
1759
  } catch {
1410
1760
  continue;
1411
1761
  }
@@ -1460,7 +1810,7 @@ function collectPageTexts(frontendDir) {
1460
1810
  for (const rel of ranked) {
1461
1811
  if (out.length >= MAX_TEXT_FILES) break;
1462
1812
  try {
1463
- const texts = extractTexts(import_node_fs5.default.readFileSync(import_node_path5.default.join(frontendDir, rel), "utf8"));
1813
+ const texts = extractTexts(import_node_fs6.default.readFileSync(import_node_path6.default.join(frontendDir, rel), "utf8"));
1464
1814
  if (texts.length) out.push({ rel, texts });
1465
1815
  } catch {
1466
1816
  }
@@ -1550,7 +1900,7 @@ function buildPageContentTypes(pageTexts, budget) {
1550
1900
  }
1551
1901
  function detectFramework2(frontendDir) {
1552
1902
  try {
1553
- const pkg = JSON.parse(import_node_fs5.default.readFileSync(import_node_path5.default.join(frontendDir, "package.json"), "utf8"));
1903
+ const pkg = JSON.parse(import_node_fs6.default.readFileSync(import_node_path6.default.join(frontendDir, "package.json"), "utf8"));
1554
1904
  const deps = { ...pkg.dependencies || {}, ...pkg.devDependencies || {} };
1555
1905
  if (deps.next) return "next";
1556
1906
  if (deps["@tanstack/react-start"]) return "tanstack";
@@ -1622,10 +1972,10 @@ async function inferManifest(strapi, frontendDir, opts) {
1622
1972
  warnings: [],
1623
1973
  errors: []
1624
1974
  };
1625
- const existing = import_node_path5.default.join(frontendDir, "strapi.manifest.json");
1626
- if (import_node_fs5.default.existsSync(existing)) {
1975
+ const existing = import_node_path6.default.join(frontendDir, "strapi.manifest.json");
1976
+ if (import_node_fs6.default.existsSync(existing)) {
1627
1977
  try {
1628
- const raw = JSON.parse(import_node_fs5.default.readFileSync(existing, "utf8"));
1978
+ const raw = JSON.parse(import_node_fs6.default.readFileSync(existing, "utf8"));
1629
1979
  const v2 = validateManifest(raw);
1630
1980
  result.inferred = false;
1631
1981
  result.rawManifest = raw;
@@ -1662,11 +2012,11 @@ async function inferManifest(strapi, frontendDir, opts) {
1662
2012
  let dataSeed = [];
1663
2013
  const apiKey = process.env.OPENAI_API_KEY;
1664
2014
  if (apiKey && collected.files.length) {
1665
- const callOpenAI = async (messages2) => {
1666
- const res = await fetch(OPENAI_URL, {
2015
+ const callOpenAI2 = async (messages2) => {
2016
+ const res = await fetch(OPENAI_URL2, {
1667
2017
  method: "POST",
1668
2018
  headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
1669
- body: JSON.stringify({ model: MODEL, temperature: 0, response_format: { type: "json_object" }, messages: messages2 })
2019
+ body: JSON.stringify({ model: MODEL2, temperature: 0, response_format: { type: "json_object" }, messages: messages2 })
1670
2020
  });
1671
2021
  if (!res.ok) throw new Error(`OpenAI: ${await res.text()}`);
1672
2022
  return res.json();
@@ -1677,7 +2027,7 @@ async function inferManifest(strapi, frontendDir, opts) {
1677
2027
  ];
1678
2028
  try {
1679
2029
  for (let attempt = 0; attempt < 2; attempt++) {
1680
- const data = await callOpenAI(messages);
2030
+ const data = await callOpenAI2(messages);
1681
2031
  const raw = JSON.parse(data.choices?.[0]?.message?.content ?? "{}");
1682
2032
  const candidate = {
1683
2033
  manifestVersion: 1,
@@ -1712,7 +2062,7 @@ async function inferManifest(strapi, frontendDir, opts) {
1712
2062
  const parts = [];
1713
2063
  for (const f of collected.files) {
1714
2064
  try {
1715
- parts.push(import_node_fs5.default.readFileSync(import_node_path5.default.join(frontendDir, f.rel), "utf8"));
2065
+ parts.push(import_node_fs6.default.readFileSync(import_node_path6.default.join(frontendDir, f.rel), "utf8"));
1716
2066
  } catch {
1717
2067
  parts.push(f.content);
1718
2068
  }
@@ -1779,12 +2129,12 @@ async function inferManifest(strapi, frontendDir, opts) {
1779
2129
  }
1780
2130
 
1781
2131
  // server/src/provision/integrate.ts
1782
- var import_node_fs6 = __toESM(require("node:fs"));
1783
- var import_node_path6 = __toESM(require("node:path"));
2132
+ var import_node_fs7 = __toESM(require("node:fs"));
2133
+ var import_node_path7 = __toESM(require("node:path"));
1784
2134
  var import_node_child_process2 = require("node:child_process");
1785
- var OPENAI_URL2 = "https://api.openai.com/v1/chat/completions";
1786
- var MODEL2 = process.env.OPENAI_CHAT_MODEL || "gpt-4o";
1787
- var SKIP_DIRS2 = /* @__PURE__ */ new Set([
2135
+ var OPENAI_URL3 = "https://api.openai.com/v1/chat/completions";
2136
+ var MODEL3 = process.env.OPENAI_CHAT_MODEL || "gpt-4o";
2137
+ var SKIP_DIRS3 = /* @__PURE__ */ new Set([
1788
2138
  "node_modules",
1789
2139
  ".git",
1790
2140
  "dist",
@@ -1798,7 +2148,7 @@ var SKIP_DIRS2 = /* @__PURE__ */ new Set([
1798
2148
  ".cache",
1799
2149
  "public"
1800
2150
  ]);
1801
- var CODE_EXT2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs"]);
2151
+ var CODE_EXT3 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs"]);
1802
2152
  var MAX_DATA_FILES = 3;
1803
2153
  var MAX_FILE_CHARS2 = 16e3;
1804
2154
  function score2(rel) {
@@ -1813,18 +2163,18 @@ function score2(rel) {
1813
2163
  function walk2(dir, base, out) {
1814
2164
  let entries;
1815
2165
  try {
1816
- entries = import_node_fs6.default.readdirSync(dir, { withFileTypes: true });
2166
+ entries = import_node_fs7.default.readdirSync(dir, { withFileTypes: true });
1817
2167
  } catch {
1818
2168
  return;
1819
2169
  }
1820
2170
  for (const e of entries) {
1821
2171
  if (e.name.startsWith(".")) continue;
1822
- const full = import_node_path6.default.join(dir, e.name);
2172
+ const full = import_node_path7.default.join(dir, e.name);
1823
2173
  if (e.isDirectory()) {
1824
- if (SKIP_DIRS2.has(e.name)) continue;
2174
+ if (SKIP_DIRS3.has(e.name)) continue;
1825
2175
  walk2(full, base, out);
1826
- } else if (CODE_EXT2.has(import_node_path6.default.extname(e.name))) {
1827
- out.push(import_node_path6.default.relative(base, full));
2176
+ } else if (CODE_EXT3.has(import_node_path7.default.extname(e.name))) {
2177
+ out.push(import_node_path7.default.relative(base, full));
1828
2178
  }
1829
2179
  }
1830
2180
  }
@@ -1833,7 +2183,7 @@ function findDataFiles(frontendDir) {
1833
2183
  walk2(frontendDir, frontendDir, all);
1834
2184
  return all.map((rel) => ({ rel, s: score2(rel) })).filter((x) => x.s > 0).filter(({ rel }) => {
1835
2185
  try {
1836
- const c = import_node_fs6.default.readFileSync(import_node_path6.default.join(frontendDir, rel), "utf8");
2186
+ const c = import_node_fs7.default.readFileSync(import_node_path7.default.join(frontendDir, rel), "utf8");
1837
2187
  return /export\s+const\s+\w+\s*[:=]\s*(\[|\{)/.test(c);
1838
2188
  } catch {
1839
2189
  return false;
@@ -1902,19 +2252,19 @@ export async function fetchSingle(singular: string, o: FetchOpts = {}): Promise<
1902
2252
  }
1903
2253
  `;
1904
2254
  async function ensureClientDep(frontendDir, warnings) {
1905
- if (import_node_fs6.default.existsSync(import_node_path6.default.join(frontendDir, "node_modules", "@strapi", "client"))) return;
2255
+ if (import_node_fs7.default.existsSync(import_node_path7.default.join(frontendDir, "node_modules", "@strapi", "client"))) return;
1906
2256
  try {
1907
- const pkgPath = import_node_path6.default.join(frontendDir, "package.json");
1908
- const pkg = JSON.parse(import_node_fs6.default.readFileSync(pkgPath, "utf8"));
2257
+ const pkgPath = import_node_path7.default.join(frontendDir, "package.json");
2258
+ const pkg = JSON.parse(import_node_fs7.default.readFileSync(pkgPath, "utf8"));
1909
2259
  pkg.dependencies = pkg.dependencies || {};
1910
2260
  if (!pkg.dependencies["@strapi/client"]) {
1911
2261
  pkg.dependencies["@strapi/client"] = "^1.6.2";
1912
- import_node_fs6.default.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
2262
+ import_node_fs7.default.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
1913
2263
  }
1914
2264
  } catch (e) {
1915
2265
  warnings.push(`package.json do frontend: ${e?.message ?? e}`);
1916
2266
  }
1917
- const pm = import_node_fs6.default.existsSync(import_node_path6.default.join(frontendDir, "bun.lock")) || import_node_fs6.default.existsSync(import_node_path6.default.join(frontendDir, "bun.lockb")) ? "bun" : import_node_fs6.default.existsSync(import_node_path6.default.join(frontendDir, "pnpm-lock.yaml")) ? "pnpm" : import_node_fs6.default.existsSync(import_node_path6.default.join(frontendDir, "yarn.lock")) ? "yarn" : "npm";
2267
+ const pm = import_node_fs7.default.existsSync(import_node_path7.default.join(frontendDir, "bun.lock")) || import_node_fs7.default.existsSync(import_node_path7.default.join(frontendDir, "bun.lockb")) ? "bun" : import_node_fs7.default.existsSync(import_node_path7.default.join(frontendDir, "pnpm-lock.yaml")) ? "pnpm" : import_node_fs7.default.existsSync(import_node_path7.default.join(frontendDir, "yarn.lock")) ? "yarn" : "npm";
1918
2268
  const args = pm === "npm" ? ["install", "@strapi/client", "--no-audit", "--no-fund"] : ["add", "@strapi/client"];
1919
2269
  await new Promise((resolve) => {
1920
2270
  try {
@@ -1966,11 +2316,11 @@ ${baseSrc.length > MAX_FILE_CHARS2 ? baseSrc.slice(0, MAX_FILE_CHARS2) : baseSrc
1966
2316
 
1967
2317
  AMOSTRA REAL DA RESPOSTA DO STRAPI (JSON, por singularName):
1968
2318
  ${JSON.stringify(sample, null, 1).slice(0, 18e3)}`;
1969
- const res = await fetch(OPENAI_URL2, {
2319
+ const res = await fetch(OPENAI_URL3, {
1970
2320
  method: "POST",
1971
2321
  headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
1972
2322
  body: JSON.stringify({
1973
- model: MODEL2,
2323
+ model: MODEL3,
1974
2324
  temperature: 0,
1975
2325
  messages: [
1976
2326
  { role: "system", content: "Voc\xEA gera uma fun\xE7\xE3o pura de mapeamento Strapi\u2192shape do frontend. Responde s\xF3 com a fun\xE7\xE3o." },
@@ -2124,15 +2474,15 @@ export function LanguageSwitcher() {
2124
2474
  export default LanguageSwitcher;
2125
2475
  `;
2126
2476
  function injectSwitcher(frontendDir, warnings, dataImport = "@/data/site") {
2127
- const compDir = import_node_path6.default.join(frontendDir, "src", "components");
2128
- import_node_fs6.default.mkdirSync(compDir, { recursive: true });
2129
- import_node_fs6.default.writeFileSync(import_node_path6.default.join(compDir, "LanguageSwitcher.tsx"), switcherTsx(dataImport), "utf8");
2477
+ const compDir = import_node_path7.default.join(frontendDir, "src", "components");
2478
+ import_node_fs7.default.mkdirSync(compDir, { recursive: true });
2479
+ import_node_fs7.default.writeFileSync(import_node_path7.default.join(compDir, "LanguageSwitcher.tsx"), switcherTsx(dataImport), "utf8");
2130
2480
  const rootRel = ["src/routes/__root.tsx", "src/routes/__root.jsx"].find(
2131
- (r) => import_node_fs6.default.existsSync(import_node_path6.default.join(frontendDir, r))
2481
+ (r) => import_node_fs7.default.existsSync(import_node_path7.default.join(frontendDir, r))
2132
2482
  );
2133
2483
  if (rootRel) {
2134
- const abs = import_node_path6.default.join(frontendDir, rootRel);
2135
- let src = import_node_fs6.default.readFileSync(abs, "utf8");
2484
+ const abs = import_node_path7.default.join(frontendDir, rootRel);
2485
+ let src = import_node_fs7.default.readFileSync(abs, "utf8");
2136
2486
  if (!src.includes("loadAllData")) {
2137
2487
  src = `import { loadAllData } from "${dataImport}";
2138
2488
  ` + src;
@@ -2146,17 +2496,17 @@ function injectSwitcher(frontendDir, warnings, dataImport = "@/data/site") {
2146
2496
  } else {
2147
2497
  warnings.push("n\xE3o consegui injetar o loader no __root (padr\xE3o n\xE3o encontrado).");
2148
2498
  }
2149
- import_node_fs6.default.writeFileSync(abs, src, "utf8");
2499
+ import_node_fs7.default.writeFileSync(abs, src, "utf8");
2150
2500
  }
2151
2501
  } else {
2152
2502
  warnings.push("__root n\xE3o encontrado \u2014 dados ao vivo n\xE3o ligados ao SSR.");
2153
2503
  }
2154
2504
  const headerRel = ["src/components/Header.tsx", "src/components/Header.jsx"].find(
2155
- (r) => import_node_fs6.default.existsSync(import_node_path6.default.join(frontendDir, r))
2505
+ (r) => import_node_fs7.default.existsSync(import_node_path7.default.join(frontendDir, r))
2156
2506
  );
2157
2507
  if (headerRel) {
2158
- const abs = import_node_path6.default.join(frontendDir, headerRel);
2159
- let src = import_node_fs6.default.readFileSync(abs, "utf8");
2508
+ const abs = import_node_path7.default.join(frontendDir, headerRel);
2509
+ let src = import_node_fs7.default.readFileSync(abs, "utf8");
2160
2510
  if (!src.includes("LanguageSwitcher")) {
2161
2511
  src = `import { LanguageSwitcher } from "@/components/LanguageSwitcher";
2162
2512
  ` + src;
@@ -2166,7 +2516,7 @@ function injectSwitcher(frontendDir, warnings, dataImport = "@/data/site") {
2166
2516
  src = src.replace(/<\/header>/, ` <LanguageSwitcher />
2167
2517
  </header>`);
2168
2518
  }
2169
- import_node_fs6.default.writeFileSync(abs, src, "utf8");
2519
+ import_node_fs7.default.writeFileSync(abs, src, "utf8");
2170
2520
  }
2171
2521
  } else {
2172
2522
  warnings.push("Header n\xE3o encontrado \u2014 adicione <LanguageSwitcher/> manualmente.");
@@ -2212,11 +2562,11 @@ REGRAS ESTRITAS:
2212
2562
 
2213
2563
  ARQUIVO:
2214
2564
  ${src.length > MAX_FILE_CHARS2 ? src.slice(0, MAX_FILE_CHARS2) : src}`;
2215
- const res = await fetch(OPENAI_URL2, {
2565
+ const res = await fetch(OPENAI_URL3, {
2216
2566
  method: "POST",
2217
2567
  headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
2218
2568
  body: JSON.stringify({
2219
- model: MODEL2,
2569
+ model: MODEL3,
2220
2570
  temperature: 0,
2221
2571
  messages: [
2222
2572
  { role: "system", content: "Voc\xEA religa componentes ao CMS trocando s\xF3 os textos do mapa por express\xF5es. Responde s\xF3 com o c\xF3digo." },
@@ -2290,10 +2640,10 @@ async function rewireComponents(strapi, opts, warnings) {
2290
2640
  (rel) => /\.(tsx|jsx)$/.test(rel) && !/__root|routeTree\.gen|\/api\/|LanguageSwitcher|PreviewBridge|\/ui\//.test(rel)
2291
2641
  );
2292
2642
  for (const rel of targets) {
2293
- const abs = import_node_path6.default.join(opts.frontendDir, rel);
2643
+ const abs = import_node_path7.default.join(opts.frontendDir, rel);
2294
2644
  let src;
2295
2645
  try {
2296
- src = import_node_fs6.default.readFileSync(abs, "utf8");
2646
+ src = import_node_fs7.default.readFileSync(abs, "utf8");
2297
2647
  } catch {
2298
2648
  continue;
2299
2649
  }
@@ -2312,8 +2662,8 @@ async function rewireComponents(strapi, opts, warnings) {
2312
2662
  continue;
2313
2663
  }
2314
2664
  const bak = abs + ".bak";
2315
- if (!import_node_fs6.default.existsSync(bak)) import_node_fs6.default.writeFileSync(bak, src, "utf8");
2316
- import_node_fs6.default.writeFileSync(abs, out, "utf8");
2665
+ if (!import_node_fs7.default.existsSync(bak)) import_node_fs7.default.writeFileSync(bak, src, "utf8");
2666
+ import_node_fs7.default.writeFileSync(abs, out, "utf8");
2317
2667
  rewired.push(rel);
2318
2668
  } catch (e) {
2319
2669
  warnings.push(`${rel}: rewire falhou (${e?.message ?? e}).`);
@@ -2352,19 +2702,19 @@ async function integrateFrontend(strapi, opts) {
2352
2702
  count: Array.isArray(sample[c.singularName]) ? sample[c.singularName].length : sample[c.singularName] ? 1 : 0
2353
2703
  }));
2354
2704
  for (const rel of dataFiles) {
2355
- const abs = import_node_path6.default.join(opts.frontendDir, rel);
2705
+ const abs = import_node_path7.default.join(opts.frontendDir, rel);
2356
2706
  try {
2357
- const original = import_node_fs6.default.readFileSync(abs, "utf8");
2707
+ const original = import_node_fs7.default.readFileSync(abs, "utf8");
2358
2708
  const bak = abs + ".bak";
2359
- if (!import_node_fs6.default.existsSync(bak)) import_node_fs6.default.writeFileSync(bak, original, "utf8");
2360
- const baseSrc = import_node_fs6.default.existsSync(bak) ? import_node_fs6.default.readFileSync(bak, "utf8") : original;
2709
+ if (!import_node_fs7.default.existsSync(bak)) import_node_fs7.default.writeFileSync(bak, original, "utf8");
2710
+ const baseSrc = import_node_fs7.default.existsSync(bak) ? import_node_fs7.default.readFileSync(bak, "utf8") : original;
2361
2711
  const mapper = await generateMapper(apiKey, baseSrc, sample, assetImportIds(baseSrc));
2362
2712
  if (!mapper || !/mapStrapiToData/.test(mapper)) {
2363
2713
  result.warnings.push(`${rel}: mapeador inv\xE1lido, pulado.`);
2364
2714
  continue;
2365
2715
  }
2366
2716
  const moduleSrc = buildLiveDataModule(baseSrc, mapper, ctMeta, locales, def);
2367
- import_node_fs6.default.writeFileSync(abs, moduleSrc, "utf8");
2717
+ import_node_fs7.default.writeFileSync(abs, moduleSrc, "utf8");
2368
2718
  result.filesRewritten.push(rel);
2369
2719
  } catch (e) {
2370
2720
  result.errors.push(`${rel}: ${e?.message ?? e}`);
@@ -2373,8 +2723,8 @@ async function integrateFrontend(strapi, opts) {
2373
2723
  if (result.filesRewritten.length > 0) {
2374
2724
  try {
2375
2725
  const rel0 = result.filesRewritten[0];
2376
- const dataDir = import_node_path6.default.dirname(import_node_path6.default.join(opts.frontendDir, rel0));
2377
- import_node_fs6.default.writeFileSync(import_node_path6.default.join(dataDir, "strapi-client.ts"), STRAPI_CLIENT_TS, "utf8");
2726
+ const dataDir = import_node_path7.default.dirname(import_node_path7.default.join(opts.frontendDir, rel0));
2727
+ import_node_fs7.default.writeFileSync(import_node_path7.default.join(dataDir, "strapi-client.ts"), STRAPI_CLIENT_TS, "utf8");
2378
2728
  const dataImport = "@/" + rel0.replace(/^src\//, "").replace(/\.(tsx?|jsx?)$/, "");
2379
2729
  injectSwitcher(opts.frontendDir, result.warnings, dataImport);
2380
2730
  await ensureClientDep(opts.frontendDir, result.warnings);
@@ -2398,12 +2748,12 @@ async function integrateFrontend(strapi, opts) {
2398
2748
 
2399
2749
  // server/src/controllers/frontend.ts
2400
2750
  var MANIFEST_NAME = "strapi.manifest.json";
2401
- function ensureInside2(base, target) {
2402
- const b = import_node_path7.default.resolve(base);
2403
- const t = import_node_path7.default.resolve(target);
2751
+ function ensureInside3(base, target) {
2752
+ const b = import_node_path8.default.resolve(base);
2753
+ const t = import_node_path8.default.resolve(target);
2404
2754
  if (t === b) return true;
2405
- const rel = import_node_path7.default.relative(b, t);
2406
- return !!rel && !rel.startsWith("..") && !import_node_path7.default.isAbsolute(rel);
2755
+ const rel = import_node_path8.default.relative(b, t);
2756
+ return !!rel && !rel.startsWith("..") && !import_node_path8.default.isAbsolute(rel);
2407
2757
  }
2408
2758
  function toKebab(input) {
2409
2759
  const s = (input || "frontend").toLowerCase().replace(/\.zip$/, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/^[^a-z]+/, "");
@@ -2431,13 +2781,13 @@ var frontend_default = {
2431
2781
  const originalName = file.originalFilename || file.name || "frontend";
2432
2782
  let zip;
2433
2783
  try {
2434
- zip = await import_jszip.default.loadAsync(import_node_fs7.default.readFileSync(filepath));
2784
+ zip = await import_jszip.default.loadAsync(import_node_fs8.default.readFileSync(filepath));
2435
2785
  } catch (e) {
2436
2786
  return ctx.badRequest(`Zip inv\xE1lido: ${e?.message ?? e}`);
2437
2787
  }
2438
2788
  const entryNames = Object.keys(zip.files);
2439
2789
  const manifestEntry = entryNames.find(
2440
- (p) => import_node_path7.default.basename(p) === MANIFEST_NAME && !zip.files[p].dir
2790
+ (p) => import_node_path8.default.basename(p) === MANIFEST_NAME && !zip.files[p].dir
2441
2791
  );
2442
2792
  let rootPrefix = "";
2443
2793
  if (manifestEntry) {
@@ -2450,27 +2800,27 @@ var frontend_default = {
2450
2800
  }
2451
2801
  const name = toKebab(originalName);
2452
2802
  const strapiAppDir = strapi.dirs.app.root;
2453
- const frontendDir = import_node_path7.default.resolve(strapiAppDir, "..", name);
2454
- if (import_node_fs7.default.existsSync(frontendDir) && import_node_fs7.default.readdirSync(frontendDir).length > 0) {
2803
+ const frontendDir = import_node_path8.default.resolve(strapiAppDir, "..", name);
2804
+ if (import_node_fs8.default.existsSync(frontendDir) && import_node_fs8.default.readdirSync(frontendDir).length > 0) {
2455
2805
  return ctx.badRequest(
2456
2806
  `A pasta de destino j\xE1 existe e n\xE3o est\xE1 vazia: ${frontendDir}. Renomeie o .zip ou remova a pasta.`
2457
2807
  );
2458
2808
  }
2459
2809
  try {
2460
- import_node_fs7.default.mkdirSync(frontendDir, { recursive: true });
2810
+ import_node_fs8.default.mkdirSync(frontendDir, { recursive: true });
2461
2811
  for (const entryName of entryNames) {
2462
2812
  const entry = zip.files[entryName];
2463
2813
  const rel = rootPrefix && entryName.startsWith(rootPrefix) ? entryName.slice(rootPrefix.length) : entryName;
2464
2814
  if (!rel) continue;
2465
- const dest = import_node_path7.default.join(frontendDir, rel);
2466
- if (!ensureInside2(frontendDir, dest)) {
2815
+ const dest = import_node_path8.default.join(frontendDir, rel);
2816
+ if (!ensureInside3(frontendDir, dest)) {
2467
2817
  throw new Error(`entrada perigosa no zip bloqueada: ${entryName}`);
2468
2818
  }
2469
2819
  if (entry.dir) {
2470
- import_node_fs7.default.mkdirSync(dest, { recursive: true });
2820
+ import_node_fs8.default.mkdirSync(dest, { recursive: true });
2471
2821
  } else {
2472
- import_node_fs7.default.mkdirSync(import_node_path7.default.dirname(dest), { recursive: true });
2473
- import_node_fs7.default.writeFileSync(dest, await entry.async("nodebuffer"));
2822
+ import_node_fs8.default.mkdirSync(import_node_path8.default.dirname(dest), { recursive: true });
2823
+ import_node_fs8.default.writeFileSync(dest, await entry.async("nodebuffer"));
2474
2824
  }
2475
2825
  }
2476
2826
  } catch (e) {
@@ -2498,16 +2848,16 @@ var frontend_default = {
2498
2848
  const rawManifest = body.manifest;
2499
2849
  const frontendDir = body.frontendDir;
2500
2850
  if (!rawManifest) return ctx.badRequest('Envie o "manifest".');
2501
- if (!frontendDir || !import_node_path7.default.isAbsolute(frontendDir)) {
2851
+ if (!frontendDir || !import_node_path8.default.isAbsolute(frontendDir)) {
2502
2852
  return ctx.badRequest("frontendDir ausente ou inv\xE1lido.");
2503
2853
  }
2504
2854
  const strapiAppDir = strapi.dirs.app.root;
2505
2855
  const apiRoot = strapi.dirs.app.api;
2506
- const parent = import_node_path7.default.resolve(strapiAppDir, "..");
2507
- if (!ensureInside2(parent, frontendDir) || frontendDir === parent) {
2856
+ const parent = import_node_path8.default.resolve(strapiAppDir, "..");
2857
+ if (!ensureInside3(parent, frontendDir) || frontendDir === parent) {
2508
2858
  return ctx.badRequest("frontendDir fora da pasta permitida.");
2509
2859
  }
2510
- if (!import_node_fs7.default.existsSync(frontendDir)) {
2860
+ if (!import_node_fs8.default.existsSync(frontendDir)) {
2511
2861
  return ctx.badRequest("frontendDir n\xE3o existe (rode a an\xE1lise primeiro).");
2512
2862
  }
2513
2863
  const v = validateManifest(rawManifest);
@@ -2515,8 +2865,8 @@ var frontend_default = {
2515
2865
  return ctx.badRequest({ message: "Manifest inv\xE1lido", errors: v.errors });
2516
2866
  }
2517
2867
  try {
2518
- import_node_fs7.default.writeFileSync(
2519
- import_node_path7.default.join(frontendDir, MANIFEST_NAME),
2868
+ import_node_fs8.default.writeFileSync(
2869
+ import_node_path8.default.join(frontendDir, MANIFEST_NAME),
2520
2870
  JSON.stringify(v.data, null, 2),
2521
2871
  "utf8"
2522
2872
  );
@@ -2578,8 +2928,8 @@ var frontend_default = {
2578
2928
  if (!dir || !url) {
2579
2929
  return ctx.badRequest("Nenhum frontend provisionado para rodar.");
2580
2930
  }
2581
- const parent = import_node_path7.default.resolve(strapi.dirs.app.root, "..");
2582
- if (!ensureInside2(parent, dir) || !import_node_fs7.default.existsSync(dir)) {
2931
+ const parent = import_node_path8.default.resolve(strapi.dirs.app.root, "..");
2932
+ if (!ensureInside3(parent, dir) || !import_node_fs8.default.existsSync(dir)) {
2583
2933
  return ctx.badRequest("Pasta do frontend inv\xE1lida.");
2584
2934
  }
2585
2935
  ctx.body = await startFrontend(strapi, { dir, url });
@@ -2603,19 +2953,48 @@ var frontend_default = {
2603
2953
  frontendDir = st.done?.frontendDir || "";
2604
2954
  }
2605
2955
  if (!frontendDir) return ctx.badRequest("Nenhum frontend provisionado.");
2606
- const parent = import_node_path7.default.resolve(strapi.dirs.app.root, "..");
2607
- if (!ensureInside2(parent, frontendDir) || !import_node_fs7.default.existsSync(frontendDir)) {
2956
+ const parent = import_node_path8.default.resolve(strapi.dirs.app.root, "..");
2957
+ if (!ensureInside3(parent, frontendDir) || !import_node_fs8.default.existsSync(frontendDir)) {
2608
2958
  return ctx.badRequest("Pasta do frontend inv\xE1lida.");
2609
2959
  }
2610
2960
  let manifest;
2611
2961
  try {
2612
- manifest = JSON.parse(import_node_fs7.default.readFileSync(import_node_path7.default.join(frontendDir, MANIFEST_NAME), "utf8"));
2962
+ manifest = JSON.parse(import_node_fs8.default.readFileSync(import_node_path8.default.join(frontendDir, MANIFEST_NAME), "utf8"));
2613
2963
  } catch {
2614
2964
  return ctx.badRequest("Manifest do projeto n\xE3o encontrado (rode a provis\xE3o primeiro).");
2615
2965
  }
2616
2966
  const v = validateManifest(manifest);
2617
2967
  if (!v.ok) return ctx.badRequest({ message: "Manifest inv\xE1lido", errors: v.errors });
2618
2968
  ctx.body = await integrateFrontend(strapi, { frontendDir, manifest: v.data });
2969
+ },
2970
+ /**
2971
+ * Religa o frontend à Strapi por FETCH AO VIVO: gera a camada de dados (REST,
2972
+ * flat) e religa os componentes (com .bak + fallback + sanidade). Só escreve no
2973
+ * frontend, nunca na Strapi. Usa o último provisionado por padrão.
2974
+ */
2975
+ async wire(ctx) {
2976
+ const strapi = ctx.strapi ?? global.strapi;
2977
+ if (!devOnly(ctx)) return;
2978
+ const body = ctx.request.body || {};
2979
+ let frontendDir = body.frontendDir;
2980
+ if (!frontendDir) {
2981
+ const st = getProvisionStatus(strapi.dirs.app.root);
2982
+ frontendDir = st.done?.frontendDir || "";
2983
+ }
2984
+ if (!frontendDir) return ctx.badRequest("Nenhum frontend provisionado.");
2985
+ const parent = import_node_path8.default.resolve(strapi.dirs.app.root, "..");
2986
+ if (!ensureInside3(parent, frontendDir) || !import_node_fs8.default.existsSync(frontendDir)) {
2987
+ return ctx.badRequest("Pasta do frontend inv\xE1lida.");
2988
+ }
2989
+ let manifest;
2990
+ try {
2991
+ manifest = JSON.parse(import_node_fs8.default.readFileSync(import_node_path8.default.join(frontendDir, MANIFEST_NAME), "utf8"));
2992
+ } catch {
2993
+ return ctx.badRequest("Manifest do projeto n\xE3o encontrado (rode a provis\xE3o primeiro).");
2994
+ }
2995
+ const v = validateManifest(manifest);
2996
+ if (!v.ok) return ctx.badRequest({ message: "Manifest inv\xE1lido", errors: v.errors });
2997
+ ctx.body = await wireFrontend(strapi, { frontendDir, manifest: v.data });
2619
2998
  }
2620
2999
  };
2621
3000
 
@@ -2699,8 +3078,8 @@ var McpClient = class {
2699
3078
  };
2700
3079
 
2701
3080
  // server/src/provision/translate.ts
2702
- var OPENAI_URL3 = "https://api.openai.com/v1/chat/completions";
2703
- var MODEL3 = process.env.OPENAI_CHAT_MODEL || "gpt-4o";
3081
+ var OPENAI_URL4 = "https://api.openai.com/v1/chat/completions";
3082
+ var MODEL4 = process.env.OPENAI_CHAT_MODEL || "gpt-4o";
2704
3083
  var approxTokens = (s) => Math.ceil((s || "").length / 4);
2705
3084
  var MAX_CHUNK_TOKENS = 1200;
2706
3085
  var splitParagraphs = (text) => text.split(/\n{2,}/);
@@ -2745,7 +3124,7 @@ function splitForTranslation(value, _type) {
2745
3124
  async function translateChunk(apiKey, text, sourceLang, targetLang) {
2746
3125
  if (!text || !text.trim()) return text;
2747
3126
  const body = {
2748
- model: MODEL3,
3127
+ model: MODEL4,
2749
3128
  temperature: 0,
2750
3129
  max_tokens: Math.min(4e3, approxTokens(text) * 3 + 256),
2751
3130
  messages: [
@@ -2756,7 +3135,7 @@ async function translateChunk(apiKey, text, sourceLang, targetLang) {
2756
3135
  { role: "user", content: text }
2757
3136
  ]
2758
3137
  };
2759
- const res = await fetch(OPENAI_URL3, {
3138
+ const res = await fetch(OPENAI_URL4, {
2760
3139
  method: "POST",
2761
3140
  headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
2762
3141
  body: JSON.stringify(body)
@@ -2823,22 +3202,22 @@ function createContentTools(strapi) {
2823
3202
  for (const [name, a] of Object.entries(attributes)) {
2824
3203
  const v = node[name];
2825
3204
  if (v == null) continue;
2826
- const path9 = [...basePath, name];
3205
+ const path10 = [...basePath, name];
2827
3206
  if (TEXTUAL.includes(a.type)) {
2828
3207
  if (typeof v === "string" && v.toLowerCase().includes(needle)) {
2829
- collect(path9, name, v);
3208
+ collect(path10, name, v);
2830
3209
  }
2831
3210
  } else if (a.type === "component" && a.component) {
2832
3211
  const sub = attrsOf(a.component);
2833
3212
  if (a.repeatable && Array.isArray(v)) {
2834
- v.forEach((item, i) => walkFind(item, sub, [...path9, i], needle, collect));
3213
+ v.forEach((item, i) => walkFind(item, sub, [...path10, i], needle, collect));
2835
3214
  } else {
2836
- walkFind(v, sub, path9, needle, collect);
3215
+ walkFind(v, sub, path10, needle, collect);
2837
3216
  }
2838
3217
  } else if (a.type === "dynamiczone" && Array.isArray(v)) {
2839
3218
  v.forEach((item, i) => {
2840
3219
  if (item?.__component) {
2841
- walkFind(item, attrsOf(item.__component), [...path9, i], needle, collect);
3220
+ walkFind(item, attrsOf(item.__component), [...path10, i], needle, collect);
2842
3221
  }
2843
3222
  });
2844
3223
  }
@@ -2863,12 +3242,12 @@ function createContentTools(strapi) {
2863
3242
  }
2864
3243
  const dp = hasDraftAndPublish(ct.uid);
2865
3244
  for (const e of entries) {
2866
- walkFind(e, attributes, [], needle, (path9, campo, valor) => {
3245
+ walkFind(e, attributes, [], needle, (path10, campo, valor) => {
2867
3246
  matches.push({
2868
3247
  uid: ct.uid,
2869
3248
  tipo: ct.info?.displayName || ct.uid,
2870
3249
  documentId: e.documentId,
2871
- path: path9,
3250
+ path: path10,
2872
3251
  campo,
2873
3252
  valor_atual: valor.length > 300 ? valor.slice(0, 300) + "\u2026" : valor,
2874
3253
  // draftAndPublish=false → não há rascunho; a edição já é o conteúdo
@@ -2916,8 +3295,8 @@ function createContentTools(strapi) {
2916
3295
  }
2917
3296
  return value;
2918
3297
  };
2919
- const editarCampo = async ({ uid, documentId, path: path9, campo, novo_valor, locale }) => {
2920
- const p = Array.isArray(path9) && path9.length ? path9 : campo ? [campo] : null;
3298
+ const editarCampo = async ({ uid, documentId, path: path10, campo, novo_valor, locale }) => {
3299
+ const p = Array.isArray(path10) && path10.length ? path10 : campo ? [campo] : null;
2921
3300
  if (!p) return { erro: 'informe "path" (array) ou "campo"' };
2922
3301
  const attributes = strapi.contentTypes?.[uid]?.attributes || {};
2923
3302
  const topAttr = p[0];
@@ -3219,29 +3598,29 @@ var openAiToolSpecs = [
3219
3598
  ];
3220
3599
 
3221
3600
  // server/src/provision/enable-i18n.ts
3222
- var import_node_fs8 = __toESM(require("node:fs"));
3223
- var import_node_path8 = __toESM(require("node:path"));
3601
+ var import_node_fs9 = __toESM(require("node:fs"));
3602
+ var import_node_path9 = __toESM(require("node:path"));
3224
3603
  var LOCALIZABLE = ["string", "text", "richtext", "component", "dynamiczone"];
3225
3604
  var isDev2 = () => process.env.NODE_ENV === "development";
3226
3605
  function schemaPathFor(apiRoot, uid) {
3227
3606
  const m = /^api::([^.]+)\.([^.]+)$/.exec(uid);
3228
3607
  if (!m) return null;
3229
3608
  const [, api, ct] = m;
3230
- return import_node_path8.default.join(apiRoot, api, "content-types", ct, "schema.json");
3609
+ return import_node_path9.default.join(apiRoot, api, "content-types", ct, "schema.json");
3231
3610
  }
3232
3611
  function listAllUids(apiRoot) {
3233
3612
  const out = [];
3234
3613
  let apis = [];
3235
3614
  try {
3236
- apis = import_node_fs8.default.readdirSync(apiRoot);
3615
+ apis = import_node_fs9.default.readdirSync(apiRoot);
3237
3616
  } catch {
3238
3617
  return out;
3239
3618
  }
3240
3619
  for (const api of apis) {
3241
- const ctDir = import_node_path8.default.join(apiRoot, api, "content-types");
3242
- if (!import_node_fs8.default.existsSync(ctDir)) continue;
3243
- for (const ct of import_node_fs8.default.readdirSync(ctDir)) {
3244
- if (import_node_fs8.default.existsSync(import_node_path8.default.join(ctDir, ct, "schema.json"))) out.push(`api::${api}.${ct}`);
3620
+ const ctDir = import_node_path9.default.join(apiRoot, api, "content-types");
3621
+ if (!import_node_fs9.default.existsSync(ctDir)) continue;
3622
+ for (const ct of import_node_fs9.default.readdirSync(ctDir)) {
3623
+ if (import_node_fs9.default.existsSync(import_node_path9.default.join(ctDir, ct, "schema.json"))) out.push(`api::${api}.${ct}`);
3245
3624
  }
3246
3625
  }
3247
3626
  return out;
@@ -3251,10 +3630,10 @@ var withLocalized = (obj) => ({
3251
3630
  i18n: { ...(obj || {}).i18n || {}, localized: true }
3252
3631
  });
3253
3632
  function patchOne(file, campos) {
3254
- if (!import_node_fs8.default.existsSync(file)) return { erro: `schema.json n\xE3o encontrado (${file})` };
3633
+ if (!import_node_fs9.default.existsSync(file)) return { erro: `schema.json n\xE3o encontrado (${file})` };
3255
3634
  let schema;
3256
3635
  try {
3257
- schema = JSON.parse(import_node_fs8.default.readFileSync(file, "utf8"));
3636
+ schema = JSON.parse(import_node_fs9.default.readFileSync(file, "utf8"));
3258
3637
  } catch (e) {
3259
3638
  return { erro: `schema.json ileg\xEDvel: ${e?.message ?? e}` };
3260
3639
  }
@@ -3268,7 +3647,7 @@ function patchOne(file, campos) {
3268
3647
  changed.push(name);
3269
3648
  }
3270
3649
  try {
3271
- import_node_fs8.default.writeFileSync(file, JSON.stringify(schema, null, 2) + "\n", "utf8");
3650
+ import_node_fs9.default.writeFileSync(file, JSON.stringify(schema, null, 2) + "\n", "utf8");
3272
3651
  } catch (e) {
3273
3652
  return { erro: `falha ao gravar schema.json: ${e?.message ?? e}` };
3274
3653
  }
@@ -3279,8 +3658,8 @@ function enableI18n(opts) {
3279
3658
  if (!allowOutsideDev && !isDev2()) {
3280
3659
  return { erro: "habilitar i18n s\xF3 \xE9 permitido em desenvolvimento (NODE_ENV=development)." };
3281
3660
  }
3282
- const srcDir = strapi?.dirs?.app?.src || import_node_path8.default.join(process.cwd(), "src");
3283
- const apiRoot = import_node_path8.default.join(srcDir, "api");
3661
+ const srcDir = strapi?.dirs?.app?.src || import_node_path9.default.join(process.cwd(), "src");
3662
+ const apiRoot = import_node_path9.default.join(srcDir, "api");
3284
3663
  if (!uid || uid === "*") {
3285
3664
  const uids = listAllUids(apiRoot);
3286
3665
  if (!uids.length) return { erro: `nenhuma content-type encontrada em ${apiRoot}` };
@@ -3303,9 +3682,9 @@ function enableI18n(opts) {
3303
3682
  }
3304
3683
 
3305
3684
  // server/src/services/chat.ts
3306
- var MODEL4 = process.env.OPENAI_CHAT_MODEL || "gpt-4o";
3685
+ var MODEL5 = process.env.OPENAI_CHAT_MODEL || "gpt-4o";
3307
3686
  var MAX_TURNS = 10;
3308
- var OPENAI_URL4 = "https://api.openai.com/v1/chat/completions";
3687
+ var OPENAI_URL5 = "https://api.openai.com/v1/chat/completions";
3309
3688
  var SYSTEM = {
3310
3689
  pt: `Voc\xEA \xE9 um assistente embutido no admin do Strapi 5 deste projeto. Voc\xEA N\xC3O \xE9 s\xF3 um guia: voc\xEA consegue EDITAR e PUBLICAR conte\xFAdo de verdade atrav\xE9s das ferramentas.
3311
3690
 
@@ -3469,12 +3848,12 @@ PUBLISH POLICY: DRAFT MODE (auto-publish OFF). Do NOT call publicar unless the u
3469
3848
  convo.push({ role: m.role, content: m.content });
3470
3849
  }
3471
3850
  });
3472
- const callOpenAI = async (body) => {
3851
+ const callOpenAI2 = async (body) => {
3473
3852
  const ctrl = new AbortController();
3474
3853
  const timer = setTimeout(() => ctrl.abort(), 6e4);
3475
3854
  let res;
3476
3855
  try {
3477
- res = await fetch(OPENAI_URL4, {
3856
+ res = await fetch(OPENAI_URL5, {
3478
3857
  method: "POST",
3479
3858
  headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
3480
3859
  body: JSON.stringify(body),
@@ -3491,8 +3870,8 @@ PUBLISH POLICY: DRAFT MODE (auto-publish OFF). Do NOT call publicar unless the u
3491
3870
  };
3492
3871
  let didWrite = false;
3493
3872
  for (let turn = 0; turn < MAX_TURNS; turn++) {
3494
- const data = await callOpenAI({
3495
- model: MODEL4,
3873
+ const data = await callOpenAI2({
3874
+ model: MODEL5,
3496
3875
  max_tokens: 2048,
3497
3876
  messages: convo,
3498
3877
  ...tools2.length > 0 ? { tools: tools2, tool_choice: "auto" } : {}
@@ -3536,7 +3915,7 @@ PUBLISH POLICY: DRAFT MODE (auto-publish OFF). Do NOT call publicar unless the u
3536
3915
  const text = (typeof msg.content === "string" ? msg.content : "").trim();
3537
3916
  return {
3538
3917
  reply: text || "(sem resposta)",
3539
- model: MODEL4,
3918
+ model: MODEL5,
3540
3919
  lang: language,
3541
3920
  didWrite,
3542
3921
  toolsAvailable: tools2.length
@@ -3544,7 +3923,7 @@ PUBLISH POLICY: DRAFT MODE (auto-publish OFF). Do NOT call publicar unless the u
3544
3923
  }
3545
3924
  return {
3546
3925
  reply: language === "en" ? "(agent turn limit reached)" : "(limite de turnos do agente atingido)",
3547
- model: MODEL4,
3926
+ model: MODEL5,
3548
3927
  lang: language,
3549
3928
  didWrite,
3550
3929
  toolsAvailable: tools2.length
@@ -3645,6 +4024,12 @@ var routes_default = {
3645
4024
  path: "/frontend/integrate",
3646
4025
  handler: "frontend.integrate",
3647
4026
  config: { policies: [] }
4027
+ },
4028
+ {
4029
+ method: "POST",
4030
+ path: "/frontend/wire",
4031
+ handler: "frontend.wire",
4032
+ config: { policies: [] }
3648
4033
  }
3649
4034
  ]
3650
4035
  }