beplus-mcp 0.18.0 → 0.19.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.
Files changed (3) hide show
  1. package/README.md +1 -0
  2. package/dist/index.js +520 -61
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -82,6 +82,7 @@ Reinicie o cliente. Rode a tool **`whoami`** para confirmar o vínculo.
82
82
  |------|-----------|
83
83
  | `generate_image` | Gera imagem (nano-banana / gpt-image-2). Bloqueante ~90s; retorna URL + imagem inline. Suporta refs, tamanho/qualidade, e extras do gpt-image-2 (background, moderation, output_format/compression). |
84
84
  | `generate_video` | Gera vídeo (Seedance / Kling). Aguarda ~120s; senão devolve o id pra `check_generation`. Suporta first/last frame, refs de imagem/vídeo/áudio, prompt negativo, áudio gerado, mode e motion-control. |
85
+ | `upscale` | Melhora a resolução de uma imagem ou vídeo que já existe (Topaz). Detecta o tipo pelo arquivo. Imagem até 4x (~20s, a partir de 6💎); vídeo até 30s e saída até 1080p, custa por bloco de 10s e **descarta o áudio**. Mede o arquivo sozinho para o preço sair certo. ⚠️ Vídeo é restrito à equipe. |
85
86
  | `generate_audio` | Sintetiza fala (Gemini TTS, ~30 vozes, multi-locutor). Síncrono. |
86
87
  | `generate_music` | Gera música completa (Suno v5.5 / v4.5) — modo descrição ou letra própria, tags, instrumental, vocal_gender, controles criativos. Async. |
87
88
  | `check_generation` | Status de uma geração async por id. |
package/dist/index.js CHANGED
@@ -515,6 +515,24 @@ var VIDEO_MODELS = [
515
515
  "kwaivgi/kling-v3-video",
516
516
  "kwaivgi/kling-v3-omni-video"
517
517
  ];
518
+ var UPSCALE_IMAGE_VARIANTS = [
519
+ "Wonder 3.5",
520
+ "Wonder 3",
521
+ "Wonder 2",
522
+ "Wonder",
523
+ "Recover 3",
524
+ "Standard MAX",
525
+ "Redefine",
526
+ "Recovery V2",
527
+ "Recovery"
528
+ ];
529
+ var UPSCALE_VIDEO_VARIANTS = [
530
+ "Starlight Precise 2.6",
531
+ "Starlight HQ",
532
+ "Starlight Mini",
533
+ "Starlight Sharp",
534
+ "Starlight Fast 2"
535
+ ];
518
536
  var MUSIC_MODELS = [
519
537
  "suno/v5-5",
520
538
  "suno/v4-5"
@@ -865,11 +883,11 @@ ${BUDGET_BLOCKED_GUIDANCE}`);
865
883
  `${args.model} s\xF3 suporta 480p ou 720p (voc\xEA pediu ${args.resolution}). Use seedance-2.0 (standard) para 1080p/4K.`
866
884
  );
867
885
  }
868
- if (args.model === "bytedance/seedance-2.5" && args.aspect_ratio) {
869
- const SEEDANCE_25_RATIOS = ["1:1", "16:9", "9:16", "4:3", "3:4", "21:9", "adaptive"];
870
- if (!SEEDANCE_25_RATIOS.includes(args.aspect_ratio)) {
886
+ if (args.model?.startsWith("bytedance/seedance-") && args.aspect_ratio) {
887
+ const SEEDANCE_RATIOS = ["1:1", "16:9", "9:16", "4:3", "3:4", "21:9", "adaptive"];
888
+ if (!SEEDANCE_RATIOS.includes(args.aspect_ratio)) {
871
889
  return errorResult(
872
- `bytedance/seedance-2.5 n\xE3o aceita a propor\xE7\xE3o ${args.aspect_ratio}. Use uma destas: ${SEEDANCE_25_RATIOS.join(", ")}.`
890
+ `${args.model} n\xE3o aceita a propor\xE7\xE3o ${args.aspect_ratio}. Use uma destas: ${SEEDANCE_RATIOS.join(", ")}.`
873
891
  );
874
892
  }
875
893
  }
@@ -996,7 +1014,7 @@ ${list}${flagsLine(status)}` }, ...blocks] };
996
1014
  return textResult(`Nenhuma gera\xE7\xE3o${scope} encontrada.`);
997
1015
  }
998
1016
  const lines = generations.map((g, i) => {
999
- const type = g.generation_type || "image";
1017
+ const type = g.model?.startsWith("topaz/upscale") ? "upscale" : g.generation_type || "image";
1000
1018
  const when = g.created_at ? new Date(g.created_at).toISOString().slice(0, 10) : "\u2014";
1001
1019
  const prompt = (g.prompt || "").replace(/\s+/g, " ").slice(0, 80);
1002
1020
  const url = firstOutputUrl(g.output);
@@ -1098,8 +1116,413 @@ ${list}${flagsLine(status)}` }, ...blocks] };
1098
1116
  );
1099
1117
  }
1100
1118
 
1101
- // src/tools/audio.ts
1119
+ // src/tools/upscale.ts
1102
1120
  import { z as z2 } from "zod";
1121
+
1122
+ // src/probe.ts
1123
+ import { readFile as readFile2, open } from "fs/promises";
1124
+ import { extname as extname2, isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
1125
+ import { homedir as homedir2 } from "os";
1126
+ import { fileURLToPath as fileURLToPath2 } from "url";
1127
+ var IMAGE_EXT = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".webp"]);
1128
+ var VIDEO_EXT = /* @__PURE__ */ new Set([".mp4", ".mov", ".m4v"]);
1129
+ var HEADER_BYTES = 128 * 1024;
1130
+ var MAX_SCAN_BYTES = 8 * 1024 * 1024;
1131
+ var FETCH_TIMEOUT_MS = 3e4;
1132
+ function isRemote(ref) {
1133
+ return /^https?:\/\//i.test(ref);
1134
+ }
1135
+ function toLocalPath2(ref) {
1136
+ if (ref.startsWith("file://")) return fileURLToPath2(ref);
1137
+ if (ref === "~") return homedir2();
1138
+ if (ref.startsWith("~/")) return resolve2(homedir2(), ref.slice(2));
1139
+ return isAbsolute2(ref) ? ref : resolve2(process.cwd(), ref);
1140
+ }
1141
+ function kindFromExt(ref) {
1142
+ const ext = extname2(new URL(ref, "file:///").pathname || ref).toLowerCase();
1143
+ if (IMAGE_EXT.has(ext)) return "image";
1144
+ if (VIDEO_EXT.has(ext)) return "video";
1145
+ return null;
1146
+ }
1147
+ async function readHead(ref, bytes) {
1148
+ if (!isRemote(ref)) {
1149
+ const fh = await open(toLocalPath2(ref), "r");
1150
+ try {
1151
+ const buf = Buffer.alloc(bytes);
1152
+ const { bytesRead } = await fh.read(buf, 0, bytes, 0);
1153
+ return buf.subarray(0, bytesRead);
1154
+ } finally {
1155
+ await fh.close();
1156
+ }
1157
+ }
1158
+ const ctrl = new AbortController();
1159
+ const timer = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS);
1160
+ try {
1161
+ const res = await fetch(ref, {
1162
+ headers: { Range: `bytes=0-${bytes - 1}` },
1163
+ signal: ctrl.signal
1164
+ });
1165
+ if (!res.ok && res.status !== 206) throw new Error(`HTTP ${res.status}`);
1166
+ if (!res.body) return Buffer.from(await res.arrayBuffer());
1167
+ const chunks = [];
1168
+ let total = 0;
1169
+ const reader = res.body.getReader();
1170
+ while (total < bytes) {
1171
+ const { done, value } = await reader.read();
1172
+ if (done || !value) break;
1173
+ chunks.push(Buffer.from(value));
1174
+ total += value.length;
1175
+ }
1176
+ void reader.cancel().catch(() => {
1177
+ });
1178
+ return Buffer.concat(chunks).subarray(0, bytes);
1179
+ } finally {
1180
+ clearTimeout(timer);
1181
+ }
1182
+ }
1183
+ function probePng(b) {
1184
+ if (b.length < 24) return null;
1185
+ if (b.readUInt32BE(0) !== 2303741511) return null;
1186
+ if (b.toString("ascii", 12, 16) !== "IHDR") return null;
1187
+ return { width: b.readUInt32BE(16), height: b.readUInt32BE(20) };
1188
+ }
1189
+ function probeJpeg(b) {
1190
+ if (b.length < 4 || b[0] !== 255 || b[1] !== 216) return null;
1191
+ let i = 2;
1192
+ while (i + 9 < b.length) {
1193
+ if (b[i] !== 255) {
1194
+ i += 1;
1195
+ continue;
1196
+ }
1197
+ const marker = b[i + 1];
1198
+ if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
1199
+ return { height: b.readUInt16BE(i + 5), width: b.readUInt16BE(i + 7) };
1200
+ }
1201
+ if (marker === 216 || marker === 1 || marker >= 208 && marker <= 215) {
1202
+ i += 2;
1203
+ continue;
1204
+ }
1205
+ i += 2 + b.readUInt16BE(i + 2);
1206
+ }
1207
+ return null;
1208
+ }
1209
+ function probeWebp(b) {
1210
+ if (b.length < 30) return null;
1211
+ if (b.toString("ascii", 0, 4) !== "RIFF" || b.toString("ascii", 8, 12) !== "WEBP") return null;
1212
+ const fmt2 = b.toString("ascii", 12, 16);
1213
+ if (fmt2 === "VP8 ") {
1214
+ return { width: b.readUInt16LE(26) & 16383, height: b.readUInt16LE(28) & 16383 };
1215
+ }
1216
+ if (fmt2 === "VP8L") {
1217
+ const bits = b.readUInt32LE(21);
1218
+ return { width: (bits & 16383) + 1, height: (bits >> 14 & 16383) + 1 };
1219
+ }
1220
+ if (fmt2 === "VP8X") {
1221
+ const w = b[24] | b[25] << 8 | b[26] << 16;
1222
+ const h = b[27] | b[28] << 8 | b[29] << 16;
1223
+ return { width: w + 1, height: h + 1 };
1224
+ }
1225
+ return null;
1226
+ }
1227
+ function probeMp4(b) {
1228
+ let duration;
1229
+ let width = 0;
1230
+ let height = 0;
1231
+ let frames = 0;
1232
+ let mediaDuration;
1233
+ const walk = (start, end, depth) => {
1234
+ let i = start;
1235
+ while (i + 8 <= end && depth < 8) {
1236
+ let size = b.readUInt32BE(i);
1237
+ const type = b.toString("ascii", i + 4, i + 8);
1238
+ let head = 8;
1239
+ if (size === 1) {
1240
+ if (i + 16 > end) return;
1241
+ size = Number(b.readBigUInt64BE(i + 8));
1242
+ head = 16;
1243
+ }
1244
+ if (size < head || i + size > end) {
1245
+ return;
1246
+ }
1247
+ if (type === "moov" || type === "trak" || type === "mdia" || type === "minf" || type === "stbl") {
1248
+ walk(i + head, i + size, depth + 1);
1249
+ } else if (type === "mvhd") {
1250
+ const version = b[i + head];
1251
+ if (version === 1) {
1252
+ const timescale = b.readUInt32BE(i + head + 20);
1253
+ const dur2 = Number(b.readBigUInt64BE(i + head + 24));
1254
+ if (timescale) duration = dur2 / timescale;
1255
+ } else {
1256
+ const timescale = b.readUInt32BE(i + head + 12);
1257
+ const dur2 = b.readUInt32BE(i + head + 16);
1258
+ if (timescale) duration = dur2 / timescale;
1259
+ }
1260
+ } else if (type === "tkhd") {
1261
+ const version = b[i + head];
1262
+ const matrixAt = i + head + (version === 1 ? 52 : 40);
1263
+ const w = b.readUInt32BE(matrixAt + 36) / 65536;
1264
+ const h = b.readUInt32BE(matrixAt + 40) / 65536;
1265
+ if (w > 0 && h > 0) {
1266
+ const a = b.readInt32BE(matrixAt) / 65536;
1267
+ const d = b.readInt32BE(matrixAt + 16) / 65536;
1268
+ const rotated = Math.abs(a) < 0.01 && Math.abs(d) < 0.01;
1269
+ const tw = Math.round(rotated ? h : w);
1270
+ const th = Math.round(rotated ? w : h);
1271
+ if (tw * th > width * height) {
1272
+ width = tw;
1273
+ height = th;
1274
+ }
1275
+ }
1276
+ } else if (type === "mdhd") {
1277
+ const version = b[i + head];
1278
+ if (version === 1) {
1279
+ const ts = b.readUInt32BE(i + head + 20);
1280
+ const dur2 = Number(b.readBigUInt64BE(i + head + 24));
1281
+ if (ts) mediaDuration = dur2 / ts;
1282
+ } else {
1283
+ const ts = b.readUInt32BE(i + head + 12);
1284
+ const dur2 = b.readUInt32BE(i + head + 16);
1285
+ if (ts) mediaDuration = dur2 / ts;
1286
+ }
1287
+ } else if (type === "stsz") {
1288
+ const count = b.readUInt32BE(i + head + 8);
1289
+ if (count > frames) frames = count;
1290
+ }
1291
+ i += size;
1292
+ }
1293
+ };
1294
+ walk(0, b.length, 0);
1295
+ if (!width || !height) return null;
1296
+ const dur = duration ?? mediaDuration;
1297
+ const fps = dur && frames ? Math.round(frames / dur * 100) / 100 : void 0;
1298
+ return { width, height, duration: dur, fps };
1299
+ }
1300
+ var ProbeError = class extends Error {
1301
+ };
1302
+ async function probeMedia(ref) {
1303
+ const kind = kindFromExt(ref);
1304
+ if (!kind) {
1305
+ throw new ProbeError(
1306
+ "Formato n\xE3o reconhecido pela extens\xE3o. Aceito: png, jpg, jpeg, webp, mp4, mov, m4v."
1307
+ );
1308
+ }
1309
+ let head;
1310
+ try {
1311
+ head = await readHead(ref, HEADER_BYTES);
1312
+ } catch (e) {
1313
+ throw new ProbeError(`N\xE3o consegui ler o arquivo: ${e instanceof Error ? e.message : String(e)}`);
1314
+ }
1315
+ if (!head.length) throw new ProbeError("Arquivo vazio.");
1316
+ if (kind === "image") {
1317
+ const dim = probePng(head) ?? probeJpeg(head) ?? probeWebp(head);
1318
+ if (!dim) throw new ProbeError("N\xE3o consegui ler as dimens\xF5es da imagem pelo cabe\xE7alho.");
1319
+ return { kind, width: dim.width, height: dim.height };
1320
+ }
1321
+ let info = probeMp4(head);
1322
+ if (!info && !isRemote(ref)) {
1323
+ try {
1324
+ const whole = await readFile2(toLocalPath2(ref));
1325
+ info = probeMp4(whole.subarray(0, Math.min(whole.length, MAX_SCAN_BYTES))) ?? probeMp4(whole.subarray(Math.max(0, whole.length - MAX_SCAN_BYTES)));
1326
+ } catch {
1327
+ }
1328
+ }
1329
+ if (!info) {
1330
+ throw new ProbeError(
1331
+ "N\xE3o consegui ler as dimens\xF5es do v\xEDdeo pelo cabe\xE7alho (o \xEDndice pode estar no fim do arquivo)."
1332
+ );
1333
+ }
1334
+ return { kind, width: info.width, height: info.height, duration: info.duration, fps: info.fps };
1335
+ }
1336
+
1337
+ // src/tools/upscale.ts
1338
+ var IMAGE_MODEL = "topaz/upscale/image/generative";
1339
+ var VIDEO_MODEL = "topaz/upscale/video/generative";
1340
+ var MAX_VIDEO_SECONDS = 30;
1341
+ var MAX_OUTPUT_HEIGHT = 1080;
1342
+ var IMAGE_BUDGET_MS2 = 9e4;
1343
+ var VIDEO_BUDGET_MS2 = 24e4;
1344
+ var DEFAULT_IMAGE_VARIANT = "Wonder 3";
1345
+ var DEFAULT_VIDEO_VARIANT = "Starlight Precise 2.6";
1346
+ var SOFTNESS_ONLY = "Starlight Precise 2.6";
1347
+ function registerUpscaleTools(server, client, cfg) {
1348
+ server.registerTool(
1349
+ "upscale",
1350
+ {
1351
+ title: "Melhorar resolu\xE7\xE3o (upscale Topaz)",
1352
+ description: "Aumenta a resolu\xE7\xE3o de uma IMAGEM ou de um V\xCDDEO que j\xE1 existe, com o Topaz. Passe o arquivo em `file` (caminho local ou URL) \u2014 o tipo \xE9 detectado pela extens\xE3o e define o modelo e os ajustes v\xE1lidos. Imagem: at\xE9 4x, ~20s, a partir de 6\u{1F48E}. V\xEDdeo: no m\xE1ximo 30s de dura\xE7\xE3o e sa\xEDda at\xE9 1080p, custa por bloco de 10s (5s \u2248 87\u{1F48E}, o teto de 30s chega a 260\u{1F48E}), leva alguns minutos e o \xC1UDIO N\xC3O \xC9 MANTIDO. \u26A0\uFE0F Melhorar v\xEDdeo \xE9 restrito a quem tem a tag de budget admin. Use `estimate_cost` antes se quiser confirmar o pre\xE7o." + TEAM_GATING_NOTE,
1353
+ inputSchema: {
1354
+ file: z2.string().min(1).describe(
1355
+ "Arquivo a melhorar: caminho local (/foto.png, ~/Desktop/clipe.mp4) OU URL p\xFAblica. Caminho local sobe pro R2 automaticamente. Imagem: png, jpg, jpeg, webp. V\xEDdeo: mp4, mov, m4v."
1356
+ ),
1357
+ factor: z2.number().int().min(1).max(4).optional().describe("Quantas vezes aumentar (1 a 4). Padr\xE3o 2. Em 1x s\xF3 melhora a qualidade, sem ampliar."),
1358
+ variant: z2.string().optional().describe(
1359
+ "Modelo do Topaz. Imagem: Wonder 3 (padr\xE3o, serve para quase tudo), Wonder 3.5 (detalhe fino), Wonder 2, Wonder, Recover 3 (imagem muito danificada), Standard MAX (fiel ao original), Redefine (usa a descri\xE7\xE3o), Recovery V2, Recovery. V\xEDdeo: Starlight Precise 2.6 (padr\xE3o), Starlight HQ, Starlight Mini, Starlight Sharp, Starlight Fast 2 (mais r\xE1pido e custa metade)."
1360
+ ),
1361
+ project: z2.string().max(64).optional().describe('Projeto de destino (uuid OU code curto, ex.: "VRAO-26"). Ausente \u2192 projeto ativo.'),
1362
+ // ── só imagem ────────────────────────────────────────────────────────
1363
+ output_format: z2.enum(["jpeg", "png"]).optional().describe("[imagem] Formato da sa\xEDda. Padr\xE3o png."),
1364
+ subject_detection: z2.enum(["All", "Foreground", "Background"]).optional().describe("[imagem] Onde aplicar: imagem toda, s\xF3 o primeiro plano ou s\xF3 o fundo. Padr\xE3o All."),
1365
+ face_enhancement: z2.boolean().optional().describe("[imagem] Tratar rostos separadamente. Padr\xE3o true."),
1366
+ face_enhancement_strength: z2.number().min(0).max(1).optional().describe("[imagem] For\xE7a nos rostos (0 a 1). Padr\xE3o 0.8."),
1367
+ face_enhancement_creativity: z2.number().min(0).max(1).optional().describe("[imagem] Liberdade nos rostos (0 a 1). Padr\xE3o 0."),
1368
+ enhancement_strength: z2.enum(["low", "medium", "high"]).optional().describe("[imagem] Intensidade geral do tratamento."),
1369
+ creativity: z2.number().int().min(1).max(6).optional().describe("[imagem] Quanto detalhe o modelo pode inventar (1 a 6)."),
1370
+ texture: z2.number().int().min(1).max(5).optional().describe("[imagem] Textura (1 a 5)."),
1371
+ sharpen: z2.number().min(0).max(1).optional().describe("[imagem] Nitidez (0 a 1)."),
1372
+ denoise: z2.number().min(0).max(1).optional().describe("[imagem] Redu\xE7\xE3o de ru\xEDdo (0 a 1)."),
1373
+ detail: z2.number().min(0).max(1).optional().describe("[imagem] Detalhe (0 a 1)."),
1374
+ crop_to_fill: z2.boolean().optional().describe("[imagem] Cortar para preencher em vez de encaixar."),
1375
+ autoprompt: z2.boolean().optional().describe("[imagem] O modelo escreve sozinho a orienta\xE7\xE3o a partir da imagem."),
1376
+ prompt: z2.string().max(2e3).optional().describe("[imagem] Orienta\xE7\xE3o para guiar a reconstru\xE7\xE3o (o que a imagem mostra). \xDAtil no Redefine."),
1377
+ // ── só vídeo ─────────────────────────────────────────────────────────
1378
+ softness: z2.number().int().min(1).max(5).optional().describe("[v\xEDdeo] Suavidade, 1 mais n\xEDtido e 5 mais suave. S\xF3 vale no Starlight Precise 2.6."),
1379
+ target_fps: z2.number().int().min(16).max(60).optional().describe("[v\xEDdeo] Quadros por segundo da sa\xEDda. Mudar o valor liga a interpola\xE7\xE3o de quadros."),
1380
+ h264_output: z2.boolean().optional().describe(
1381
+ "[v\xEDdeo] Exportar em H.264. Padr\xE3o true \u2014 desligado sai em H.265, que n\xE3o toca no Firefox e depende de hardware no Chrome."
1382
+ ),
1383
+ // ── medidas ──────────────────────────────────────────────────────────
1384
+ source_width: z2.number().int().positive().optional().describe("Largura do arquivo. S\xF3 se a leitura autom\xE1tica falhar."),
1385
+ source_height: z2.number().int().positive().optional().describe("Altura do arquivo. S\xF3 se a leitura autom\xE1tica falhar."),
1386
+ source_duration: z2.number().positive().optional().describe("[v\xEDdeo] Dura\xE7\xE3o em segundos. S\xF3 se a leitura autom\xE1tica falhar."),
1387
+ source_fps: z2.number().positive().optional().describe("[v\xEDdeo] Quadros por segundo do arquivo. S\xF3 se a leitura autom\xE1tica falhar.")
1388
+ }
1389
+ },
1390
+ async (args) => {
1391
+ try {
1392
+ const kind = kindFromExt(args.file);
1393
+ if (!kind) {
1394
+ return errorResult(
1395
+ "N\xE3o reconheci o tipo do arquivo pela extens\xE3o. Imagem: png, jpg, jpeg, webp. V\xEDdeo: mp4, mov, m4v."
1396
+ );
1397
+ }
1398
+ const isVideo = kind === "video";
1399
+ const factor = args.factor ?? 2;
1400
+ const variant = args.variant ?? (isVideo ? DEFAULT_VIDEO_VARIANT : DEFAULT_IMAGE_VARIANT);
1401
+ const allowed = isVideo ? UPSCALE_VIDEO_VARIANTS : UPSCALE_IMAGE_VARIANTS;
1402
+ if (!allowed.includes(variant)) {
1403
+ return errorResult(
1404
+ `"${variant}" n\xE3o \xE9 um modelo de ${isVideo ? "v\xEDdeo" : "imagem"}. Use um destes: ${allowed.join(", ")}.`
1405
+ );
1406
+ }
1407
+ let width = args.source_width;
1408
+ let height = args.source_height;
1409
+ let duration = args.source_duration;
1410
+ let fps = args.source_fps;
1411
+ if (!width || !height || isVideo && !duration) {
1412
+ try {
1413
+ const probed = await probeMedia(args.file);
1414
+ width = width ?? probed.width;
1415
+ height = height ?? probed.height;
1416
+ duration = duration ?? probed.duration;
1417
+ fps = fps ?? probed.fps;
1418
+ } catch (e) {
1419
+ if (e instanceof ProbeError) {
1420
+ return errorResult(
1421
+ `${e.message}
1422
+
1423
+ \u{1F4A1} Passe as medidas na m\xE3o para seguir: source_width, source_height` + (isVideo ? ", source_duration e source_fps" : "") + ". Sem elas o custo sai errado e a trava de tamanho n\xE3o funciona."
1424
+ );
1425
+ }
1426
+ throw e;
1427
+ }
1428
+ }
1429
+ if (isVideo) {
1430
+ if (duration && duration > MAX_VIDEO_SECONDS) {
1431
+ return errorResult(
1432
+ `Este v\xEDdeo tem ${Math.round(duration)}s e o limite para melhorar \xE9 ${MAX_VIDEO_SECONDS}s. Corte um trecho menor e tente de novo.`
1433
+ );
1434
+ }
1435
+ if (height && height * factor > MAX_OUTPUT_HEIGHT) {
1436
+ const maxFactor = Math.floor(MAX_OUTPUT_HEIGHT / height);
1437
+ return errorResult(
1438
+ maxFactor >= 2 ? `Neste v\xEDdeo o aumento m\xE1ximo \xE9 ${maxFactor}x \u2014 acima disso a sa\xEDda passa de 1080p.` : "Este v\xEDdeo j\xE1 est\xE1 em 1080p ou acima, ent\xE3o n\xE3o d\xE1 para aumentar mais."
1439
+ );
1440
+ }
1441
+ }
1442
+ const url = await resolveRef(cfg, args.file);
1443
+ const label = [isVideo ? "V\xEDdeo" : "Imagem", `${factor}x`, variant].join(" \xB7 ");
1444
+ const input = compact({
1445
+ [isVideo ? "video" : "image"]: url,
1446
+ model: variant,
1447
+ upscale_factor: factor,
1448
+ prompt: args.prompt || label,
1449
+ source_width: width,
1450
+ source_height: height,
1451
+ source_duration: duration,
1452
+ source_fps: fps,
1453
+ ...isVideo ? {
1454
+ target_fps: args.target_fps,
1455
+ // O backend descarta softness fora desta variante; filtrar aqui
1456
+ // deixa o motivo visível em vez de sumir em silêncio.
1457
+ softness: variant === SOFTNESS_ONLY ? args.softness : void 0,
1458
+ H264_output: args.h264_output ?? true
1459
+ } : {
1460
+ output_format: args.output_format,
1461
+ subject_detection: args.subject_detection,
1462
+ face_enhancement: args.face_enhancement,
1463
+ face_enhancement_strength: args.face_enhancement_strength,
1464
+ face_enhancement_creativity: args.face_enhancement_creativity,
1465
+ enhancement_strength: args.enhancement_strength,
1466
+ creativity: args.creativity,
1467
+ texture: args.texture,
1468
+ sharpen: args.sharpen,
1469
+ denoise: args.denoise,
1470
+ detail: args.detail,
1471
+ crop_to_fill: args.crop_to_fill,
1472
+ autoprompt: args.autoprompt
1473
+ }
1474
+ });
1475
+ const start = await client.startGeneration(
1476
+ compact({
1477
+ model: isVideo ? VIDEO_MODEL : IMAGE_MODEL,
1478
+ input,
1479
+ project: args.project ?? cfg.activeProject ?? void 0
1480
+ })
1481
+ );
1482
+ if (!start.success || !start.generation_id) {
1483
+ if (isTeamGatingError(start.message)) return errorResult(await buildPickProjectMessage(client));
1484
+ return errorResult(start.message || "Falha ao iniciar o upscale.");
1485
+ }
1486
+ const footer = costFooter(start, cfg);
1487
+ const { settled, status } = await pollUntilDone(
1488
+ client,
1489
+ start.generation_id,
1490
+ isVideo ? VIDEO_BUDGET_MS2 : IMAGE_BUDGET_MS2
1491
+ );
1492
+ if (!settled) {
1493
+ return textResult(
1494
+ `\u23F3 Upscale ainda em processamento (id: ${start.generation_id}, ${status.progress ?? 0}%). ` + (isVideo ? "V\xEDdeo costuma levar de 3 a 15 minutos, dependendo da dura\xE7\xE3o e da resolu\xE7\xE3o de sa\xEDda \u2014 " : "") + `use check_generation com esse id.
1495
+ ${footer}`
1496
+ );
1497
+ }
1498
+ if (status.status === "failed") {
1499
+ return errorResult(`Upscale falhou: ${status.error_message || "erro desconhecido"}.
1500
+ ${footer}`);
1501
+ }
1502
+ const urls = outputUrls(status);
1503
+ const list = urls.map((u, i) => `${i + 1}. ${u}`).join("\n") || "(sem URLs)";
1504
+ const saida = width && height ? ` \u2014 sa\xEDda ${width * factor} \xD7 ${height * factor}` : "";
1505
+ const semAudio = isVideo ? "\nO \xE1udio n\xE3o foi mantido (o Topaz descarta a faixa)." : "";
1506
+ return textResult(
1507
+ `\u2705 ${isVideo ? "V\xEDdeo" : "Imagem"} melhorado em ${factor}x com ${variant}${saida}.
1508
+ ${list}${semAudio}
1509
+ ${footer}`
1510
+ );
1511
+ } catch (e) {
1512
+ const msg = e instanceof ApiError ? e.message : e instanceof Error ? e.message : "";
1513
+ if (isTeamGatingError(msg)) return errorResult(await buildPickProjectMessage(client));
1514
+ if (isBudgetBlockedError(msg)) {
1515
+ return errorResult(`${apiErrorToUserMessage(e)}
1516
+ ${BUDGET_BLOCKED_GUIDANCE}`);
1517
+ }
1518
+ return errorResult(apiErrorToUserMessage(e));
1519
+ }
1520
+ }
1521
+ );
1522
+ }
1523
+
1524
+ // src/tools/audio.ts
1525
+ import { z as z3 } from "zod";
1103
1526
  function registerAudioTools(server, client, cfg) {
1104
1527
  server.registerTool(
1105
1528
  "generate_audio",
@@ -1107,15 +1530,15 @@ function registerAudioTools(server, client, cfg) {
1107
1530
  title: "Gerar \xE1udio (TTS)",
1108
1531
  description: 'Sintetiza fala a partir de texto (s\xEDncrono \u2014 retorna a URL na hora) usando o IA Lab da BePlus. ~30 vozes. Opcional `style_instruction` (notas de dire\xE7\xE3o, ex.: "fale animado e pausado") e di\xE1logo com 2 locutores via `multi_speaker`. Consome diamantes por segundo de \xE1udio.' + TEAM_GATING_NOTE,
1109
1532
  inputSchema: {
1110
- text: z2.string().min(1).max(1e4).describe("Texto a ser falado."),
1111
- voice: z2.enum(VOICES).default(DEFAULTS.voice).describe("Voz (ex.: Kore, Puck, Charon)."),
1112
- model: z2.enum(TTS_MODELS).default(DEFAULTS.ttsModel).describe("Modelo TTS."),
1113
- style_instruction: z2.string().max(2e3).optional().describe("Instru\xE7\xE3o de estilo/tom aplicada \xE0 fala."),
1114
- multi_speaker: z2.object({
1115
- speaker1: z2.object({ name: z2.string().min(1).max(50), voice: z2.enum(VOICES) }),
1116
- speaker2: z2.object({ name: z2.string().min(1).max(50), voice: z2.enum(VOICES) })
1533
+ text: z3.string().min(1).max(1e4).describe("Texto a ser falado."),
1534
+ voice: z3.enum(VOICES).default(DEFAULTS.voice).describe("Voz (ex.: Kore, Puck, Charon)."),
1535
+ model: z3.enum(TTS_MODELS).default(DEFAULTS.ttsModel).describe("Modelo TTS."),
1536
+ style_instruction: z3.string().max(2e3).optional().describe("Instru\xE7\xE3o de estilo/tom aplicada \xE0 fala."),
1537
+ multi_speaker: z3.object({
1538
+ speaker1: z3.object({ name: z3.string().min(1).max(50), voice: z3.enum(VOICES) }),
1539
+ speaker2: z3.object({ name: z3.string().min(1).max(50), voice: z3.enum(VOICES) })
1117
1540
  }).optional().describe('Di\xE1logo de 2 locutores. Marque as falas no texto como "Nome: ...".'),
1118
- project: z2.string().max(64).optional().describe('Projeto (uuid OU code curto, ex.: "VRAO-26"). Omita para usar o projeto ativo da conta.')
1541
+ project: z3.string().max(64).optional().describe('Projeto (uuid OU code curto, ex.: "VRAO-26"). Omita para usar o projeto ativo da conta.')
1119
1542
  }
1120
1543
  },
1121
1544
  async (args) => {
@@ -1156,7 +1579,7 @@ ${BUDGET_BLOCKED_GUIDANCE}`);
1156
1579
  }
1157
1580
 
1158
1581
  // src/tools/music.ts
1159
- import { z as z3 } from "zod";
1582
+ import { z as z4 } from "zod";
1160
1583
  var MUSIC_BUDGET_MS = 15e4;
1161
1584
  function registerMusicTools(server, client, cfg) {
1162
1585
  server.registerTool(
@@ -1165,18 +1588,18 @@ function registerMusicTools(server, client, cfg) {
1165
1588
  title: "Gerar m\xFAsica",
1166
1589
  description: 'Gera uma m\xFAsica completa (com vocais ou instrumental) usando o Suno no IA Lab da BePlus. ASS\xCDNCRONO e LENTO (geralmente 30s\u20132+ min). Aguarda at\xE9 ~2,5 min; se n\xE3o terminar, retorna o generation_id e o status \u2014 ent\xE3o use check_generation. Consome diamantes. DOIS modos: (a) DESCRI\xC7\xC3O (custom_mode=false, padr\xE3o) \u2192 passe `description` ("uma balada triste de piano"); (b) LETRA PR\xD3PRIA (custom_mode=true) \u2192 passe `lyrics` (a letra) e opcionalmente `title`. Use `tags` para g\xEAnero/estilo em ambos os modos (ex.: "sertanejo, animado, vocal feminino"). `vocal_gender` (f/m) s\xF3 \xE9 aceito pelo Suno v4.5 e \xE9 ignorado quando `make_instrumental=true`.' + TEAM_GATING_NOTE,
1167
1590
  inputSchema: {
1168
- model: z3.enum(MUSIC_MODELS).default(DEFAULTS.musicModel).describe("suno/v5-5 = mais novo; suno/v4-5 = r\xE1pido/consistente (\xFAnico que aceita vocal_gender)."),
1169
- custom_mode: z3.boolean().optional().describe("false (padr\xE3o) = gera a partir de `description`; true = usa sua `lyrics` pr\xF3pria."),
1170
- description: z3.string().max(2500).optional().describe("Modo descri\xE7\xE3o (custom_mode=false): descreva a m\xFAsica desejada. Obrigat\xF3rio nesse modo."),
1171
- lyrics: z3.string().max(5e3).optional().describe("Modo letra (custom_mode=true): a letra completa da m\xFAsica. Obrigat\xF3rio nesse modo."),
1172
- title: z3.string().max(120).optional().describe("T\xEDtulo da m\xFAsica (modo letra)."),
1173
- tags: z3.string().max(1e3).optional().describe('G\xEAnero/estilo, separados por v\xEDrgula (ex.: "pop, energ\xE9tico, vocal masculino").'),
1174
- make_instrumental: z3.boolean().optional().describe("true = sem vocais (apenas instrumental). Padr\xE3o false."),
1175
- vocal_gender: z3.enum(VOCAL_GENDERS).optional().describe("G\xEAnero do vocal: f (feminino) ou m (masculino). S\xF3 Suno v4.5; ignorado se instrumental."),
1176
- negative_tags: z3.string().max(1e3).optional().describe("Estilos/elementos a evitar."),
1177
- style_weight: z3.number().min(0).max(1).optional().describe("Peso do estilo (0\u20131). Maior = segue mais as tags. Omita para o modelo decidir."),
1178
- weirdness_constraint: z3.number().min(0).max(1).optional().describe("Criatividade/imprevisibilidade (0\u20131). Omita para o modelo decidir."),
1179
- project: z3.string().max(64).optional().describe('Projeto (uuid OU code curto, ex.: "VRAO-26"). S\xF3 omita se o usu\xE1rio J\xC1 confirmou o projeto ativo nesta sess\xE3o.')
1591
+ model: z4.enum(MUSIC_MODELS).default(DEFAULTS.musicModel).describe("suno/v5-5 = mais novo; suno/v4-5 = r\xE1pido/consistente (\xFAnico que aceita vocal_gender)."),
1592
+ custom_mode: z4.boolean().optional().describe("false (padr\xE3o) = gera a partir de `description`; true = usa sua `lyrics` pr\xF3pria."),
1593
+ description: z4.string().max(2500).optional().describe("Modo descri\xE7\xE3o (custom_mode=false): descreva a m\xFAsica desejada. Obrigat\xF3rio nesse modo."),
1594
+ lyrics: z4.string().max(5e3).optional().describe("Modo letra (custom_mode=true): a letra completa da m\xFAsica. Obrigat\xF3rio nesse modo."),
1595
+ title: z4.string().max(120).optional().describe("T\xEDtulo da m\xFAsica (modo letra)."),
1596
+ tags: z4.string().max(1e3).optional().describe('G\xEAnero/estilo, separados por v\xEDrgula (ex.: "pop, energ\xE9tico, vocal masculino").'),
1597
+ make_instrumental: z4.boolean().optional().describe("true = sem vocais (apenas instrumental). Padr\xE3o false."),
1598
+ vocal_gender: z4.enum(VOCAL_GENDERS).optional().describe("G\xEAnero do vocal: f (feminino) ou m (masculino). S\xF3 Suno v4.5; ignorado se instrumental."),
1599
+ negative_tags: z4.string().max(1e3).optional().describe("Estilos/elementos a evitar."),
1600
+ style_weight: z4.number().min(0).max(1).optional().describe("Peso do estilo (0\u20131). Maior = segue mais as tags. Omita para o modelo decidir."),
1601
+ weirdness_constraint: z4.number().min(0).max(1).optional().describe("Criatividade/imprevisibilidade (0\u20131). Omita para o modelo decidir."),
1602
+ project: z4.string().max(64).optional().describe('Projeto (uuid OU code curto, ex.: "VRAO-26"). S\xF3 omita se o usu\xE1rio J\xC1 confirmou o projeto ativo nesta sess\xE3o.')
1180
1603
  }
1181
1604
  },
1182
1605
  async (args) => {
@@ -1243,7 +1666,7 @@ ${BUDGET_BLOCKED_GUIDANCE}`);
1243
1666
  }
1244
1667
 
1245
1668
  // src/tools/account.ts
1246
- import { z as z4 } from "zod";
1669
+ import { z as z5 } from "zod";
1247
1670
  function registerAccountTools(server, client, _cfg) {
1248
1671
  server.registerTool(
1249
1672
  "whoami",
@@ -1296,7 +1719,7 @@ ${budget}` : "")
1296
1719
  title: "Listar modelos",
1297
1720
  description: "Lista os modelos dispon\xEDveis com pre\xE7o-base e limites por usu\xE1rio. Use quando estiver em d\xFAvida sobre qual modelo/params usar ou quanto custa. \xC9 a fonte de verdade em runtime.",
1298
1721
  inputSchema: {
1299
- kind: z4.enum(["image", "video", "audio", "all"]).default("all").optional().describe("Filtrar por tipo.")
1722
+ kind: z5.enum(["image", "video", "audio", "all"]).default("all").optional().describe("Filtrar por tipo.")
1300
1723
  }
1301
1724
  },
1302
1725
  async () => {
@@ -1330,16 +1753,45 @@ Limites simult\xE2neos por usu\xE1rio: imagem=${m.limits.perUserMaxImage}, v\xED
1330
1753
  title: "Estimar custo",
1331
1754
  description: "Retorna o custo autoritativo em diamantes ANTES de gerar. Para \xE1udio (TTS) o custo \xE9 por segundo e s\xF3 \xE9 conhecido ap\xF3s a s\xEDntese.",
1332
1755
  inputSchema: {
1333
- model: z4.string().min(1).describe("Id do modelo (ex.: google/nano-banana-pro, bytedance/seedance-2.0)."),
1334
- resolution: z4.string().optional(),
1335
- duration: z4.number().int().optional(),
1336
- mode: z4.string().optional(),
1337
- size: z4.string().optional(),
1338
- quality: z4.string().optional()
1756
+ model: z5.string().min(1).describe("Id do modelo (ex.: google/nano-banana-pro, bytedance/seedance-2.0)."),
1757
+ resolution: z5.string().optional(),
1758
+ duration: z5.number().int().optional(),
1759
+ mode: z5.string().optional(),
1760
+ size: z5.string().optional(),
1761
+ quality: z5.string().optional(),
1762
+ // Upscale: o preço não sai de uma chave fixa — vem das medidas do
1763
+ // arquivo (megapixel de saída na imagem, bloco de 10s no vídeo). Sem
1764
+ // elas o backend responde pelo padrão de 1024²/10s, que não é o preço
1765
+ // que vai ser cobrado.
1766
+ file: z5.string().optional().describe(
1767
+ "[upscale] Arquivo a medir (caminho local ou URL) \u2014 preenche sozinho as medidas abaixo. Use com model=topaz/upscale/image/generative ou .../video/generative."
1768
+ ),
1769
+ upscale_factor: z5.number().int().min(1).max(4).optional().describe("[upscale] Fator de aumento (1 a 4). Padr\xE3o 2."),
1770
+ model_variant: z5.string().optional().describe("[upscale] Variante do Topaz (ex.: Wonder 3, Starlight Fast 2) \u2014 muda o pre\xE7o."),
1771
+ source_width: z5.number().int().positive().optional().describe("[upscale] Largura do arquivo."),
1772
+ source_height: z5.number().int().positive().optional().describe("[upscale] Altura do arquivo."),
1773
+ source_duration: z5.number().positive().optional().describe("[upscale] Dura\xE7\xE3o em segundos (v\xEDdeo)."),
1774
+ source_fps: z5.number().positive().optional().describe("[upscale] Quadros por segundo do arquivo (v\xEDdeo).")
1339
1775
  }
1340
1776
  },
1341
1777
  async (args) => {
1342
1778
  try {
1779
+ let width = args.source_width;
1780
+ let height = args.source_height;
1781
+ let duration = args.source_duration;
1782
+ let fps = args.source_fps;
1783
+ if (args.file && (!width || !height)) {
1784
+ try {
1785
+ const probed = await probeMedia(args.file);
1786
+ width = width ?? probed.width;
1787
+ height = height ?? probed.height;
1788
+ duration = duration ?? probed.duration;
1789
+ fps = fps ?? probed.fps;
1790
+ } catch (e) {
1791
+ if (e instanceof ProbeError) return errorResult(e.message);
1792
+ throw e;
1793
+ }
1794
+ }
1343
1795
  const c = await client.calculateCost(
1344
1796
  compact({
1345
1797
  model: args.model,
@@ -1347,7 +1799,13 @@ Limites simult\xE2neos por usu\xE1rio: imagem=${m.limits.perUserMaxImage}, v\xED
1347
1799
  duration: args.duration,
1348
1800
  mode: args.mode,
1349
1801
  size: args.size,
1350
- quality: args.quality
1802
+ quality: args.quality,
1803
+ upscale_factor: args.upscale_factor,
1804
+ model_variant: args.model_variant,
1805
+ source_width: width,
1806
+ source_height: height,
1807
+ source_duration: duration,
1808
+ source_fps: fps
1351
1809
  })
1352
1810
  );
1353
1811
  const note = c.fallback ? " [modelo desconhecido \u2014 custo m\xEDnimo]" : "";
@@ -1360,7 +1818,7 @@ Limites simult\xE2neos por usu\xE1rio: imagem=${m.limits.perUserMaxImage}, v\xED
1360
1818
  }
1361
1819
 
1362
1820
  // src/tools/analyze.ts
1363
- import { z as z5 } from "zod";
1821
+ import { z as z6 } from "zod";
1364
1822
  function registerAnalyzeTools(server, client, cfg) {
1365
1823
  server.registerTool(
1366
1824
  "analyze_media",
@@ -1368,12 +1826,12 @@ function registerAnalyzeTools(server, client, cfg) {
1368
1826
  title: "Analisar m\xEDdia (Gemini)",
1369
1827
  description: "Envia um prompt + m\xEDdia (imagem, v\xEDdeo, \xE1udio ou PDF) para o Gemini e devolve uma resposta em texto. Use para descrever/analisar/transcrever/resumir/extrair informa\xE7\xE3o de m\xEDdia, ou perguntar algo sobre ela. A m\xEDdia entra como `media_urls` (URLs p\xFAblicas \u2014 tudo que o Google aceita: imagens, v\xEDdeos, \xE1udios, PDFs). V\xEDdeo/\xE1udio grandes funcionam (v\xE3o pela Files API do Google). Consome diamantes (reembolsados se falhar). Veja custo/limites com list_models.",
1370
1828
  inputSchema: {
1371
- prompt: z5.string().min(1).max(5e4).describe('O que voc\xEA quer do modelo sobre a m\xEDdia (ex.: "descreva este v\xEDdeo", "transcreva este \xE1udio").'),
1372
- media_urls: z5.array(z5.string().min(1)).max(50).optional().describe(
1829
+ prompt: z6.string().min(1).max(5e4).describe('O que voc\xEA quer do modelo sobre a m\xEDdia (ex.: "descreva este v\xEDdeo", "transcreva este \xE1udio").'),
1830
+ media_urls: z6.array(z6.string().min(1)).max(50).optional().describe(
1373
1831
  "M\xEDdia (imagem/v\xEDdeo/\xE1udio/PDF): URLs p\xFAblicas OU caminhos de arquivo local (o MCP sobe pro R2 automaticamente). Pode misturar tipos. Opcional (prompt s\xF3-texto tamb\xE9m funciona)."
1374
1832
  ),
1375
- model: z5.enum(["gemini-flash", "gemini-pro"]).default("gemini-flash").optional().describe("gemini-flash = r\xE1pido/barato (Gemini 3.5 Flash); gemini-pro = mais capaz (Gemini 3.1 Pro)."),
1376
- project: z5.string().optional().describe(
1833
+ model: z6.enum(["gemini-flash", "gemini-pro"]).default("gemini-flash").optional().describe("gemini-flash = r\xE1pido/barato (Gemini 3.5 Flash); gemini-pro = mais capaz (Gemini 3.1 Pro)."),
1834
+ project: z6.string().optional().describe(
1377
1835
  'Projeto (uuid ou code curto, ex.: "ICONS") de cujo budget cobrar. Omitido \u2192 usa o projeto ativo da conta.'
1378
1836
  )
1379
1837
  }
@@ -1402,7 +1860,7 @@ function registerAnalyzeTools(server, client, cfg) {
1402
1860
  }
1403
1861
 
1404
1862
  // src/tools/projects.ts
1405
- import { z as z6 } from "zod";
1863
+ import { z as z7 } from "zod";
1406
1864
  function projectLine(p, active) {
1407
1865
  const marker = active ? "\u25CF " : "\u25CB ";
1408
1866
  const counts = `${p.generation_count ?? 0} gera\xE7\xF5es`;
@@ -1446,11 +1904,11 @@ ${lines.join("\n")}`);
1446
1904
  title: "Criar projeto",
1447
1905
  description: "Cria um projeto novo no IA Lab (workspace compartilhado), SEMPRE dentro de um cliente/marca, e por padr\xE3o j\xE1 o deixa ativo \u2014 gera\xE7\xF5es seguintes entram nele. S\xF3 membros da equipe podem criar. O campo `client` \xE9 obrigat\xF3rio: passe o code/uuid de um cliente existente (veja list_clients) OU o NOME de um cliente novo (ele \xE9 criado automaticamente). Projetos s\xE3o globais: todos da equipe usam.",
1448
1906
  inputSchema: {
1449
- name: z6.string().min(1).max(256).describe('Nome do projeto (ex.: "Ladeira Abaixo").'),
1450
- client: z6.string().min(1).max(256).describe('Cliente/marca dono: code/uuid existente (ex.: "RDBL") OU nome novo (ex.: "Red Bull"). Obrigat\xF3rio.'),
1451
- description: z6.string().max(4e3).optional().describe("Descri\xE7\xE3o do projeto (opcional)."),
1452
- color: z6.string().regex(/^#[0-9a-fA-F]{6}$/).optional().describe("Cor em hex #RRGGBB para o badge (opcional)."),
1453
- set_active: z6.boolean().optional().describe("Deixar o novo projeto como ativo (padr\xE3o true).")
1907
+ name: z7.string().min(1).max(256).describe('Nome do projeto (ex.: "Ladeira Abaixo").'),
1908
+ client: z7.string().min(1).max(256).describe('Cliente/marca dono: code/uuid existente (ex.: "RDBL") OU nome novo (ex.: "Red Bull"). Obrigat\xF3rio.'),
1909
+ description: z7.string().max(4e3).optional().describe("Descri\xE7\xE3o do projeto (opcional)."),
1910
+ color: z7.string().regex(/^#[0-9a-fA-F]{6}$/).optional().describe("Cor em hex #RRGGBB para o badge (opcional)."),
1911
+ set_active: z7.boolean().optional().describe("Deixar o novo projeto como ativo (padr\xE3o true).")
1454
1912
  }
1455
1913
  },
1456
1914
  async (args) => {
@@ -1475,7 +1933,7 @@ ${lines.join("\n")}`);
1475
1933
  title: "Definir projeto ativo",
1476
1934
  description: 'Define o projeto ativo da conta (por uuid OU code curto, ex.: "VRAO-26"). A partir da\xED, gera\xE7\xF5es sem `project` expl\xEDcito s\xE3o vinculadas a ele \u2014 espelha o seletor de projeto da web. Passe project=null para limpar.',
1477
1935
  inputSchema: {
1478
- project: z6.string().max(64).nullable().describe("uuid OU code curto do projeto. null para nenhum projeto.")
1936
+ project: z7.string().max(64).nullable().describe("uuid OU code curto do projeto. null para nenhum projeto.")
1479
1937
  }
1480
1938
  },
1481
1939
  async ({ project }) => {
@@ -1493,7 +1951,7 @@ ${budget}` : ""));
1493
1951
  }
1494
1952
 
1495
1953
  // src/tools/clients.ts
1496
- import { z as z7 } from "zod";
1954
+ import { z as z8 } from "zod";
1497
1955
  function clientLine(c, active) {
1498
1956
  const marker = active ? "\u25CF " : "\u25CB ";
1499
1957
  const counts = `${c.project_count ?? 0} projetos`;
@@ -1532,7 +1990,7 @@ ${lines.join("\n")}`);
1532
1990
  title: "Criar cliente",
1533
1991
  description: 'Cria um cliente/marca novo (ex.: "Red Bull"). S\xF3 membros da equipe. Clientes s\xE3o globais: qualquer colega passa a v\xEA-lo e us\xE1-lo. Depois crie projetos dentro dele com create_project.',
1534
1992
  inputSchema: {
1535
- name: z7.string().min(1).max(256).describe('Nome do cliente/marca (ex.: "Red Bull").')
1993
+ name: z8.string().min(1).max(256).describe('Nome do cliente/marca (ex.: "Red Bull").')
1536
1994
  }
1537
1995
  },
1538
1996
  async (args) => {
@@ -1550,7 +2008,7 @@ ${lines.join("\n")}`);
1550
2008
  title: "Definir cliente ativo",
1551
2009
  description: 'Define o cliente ativo da conta (por uuid OU code curto, ex.: "RDBL"). Trocar de cliente limpa o projeto ativo se ele pertencia a outro cliente. Passe client=null para limpar.',
1552
2010
  inputSchema: {
1553
- client: z7.string().max(64).nullable().describe("uuid OU code curto do cliente. null para nenhum.")
2011
+ client: z8.string().max(64).nullable().describe("uuid OU code curto do cliente. null para nenhum.")
1554
2012
  }
1555
2013
  },
1556
2014
  async ({ client: ref }) => {
@@ -1566,7 +2024,7 @@ ${lines.join("\n")}`);
1566
2024
  }
1567
2025
 
1568
2026
  // src/tools/calls.ts
1569
- import { z as z8 } from "zod";
2027
+ import { z as z9 } from "zod";
1570
2028
  var VIS_LABEL = {
1571
2029
  team: "equipe",
1572
2030
  private: "privada",
@@ -1632,14 +2090,14 @@ function registerCallTools(server, client, _cfg) {
1632
2090
  title: "Buscar calls",
1633
2091
  description: 'Busca nas calls (reuni\xF5es gravadas e transcritas) vis\xEDveis pra conta \u2014 suas, da equipe e compartilhadas. Todos os filtros s\xE3o opcionais e combin\xE1veis: `query` (texto no t\xEDtulo/projeto/cliente/falante), `project` (code/uuid/nome), `client` (code/nome), `company` (empresa/equipe do dono, ex.: "BePlus"), `visibility` (team|private|workspace|link), `speaker` (nome do falante), `mine` (s\xF3 as que EU gravei). Retorna uma lista resumida \u2014 use get_call com o id pra ler a transcri\xE7\xE3o completa.',
1634
2092
  inputSchema: {
1635
- query: z8.string().max(200).optional().describe("Texto livre \u2014 casa t\xEDtulo, projeto, cliente ou falante."),
1636
- project: z8.string().max(64).optional().describe('Projeto: code (ex.: "CONTEUDO-INS"), uuid ou parte do nome.'),
1637
- client: z8.string().max(128).optional().describe("Cliente: code ou parte do nome."),
1638
- company: z8.string().max(128).optional().describe('Empresa/equipe do dono da call (ex.: "BePlus"). Filtra por parte do nome.'),
1639
- visibility: z8.enum(["team", "private", "workspace", "link"]).optional().describe("Privacidade: team (equipe), private (privada), workspace (projeto) ou link."),
1640
- speaker: z8.string().max(128).optional().describe("Nome do falante."),
1641
- mine: z8.boolean().optional().describe('true = s\xF3 as calls que EU gravei (sou o dono). Use pra "minhas grava\xE7\xF5es".'),
1642
- limit: z8.number().int().min(1).max(50).optional().describe("M\xE1ximo de resultados (padr\xE3o 20).")
2093
+ query: z9.string().max(200).optional().describe("Texto livre \u2014 casa t\xEDtulo, projeto, cliente ou falante."),
2094
+ project: z9.string().max(64).optional().describe('Projeto: code (ex.: "CONTEUDO-INS"), uuid ou parte do nome.'),
2095
+ client: z9.string().max(128).optional().describe("Cliente: code ou parte do nome."),
2096
+ company: z9.string().max(128).optional().describe('Empresa/equipe do dono da call (ex.: "BePlus"). Filtra por parte do nome.'),
2097
+ visibility: z9.enum(["team", "private", "workspace", "link"]).optional().describe("Privacidade: team (equipe), private (privada), workspace (projeto) ou link."),
2098
+ speaker: z9.string().max(128).optional().describe("Nome do falante."),
2099
+ mine: z9.boolean().optional().describe('true = s\xF3 as calls que EU gravei (sou o dono). Use pra "minhas grava\xE7\xF5es".'),
2100
+ limit: z9.number().int().min(1).max(50).optional().describe("M\xE1ximo de resultados (padr\xE3o 20).")
1643
2101
  }
1644
2102
  },
1645
2103
  async (args) => {
@@ -1661,7 +2119,7 @@ ${calls.map(callLine).join("\n\n")}`);
1661
2119
  title: "Ler call",
1662
2120
  description: "Retorna o conte\xFAdo completo de uma call pelo id (use search_calls pra achar): t\xEDtulo, resumo, falantes, projeto/cliente, links da grava\xE7\xE3o e a TRANSCRI\xC7\xC3O inteira por falante. Respeita a visibilidade \u2014 s\xF3 retorna o que a conta pode ver.",
1663
2121
  inputSchema: {
1664
- id: z8.string().min(1).max(64).describe("id (uuid) da call, obtido via search_calls.")
2122
+ id: z9.string().min(1).max(64).describe("id (uuid) da call, obtido via search_calls.")
1665
2123
  }
1666
2124
  },
1667
2125
  async ({ id }) => {
@@ -1693,6 +2151,7 @@ function buildServer(cfg) {
1693
2151
  const server = new McpServer({ name: "beplus", version: VERSION });
1694
2152
  const client = new BeplusClient(cfg);
1695
2153
  registerGenerationTools(server, client, cfg);
2154
+ registerUpscaleTools(server, client, cfg);
1696
2155
  registerAudioTools(server, client, cfg);
1697
2156
  registerMusicTools(server, client, cfg);
1698
2157
  registerAccountTools(server, client, cfg);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beplus-mcp",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "Conector MCP da equipe BePlus — gere imagens/vídeos/áudio e consulte calls, projetos e clientes pela sua conta BePlus (diamantes, limites e histórico aplicados pela plataforma).",
5
5
  "type": "module",
6
6
  "bin": {