beplus-mcp 0.28.0 → 0.30.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.
package/dist/index.js CHANGED
@@ -22141,7 +22141,9 @@ var IMAGE_MODELS = [
22141
22141
  "midjourney"
22142
22142
  ];
22143
22143
  var SEEDREAM_LITE_SIZES = ["2K", "3K", "4K"];
22144
- var SEEDREAM_PRO_SIZES = ["1K", "2K"];
22144
+ var SEEDREAM_PRO_SIZES = ["1K", "1.5K", "2K"];
22145
+ var SEEDREAM_PRO_PIXELS = { min: 921600, max: 4624220 };
22146
+ var SEEDREAM_PRO_MAX_IMAGES = 4;
22145
22147
  var VIDEO_MODELS = [
22146
22148
  "bytedance/seedance-2.5",
22147
22149
  "bytedance/seedance-2.0",
@@ -22263,17 +22265,25 @@ function registerGenerationTools(server, client, cfg) {
22263
22265
  // que o modelo aceita (Seedream/nano-banana não têm limite publicado).
22264
22266
  prompt: external_exports.string().min(1).describe("Descri\xE7\xE3o da imagem desejada."),
22265
22267
  model: external_exports.enum(IMAGE_MODELS).default(DEFAULTS.imageModel).describe(
22266
- "nano-banana-2 = barato/r\xE1pido; nano-banana-pro = melhor qualidade; gpt-image-2 = OpenAI; gpt-image-1 = OpenAI, \xDANICO que faz fundo transparente de verdade (PNG/WebP com alpha) \u2014 o gpt-image-2 N\xC3O faz, use este pra recorte/sticker; seedream-5.0-lite = mais barato do cat\xE1logo, \xFAnico que chega a 4K, gera s\xE9ries e mant\xE9m o ESTILO da imagem de refer\xEAncia; seedream-5.0-pro = acabamento fotorrealista, s\xF3 at\xE9 2K \u2014 preserva o personagem da refer\xEAncia mas costuma reinterpretar o estilo (um vetor flat volta em 3D)."
22268
+ "nano-banana-2 = barato/r\xE1pido; nano-banana-pro = melhor qualidade; gpt-image-2 = OpenAI; gpt-image-1 = OpenAI, \xDANICO que faz fundo transparente de verdade (PNG/WebP com alpha) \u2014 o gpt-image-2 N\xC3O faz, use este pra recorte/sticker; seedream-5.0-lite = mais barato do cat\xE1logo, \xFAnico que chega a 4K, gera s\xE9ries e mant\xE9m o ESTILO da imagem de refer\xEAncia; seedream-5.0-pro = acabamento fotorrealista, s\xF3 at\xE9 2K (1K/1.5K/2K, propor\xE7\xE3o, tamanho livre e at\xE9 4 imagens por pedido) \u2014 preserva o personagem da refer\xEAncia mas costuma reinterpretar o estilo (um vetor flat volta em 3D)."
22269
+ ),
22270
+ aspect_ratio: external_exports.enum(ASPECT_RATIOS).optional().describe(
22271
+ "Propor\xE7\xE3o. nano-banana: padr\xE3o 1:1. Seedream Pro: combina com size (1K/1.5K/2K) e vira LARGURAxALTURA no backend; sem ela o modelo escolhe o formato pelo prompt."
22267
22272
  ),
22268
- aspect_ratio: external_exports.enum(ASPECT_RATIOS).optional().describe("Propor\xE7\xE3o (nano-banana). Padr\xE3o 1:1."),
22269
22273
  resolution: external_exports.enum(IMAGE_RESOLUTIONS).optional().describe("Resolu\xE7\xE3o (nano-banana): 1K/2K/4K. Padr\xE3o 1K."),
22270
22274
  reference_images: external_exports.array(external_exports.string().min(1)).max(16).optional().describe(
22271
22275
  "Imagens de refer\xEAncia (image-to-image): URL p\xFAblica OU caminho de arquivo local (o MCP faz upload pro R2 automaticamente). At\xE9 14 (nano-banana) / 16 (gpt-image-1/2)."
22272
22276
  ),
22273
22277
  google_search: external_exports.boolean().optional().describe("nano-banana-2: aterra a imagem em busca web do Google (fatos atuais). Padr\xE3o false."),
22274
22278
  image_search: external_exports.boolean().optional().describe("nano-banana-2: usa busca de imagens do Google como refer\xEAncia visual. Padr\xE3o false."),
22275
- size: external_exports.enum(["1024x1024", "1536x1024", "1024x1536", "auto", "1K", "2K", "3K", "4K"]).optional().describe(
22276
- "Tamanho. gpt-image-1/2: 1024x1024/1536x1024/1024x1536/auto. Seedream Lite: 2K/3K/4K (padr\xE3o 2K). Seedream Pro: 1K/2K apenas \u2014 o Pro N\xC3O faz 3K/4K."
22279
+ size: external_exports.union([
22280
+ external_exports.enum(["1024x1024", "1536x1024", "1024x1536", "auto", "1K", "1.5K", "2K", "3K", "4K"]),
22281
+ external_exports.string().regex(/^\d{3,5}x\d{3,5}$/, "use LARGURAxALTURA, ex.: 2048x1152")
22282
+ ]).optional().describe(
22283
+ "Tamanho. gpt-image-1/2: 1024x1024/1536x1024/1024x1536/auto. Seedream Lite: 2K/3K/4K (padr\xE3o 2K). Seedream Pro: 1K/1.5K/2K ou LARGURAxALTURA livre (\xE1rea entre 921.600 e 4.624.220 pixels, propor\xE7\xE3o de 1:16 a 16:1). O Pro N\xC3O faz 3K/4K."
22284
+ ),
22285
+ num_images: external_exports.number().int().min(1).max(SEEDREAM_PRO_MAX_IMAGES).optional().describe(
22286
+ "Seedream 5.0 PRO apenas: quantas imagens gerar no mesmo pedido (1-4), em chamadas paralelas. Cada imagem \xE9 cobrada; as que falharem s\xE3o estornadas."
22277
22287
  ),
22278
22288
  batch_images: external_exports.number().int().min(2).max(15).optional().describe(
22279
22289
  "Seedream 5.0 LITE apenas: gera uma s\xE9rie de imagens relacionadas numa chamada (2-15). Cada imagem \xE9 cobrada. O Seedream Pro N\xC3O suporta \u2014 passar aqui devolve erro."
@@ -22314,7 +22324,19 @@ function registerGenerationTools(server, client, cfg) {
22314
22324
  const isSeedream = isSeedreamLite || isSeedreamPro;
22315
22325
  if (isSeedream) {
22316
22326
  const allowed = isSeedreamPro ? SEEDREAM_PRO_SIZES : SEEDREAM_LITE_SIZES;
22317
- if (args.size && !allowed.includes(args.size)) {
22327
+ const free = args.size?.match(/^(\d+)x(\d+)$/);
22328
+ if (isSeedreamPro && free) {
22329
+ const w = Number(free[1]);
22330
+ const h = Number(free[2]);
22331
+ if (w * h < SEEDREAM_PRO_PIXELS.min || w * h > SEEDREAM_PRO_PIXELS.max) {
22332
+ return errorResult(
22333
+ `size ${args.size} d\xE1 ${w * h} pixels; o Pro aceita entre ${SEEDREAM_PRO_PIXELS.min} e ${SEEDREAM_PRO_PIXELS.max} (ex.: 1280x720 a 2048x2048).`
22334
+ );
22335
+ }
22336
+ if (w / h > 16 || h / w > 16) {
22337
+ return errorResult(`size ${args.size}: a propor\xE7\xE3o precisa ficar entre 1:16 e 16:1.`);
22338
+ }
22339
+ } else if (args.size && !allowed.includes(args.size)) {
22318
22340
  return errorResult(
22319
22341
  `${args.model} aceita size ${allowed.join("/")} (voc\xEA pediu ${args.size}). ` + (isSeedreamPro ? "O Pro n\xE3o faz 3K/4K \u2014 use o seedream-5.0-lite para resolu\xE7\xF5es maiores." : "O Lite n\xE3o faz 1K \u2014 use o seedream-5.0-pro se quiser a resolu\xE7\xE3o menor.")
22320
22342
  );
@@ -22336,6 +22358,11 @@ function registerGenerationTools(server, client, cfg) {
22336
22358
  } else if (args.batch_images) {
22337
22359
  return errorResult("batch_images s\xF3 existe no bytedance/seedream-5.0-lite.");
22338
22360
  }
22361
+ if (args.num_images && args.num_images > 1 && !isSeedreamPro) {
22362
+ return errorResult(
22363
+ "num_images s\xF3 existe no bytedance/seedream-5.0-pro. No seedream-5.0-lite use batch_images; nos outros modelos, chame generate_image mais vezes."
22364
+ );
22365
+ }
22339
22366
  if (args.background === "transparent") {
22340
22367
  if (args.model !== "openai/gpt-image-1") {
22341
22368
  return errorResult(
@@ -22365,6 +22392,7 @@ function registerGenerationTools(server, client, cfg) {
22365
22392
  // amigável exposto aqui.
22366
22393
  sequential_image_generation: isSeedreamLite && args.batch_images ? "auto" : void 0,
22367
22394
  max_images: isSeedreamLite && args.batch_images ? args.batch_images : void 0,
22395
+ num_images: isSeedreamPro && args.num_images && args.num_images > 1 ? args.num_images : void 0,
22368
22396
  quality: isMidjourney ? args.mj_quality : args.quality,
22369
22397
  background: args.background,
22370
22398
  moderation: args.moderation,
@@ -23004,7 +23032,7 @@ function probeWebp(b) {
23004
23032
  }
23005
23033
  function mp4Info(b) {
23006
23034
  let duration3;
23007
- let width = 0;
23035
+ let width2 = 0;
23008
23036
  let height = 0;
23009
23037
  let frames = 0;
23010
23038
  let mediaDuration;
@@ -23046,8 +23074,8 @@ function mp4Info(b) {
23046
23074
  const rotated = Math.abs(a) < 0.01 && Math.abs(d) < 0.01;
23047
23075
  const tw = Math.round(rotated ? h : w);
23048
23076
  const th = Math.round(rotated ? w : h);
23049
- if (tw * th > width * height) {
23050
- width = tw;
23077
+ if (tw * th > width2 * height) {
23078
+ width2 = tw;
23051
23079
  height = th;
23052
23080
  }
23053
23081
  }
@@ -23071,8 +23099,8 @@ function mp4Info(b) {
23071
23099
  };
23072
23100
  walk(0, b.length, 0);
23073
23101
  const dur = duration3 ?? mediaDuration;
23074
- const fps = width && height && dur && frames ? Math.round(frames / dur * 100) / 100 : void 0;
23075
- return { width, height, duration: dur, fps };
23102
+ const fps = width2 && height && dur && frames ? Math.round(frames / dur * 100) / 100 : void 0;
23103
+ return { width: width2, height, duration: dur, fps };
23076
23104
  }
23077
23105
  function probeMp4(b) {
23078
23106
  const info = mp4Info(b);
@@ -23100,9 +23128,9 @@ async function probeMedia(ref) {
23100
23128
  }
23101
23129
  if (!head.length) throw new ProbeError("Arquivo vazio.");
23102
23130
  if (kind === "image") {
23103
- const dim = probePng(head) ?? probeJpeg(head) ?? probeWebp(head);
23104
- if (!dim) throw new ProbeError("N\xE3o consegui ler as dimens\xF5es da imagem pelo cabe\xE7alho.");
23105
- return { kind, width: dim.width, height: dim.height };
23131
+ const dim2 = probePng(head) ?? probeJpeg(head) ?? probeWebp(head);
23132
+ if (!dim2) throw new ProbeError("N\xE3o consegui ler as dimens\xF5es da imagem pelo cabe\xE7alho.");
23133
+ return { kind, width: dim2.width, height: dim2.height };
23106
23134
  }
23107
23135
  let info = probeMp4(head);
23108
23136
  if (!info) {
@@ -23186,8 +23214,8 @@ function probeMp3(b) {
23186
23214
  function probeBuffer(b, mime) {
23187
23215
  const round = (n) => Math.round(n * 1e3) / 1e3;
23188
23216
  if (mime.startsWith("image/")) {
23189
- const dim = probePng(b) ?? probeJpeg(b) ?? probeWebp(b) ?? probeGif(b);
23190
- return dim ? { width: dim.width, height: dim.height } : null;
23217
+ const dim2 = probePng(b) ?? probeJpeg(b) ?? probeWebp(b) ?? probeGif(b);
23218
+ return dim2 ? { width: dim2.width, height: dim2.height } : null;
23191
23219
  }
23192
23220
  if (/^(video|audio)\/(mp4|quicktime|x-m4v|x-m4a|aac)$/.test(mime) || probeMp4Head(b)) {
23193
23221
  const info = mp4Info(b);
@@ -23316,14 +23344,14 @@ function registerUpscaleTools(server, client, cfg) {
23316
23344
  `"${variant}" n\xE3o \xE9 um modelo de ${isVideo ? "v\xEDdeo" : "imagem"}. Use um destes: ${allowed.join(", ")}.`
23317
23345
  );
23318
23346
  }
23319
- let width = args.source_width;
23347
+ let width2 = args.source_width;
23320
23348
  let height = args.source_height;
23321
23349
  let duration3 = args.source_duration;
23322
23350
  let fps = args.source_fps;
23323
- if (!width || !height || isVideo && !duration3) {
23351
+ if (!width2 || !height || isVideo && !duration3) {
23324
23352
  try {
23325
23353
  const probed = await probeMedia(args.file);
23326
- width = width ?? probed.width;
23354
+ width2 = width2 ?? probed.width;
23327
23355
  height = height ?? probed.height;
23328
23356
  duration3 = duration3 ?? probed.duration;
23329
23357
  fps = fps ?? probed.fps;
@@ -23358,7 +23386,7 @@ function registerUpscaleTools(server, client, cfg) {
23358
23386
  model: variant,
23359
23387
  upscale_factor: factor,
23360
23388
  prompt: args.prompt || label,
23361
- source_width: width,
23389
+ source_width: width2,
23362
23390
  source_height: height,
23363
23391
  source_duration: duration3,
23364
23392
  source_fps: fps,
@@ -23414,7 +23442,7 @@ ${footer}`);
23414
23442
  }
23415
23443
  const urls = outputUrls(status);
23416
23444
  const list = urls.map((u, i) => `${i + 1}. ${u}`).join("\n") || "(sem URLs)";
23417
- const saida = width && height ? ` \u2014 sa\xEDda ${width * factor} \xD7 ${height * factor}` : "";
23445
+ const saida = width2 && height ? ` \u2014 sa\xEDda ${width2 * factor} \xD7 ${height * factor}` : "";
23418
23446
  const semAudio = isVideo ? "\nO \xE1udio n\xE3o foi mantido (o Topaz descarta a faixa)." : "";
23419
23447
  return textResult(
23420
23448
  `\u2705 ${isVideo ? "V\xEDdeo" : "Imagem"} melhorado em ${factor}x com ${variant}${saida}.
@@ -23671,6 +23699,8 @@ Limites simult\xE2neos por usu\xE1rio: imagem=${m.limits.perUserMaxImage}, v\xED
23671
23699
  mode: external_exports.string().optional(),
23672
23700
  size: external_exports.string().optional(),
23673
23701
  quality: external_exports.string().optional(),
23702
+ aspect_ratio: external_exports.string().optional().describe("Seedream Pro: a propor\xE7\xE3o muda os pixels e a faixa de pre\xE7o."),
23703
+ num_images: external_exports.number().int().min(1).optional().describe("Seedream Pro: imagens por pedido; cada uma \xE9 cobrada."),
23674
23704
  // Upscale: o preço não sai de uma chave fixa — vem das medidas do
23675
23705
  // arquivo (megapixel de saída na imagem, bloco de 10s no vídeo). Sem
23676
23706
  // elas o backend responde pelo padrão de 1024²/10s, que não é o preço
@@ -23688,14 +23718,14 @@ Limites simult\xE2neos por usu\xE1rio: imagem=${m.limits.perUserMaxImage}, v\xED
23688
23718
  },
23689
23719
  async (args) => {
23690
23720
  try {
23691
- let width = args.source_width;
23721
+ let width2 = args.source_width;
23692
23722
  let height = args.source_height;
23693
23723
  let duration3 = args.source_duration;
23694
23724
  let fps = args.source_fps;
23695
- if (args.file && (!width || !height)) {
23725
+ if (args.file && (!width2 || !height)) {
23696
23726
  try {
23697
23727
  const probed = await probeMedia(args.file);
23698
- width = width ?? probed.width;
23728
+ width2 = width2 ?? probed.width;
23699
23729
  height = height ?? probed.height;
23700
23730
  duration3 = duration3 ?? probed.duration;
23701
23731
  fps = fps ?? probed.fps;
@@ -23712,9 +23742,11 @@ Limites simult\xE2neos por usu\xE1rio: imagem=${m.limits.perUserMaxImage}, v\xED
23712
23742
  mode: args.mode,
23713
23743
  size: args.size,
23714
23744
  quality: args.quality,
23745
+ aspect_ratio: args.aspect_ratio,
23746
+ num_images: args.num_images,
23715
23747
  upscale_factor: args.upscale_factor,
23716
23748
  model_variant: args.model_variant,
23717
- source_width: width,
23749
+ source_width: width2,
23718
23750
  source_height: height,
23719
23751
  source_duration: duration3,
23720
23752
  source_fps: fps
@@ -24397,11 +24429,14 @@ function isValidNode(key, v) {
24397
24429
  function isValidEdge(key, v) {
24398
24430
  return isRecord(v) && hasOnlyKeys(v, EDGE_KEYS) && v.id === key && typeof v.source === "string" && typeof v.sourcePort === "string" && typeof v.target === "string" && typeof v.targetPort === "string" && isJsonBag(v.data) && isStringOrNull(v.createdBy) && isFiniteNumber(v.createdAt);
24399
24431
  }
24432
+ var MEASURED_KEY = "__measured";
24400
24433
  function checkNodePath(path) {
24401
24434
  const segments = path.split(".");
24402
24435
  if (segments.some((s) => s.length === 0)) return `caminho inv\xE1lido "${path}"`;
24403
24436
  if (segments.some((s) => FORBIDDEN_SEGMENTS.includes(s))) return `segmento proibido em "${path}"`;
24404
24437
  const [root, ...rest] = segments;
24438
+ if (root === "data" && rest[0] === MEASURED_KEY)
24439
+ return `data.${MEASURED_KEY} \xE9 o tamanho medido pela tela do Canvas: s\xF3 o Canvas grava`;
24405
24440
  if (root === "data" && rest.length >= 1) return null;
24406
24441
  if (root === "position" && rest.length === 1 && ["x", "y"].includes(rest[0]) || root === "size" && rest.length === 1 && ["w", "h"].includes(rest[0])) return null;
24407
24442
  return `s\xF3 d\xE1 para editar data.*, position.x/y e size.w/h (recebido "${path}")`;
@@ -24549,6 +24584,120 @@ function packEnvelopes(boardId3, ops, actorId, limit = MAX_OP_BYTES - 1024) {
24549
24584
  return out;
24550
24585
  }
24551
24586
 
24587
+ // src/canvas/layout.ts
24588
+ function readMeasured(node) {
24589
+ const m = node.data[MEASURED_KEY];
24590
+ if (!m || typeof m !== "object" || Array.isArray(m)) return null;
24591
+ const { w, h, at } = m;
24592
+ if (typeof w !== "number" || typeof h !== "number" || !Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0) return null;
24593
+ return { w, h, at: typeof at === "string" ? at : null };
24594
+ }
24595
+ var SAME_WIDTH = 2;
24596
+ var WRAP_KINDS = /* @__PURE__ */ new Set(["prompt", "text", "block", "note", "label", "llm", "narration", "copy", "review", "shotlist"]);
24597
+ var SCALE_KINDS = /* @__PURE__ */ new Set(["image", "video", "media"]);
24598
+ var kindOf = (node) => typeof node.data[KIND_FIELD] === "string" ? node.data[KIND_FIELD] : "";
24599
+ function heightAt(kind, baseW, baseH, newW) {
24600
+ if (Math.abs(newW - baseW) <= SAME_WIDTH) return baseH;
24601
+ if (SCALE_KINDS.has(kind) && newW > baseW) return Math.ceil(baseH * newW / baseW);
24602
+ if (WRAP_KINDS.has(kind) && newW < baseW) return Math.ceil(baseH * baseW / newW);
24603
+ return baseH;
24604
+ }
24605
+ function usedSize(node, floorW = node.size.w) {
24606
+ const kind = kindOf(node);
24607
+ const m = readMeasured(node);
24608
+ if (!m) {
24609
+ const h = Math.max(node.size.h, heightAt(kind, node.size.w, node.size.h, floorW));
24610
+ return { w: floorW, h, source: "gravado", at: null, estimated: h !== node.size.h };
24611
+ }
24612
+ const keepsWidth = Math.abs(floorW - node.size.w) <= SAME_WIDTH;
24613
+ return {
24614
+ w: keepsWidth ? Math.max(floorW, m.w) : floorW,
24615
+ h: Math.max(node.size.h, heightAt(kind, m.w, m.h, floorW)),
24616
+ source: "medido",
24617
+ at: m.at,
24618
+ estimated: Math.abs(floorW - m.w) > SAME_WIDTH
24619
+ };
24620
+ }
24621
+ function sizeLabel(node) {
24622
+ const u = usedSize(node);
24623
+ const base = `${Math.round(u.w)}\xD7${Math.round(u.h)} ${u.source}`;
24624
+ if (u.source === "medido" && u.estimated) return `${base} (medido em ${Math.round(readMeasured(node).w)} de largura: altura estimada)`;
24625
+ return base;
24626
+ }
24627
+ var DEFAULT_WIDTHS = { prompt: 1024, image: 560, video: 560, reference: 320, frame: 320, title: 1024 };
24628
+ var DEFAULT_GAP = 80;
24629
+ var FRAME_PORTS = /* @__PURE__ */ new Set(["frame"]);
24630
+ var FEEDS = /* @__PURE__ */ new Set(["prompt", "image", "video"]);
24631
+ var outOf = (doc, id) => Object.values(doc.edges).filter((e) => e.source === id);
24632
+ function roleOf(doc, node, asTitle) {
24633
+ if (asTitle) return "title";
24634
+ const kind = kindOf(node);
24635
+ if (kind === "prompt" || kind === "video") return kind;
24636
+ if (kind !== "media" && kind !== "image") return "other";
24637
+ const feedsFrame = outOf(doc, node.id).some((e) => FRAME_PORTS.has(e.targetPort) && doc.nodes[e.target] && kindOf(doc.nodes[e.target]) === "video");
24638
+ if (feedsFrame) return "frame";
24639
+ return kind === "media" ? "reference" : "image";
24640
+ }
24641
+ function hangsRight(doc, node) {
24642
+ const kind = kindOf(node);
24643
+ if (kind !== "media" && kind !== "image") return false;
24644
+ const out = outOf(doc, node.id);
24645
+ if (!out.length) return kind === "media";
24646
+ return out.some((e) => doc.nodes[e.target] && FEEDS.has(kindOf(doc.nodes[e.target])));
24647
+ }
24648
+ var overlap = (a, b) => a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h;
24649
+ function arrangeRows(doc, rows, origin, gap = DEFAULT_GAP, widths = DEFAULT_WIDTHS) {
24650
+ const placed = [];
24651
+ const floorOf = (node, role) => role === "other" ? node.size.w : widths[role];
24652
+ const place = (id, row, asTitle, x, y2, alignRight) => {
24653
+ const node = doc.nodes[id];
24654
+ const role = roleOf(doc, node, asTitle);
24655
+ const floorW = floorOf(node, role);
24656
+ const u = usedSize(node, floorW);
24657
+ const w = Math.ceil(u.w);
24658
+ const h = Math.ceil(u.h);
24659
+ const px = alignRight !== void 0 ? alignRight - w : x;
24660
+ const p = { id, row, role, x: Math.round(px), y: Math.round(y2), w, h, source: u.source, estimated: u.estimated, floorW };
24661
+ placed.push(p);
24662
+ return p;
24663
+ };
24664
+ let y = origin.y;
24665
+ rows.forEach((row, r) => {
24666
+ if (row.title) {
24667
+ const t = place(row.title, r, true, origin.x, y);
24668
+ y = t.y + t.h + Math.round(gap / 2);
24669
+ }
24670
+ const top = y;
24671
+ const leftW = Math.max(0, ...row.left.map((id) => Math.ceil(usedSize(doc.nodes[id], floorOf(doc.nodes[id], roleOf(doc, doc.nodes[id], false))).w)));
24672
+ let leftEnd = top;
24673
+ row.left.forEach((id, i) => {
24674
+ const right2 = hangsRight(doc, doc.nodes[id]);
24675
+ const p = place(id, r, false, origin.x, i === 0 ? top : leftEnd + gap, right2 ? origin.x + leftW : void 0);
24676
+ leftEnd = p.y + p.h;
24677
+ });
24678
+ const rightX = origin.x + (row.left.length ? leftW + gap : 0);
24679
+ let rightEnd = top;
24680
+ row.right.forEach((id, i) => {
24681
+ const p = place(id, r, false, rightX, i === 0 ? top : rightEnd + gap);
24682
+ rightEnd = p.y + p.h;
24683
+ });
24684
+ y = Math.max(leftEnd, rightEnd, top) + gap * 2;
24685
+ });
24686
+ const inner = [];
24687
+ for (let i = 0; i < placed.length; i++) {
24688
+ for (let j = i + 1; j < placed.length; j++) if (overlap(placed[i], placed[j])) inner.push([placed[i].id, placed[j].id]);
24689
+ }
24690
+ const ids = new Set(placed.map((p) => p.id));
24691
+ const others = Object.values(doc.nodes).filter((n) => !ids.has(n.id) && kindOf(n) !== "section").map((n) => ({ id: n.id, x: n.position.x, y: n.position.y, ...usedSize(n) }));
24692
+ const outer = [];
24693
+ for (const p of placed) for (const o of others) if (overlap(p, o)) outer.push([p.id, o.id]);
24694
+ const left = Math.min(origin.x, ...placed.map((p) => p.x));
24695
+ const right = Math.max(origin.x, ...placed.map((p) => p.x + p.w));
24696
+ const below = others.filter((o) => o.x < right && left < o.x + o.w).map((o) => o.y + o.h);
24697
+ const freeOrigin = { x: origin.x, y: below.length ? Math.max(origin.y, Math.ceil(Math.max(...below) + gap * 2)) : origin.y };
24698
+ return { placed, inner, outer, freeOrigin };
24699
+ }
24700
+
24552
24701
  // src/canvas/summary.ts
24553
24702
  var MAX_TEXT = 160;
24554
24703
  var MAX_BLOCKS = 40;
@@ -24571,8 +24720,14 @@ var HEAVY_KEYS = /* @__PURE__ */ new Set([
24571
24720
  "settings",
24572
24721
  "enabledKeys",
24573
24722
  "checks",
24574
- "versions"
24723
+ "versions",
24724
+ MEASURED_KEY
24575
24725
  ]);
24726
+ function visibleData(data) {
24727
+ if (!Object.hasOwn(data, MEASURED_KEY)) return data;
24728
+ const { [MEASURED_KEY]: _measured, ...rest } = data;
24729
+ return rest;
24730
+ }
24576
24731
  var NAME_KEYS = /* @__PURE__ */ new Set([KIND_FIELD, "label", "nodeTitle"]);
24577
24732
  function oneLine(text, max = MAX_TEXT) {
24578
24733
  const flat = text.replace(/\s+/g, " ").trim();
@@ -24625,7 +24780,7 @@ function rowsLines(rows) {
24625
24780
  function nodeLines(node, run) {
24626
24781
  const kind = nodeKind(node);
24627
24782
  const spec = kind ? KINDS[kind] : void 0;
24628
- const head = `- ${node.id} \xB7 ${kind ?? node.type} \xB7 "${oneLine(nodeName(node), 60)}" @(${Math.round(node.position.x)},${Math.round(node.position.y)})`;
24783
+ const head = `- ${node.id} \xB7 ${kind ?? node.type} \xB7 "${oneLine(nodeName(node), 60)}" @(${Math.round(node.position.x)},${Math.round(node.position.y)}) ${sizeLabel(node)}`;
24629
24784
  const facts = [];
24630
24785
  const extra = [];
24631
24786
  const d = node.data;
@@ -24690,7 +24845,7 @@ function boardSummary(snap) {
24690
24845
  lines.push(`IA Lab: vinculado${label ? ` a ${String(label)}` : ""}.`);
24691
24846
  }
24692
24847
  if (doc.meta?.guide) lines.push(`Guia: ${oneLine(doc.meta.guide)}`);
24693
- lines.push("", "N\xF3s:");
24848
+ lines.push("", "N\xF3s (tamanho: medido = como est\xE1 na tela; gravado = piso, o cart\xE3o pode ser maior):");
24694
24849
  if (!nodes.length) lines.push("(vazio)");
24695
24850
  for (const n of nodes) lines.push(...nodeLines(n, runs.get(n.id)));
24696
24851
  lines.push("", "Liga\xE7\xF5es:");
@@ -24734,12 +24889,28 @@ function nodeDetail(node) {
24734
24889
  }
24735
24890
  return v;
24736
24891
  };
24737
- const json2 = JSON.stringify(strip(node.data, 0, ""));
24892
+ const json2 = JSON.stringify(strip(visibleData(node.data), 0, ""));
24738
24893
  const note = cuts ? `
24739
24894
  (${cuts} corte(s) marcado(s) com path=\u2026: leia inteiro com canvas_read_node e esse path)` : "";
24740
- return `${node.id} \xB7 ${nodeKind(node) ?? node.type} \xB7 "${nodeName(node)}"
24895
+ return `${node.id} \xB7 ${nodeKind(node) ?? node.type} \xB7 "${nodeName(node)}" \xB7 ${sizeLabel(node)}
24741
24896
  ${json2}${note}`;
24742
24897
  }
24898
+ var isMeasureNoise = (op) => op.type === "node.set" && typeof op.path === "string" && (op.path === `data.${MEASURED_KEY}` || op.path.startsWith(`data.${MEASURED_KEY}.`));
24899
+ function withoutMeasureNoise(envelopes) {
24900
+ const clean = (op) => {
24901
+ if (op.type === "batch" && Array.isArray(op.ops)) {
24902
+ const ops = op.ops.map((o) => clean(o)).filter((o) => o !== null);
24903
+ return ops.length ? { ...op, ops } : null;
24904
+ }
24905
+ return isMeasureNoise(op) ? null : op;
24906
+ };
24907
+ const out = [];
24908
+ for (const e of envelopes) {
24909
+ const op = clean(e.op);
24910
+ if (op) out.push({ ...e, op });
24911
+ }
24912
+ return out;
24913
+ }
24743
24914
  function changesSince(envelopes, doc) {
24744
24915
  const count = /* @__PURE__ */ new Map();
24745
24916
  for (const e of envelopes) {
@@ -25049,6 +25220,158 @@ Para aplicar: leia o n\xF3 com canvas_read_board (node_ids), edite pelas canvas_
25049
25220
  );
25050
25221
  }
25051
25222
 
25223
+ // src/tools/canvas-layout.ts
25224
+ var nodeIdArg = external_exports.string().min(1).max(128);
25225
+ var width = external_exports.number().finite().min(40).max(2e4);
25226
+ var ROLE_TEXT = {
25227
+ title: "t\xEDtulo",
25228
+ prompt: "prompt",
25229
+ image: "imagem",
25230
+ video: "v\xEDdeo",
25231
+ reference: "ref",
25232
+ frame: "frame",
25233
+ other: "outro"
25234
+ };
25235
+ function checkRows(doc, rows) {
25236
+ const seen = /* @__PURE__ */ new Set();
25237
+ for (const [i, row] of rows.entries()) {
25238
+ const ids = [...row.title ? [row.title] : [], ...row.left, ...row.right];
25239
+ if (!ids.length) throw new CanvasToolError(`rows[${i}] est\xE1 vazia: informe title, left ou right.`);
25240
+ for (const id of ids) {
25241
+ const node = doc.nodes[id];
25242
+ if (!node) throw new CanvasToolError(`rows[${i}]: o n\xF3 ${id} n\xE3o existe no board. Releia com canvas_read_board.`);
25243
+ if (node.data[KIND_FIELD] === "section") throw new CanvasToolError(`rows[${i}]: ${id} \xE9 uma Se\xE7\xE3o (fundo), n\xE3o entra no arranjo.`);
25244
+ if (seen.has(id)) throw new CanvasToolError(`O n\xF3 ${id} aparece duas vezes nas linhas: cada n\xF3 entra uma vez s\xF3.`);
25245
+ seen.add(id);
25246
+ }
25247
+ }
25248
+ }
25249
+ function layoutOps(doc, a) {
25250
+ const ops = [];
25251
+ for (const p of a.placed) {
25252
+ const node = doc.nodes[p.id];
25253
+ const set = (path, now, next) => {
25254
+ if (now !== next) ops.push({ type: "node.set", nodeId: p.id, path, value: setValue(next) });
25255
+ };
25256
+ set("position.x", node.position.x, p.x);
25257
+ set("position.y", node.position.y, p.y);
25258
+ set("size.w", node.size.w, p.floorW);
25259
+ }
25260
+ return ops;
25261
+ }
25262
+ function report(doc, a) {
25263
+ const name = (id) => `"${oneLine(nodeName(doc.nodes[id]), 36)}"`;
25264
+ const lines = [];
25265
+ const unmeasured = a.placed.filter((p) => p.source === "gravado");
25266
+ const estimated = a.placed.filter((p) => p.source === "medido" && p.estimated);
25267
+ if (unmeasured.length || estimated.length) {
25268
+ lines.push("A conferir (a altura usada pode n\xE3o ser a da tela):");
25269
+ if (unmeasured.length) {
25270
+ lines.push(
25271
+ `- sem medida da tela (usei o gravado): o cart\xE3o est\xE1 no piso, e a\xED o gravado \xE9 o da tela, ou o board n\xE3o abriu desde a \xFAltima mudan\xE7a, e a\xED pode ser maior: ${unmeasured.map((p) => name(p.id)).join(", ")}`
25272
+ );
25273
+ }
25274
+ if (estimated.length) {
25275
+ const how = estimated.map((p) => `${name(p.id)} (medido com ${Math.round(readMeasured(doc.nodes[p.id]).w)} de largura)`);
25276
+ lines.push(`- altura estimada com folga porque a largura mudou: ${how.join(", ")}`);
25277
+ }
25278
+ lines.push(
25279
+ "Se o board n\xE3o abriu desde a \xFAltima mudan\xE7a, abra (o Canvas mede o cart\xE3o que passa do piso) e rode o mesmo canvas_arrange_rows de novo para fechar sobra ou encosto."
25280
+ );
25281
+ } else {
25282
+ lines.push("Todos os n\xF3s arrumados t\xEAm medida da tela.");
25283
+ }
25284
+ const pairs = (list) => list.map(([x, y]) => `${name(x)} \xD7 ${name(y)}`).join("; ");
25285
+ lines.push(
25286
+ a.inner.length || a.outer.length ? `Sobreposi\xE7\xF5es com os tamanhos usados: ${pairs([...a.inner, ...a.outer])}.` : "Sobreposi\xE7\xF5es com os tamanhos usados: nenhuma."
25287
+ );
25288
+ lines.push('Caixas ([papel] id "nome" @(x,y) largura\xD7altura usada, origem da altura):');
25289
+ const rows = /* @__PURE__ */ new Map();
25290
+ for (const p of a.placed) {
25291
+ const item = `[${ROLE_TEXT[p.role]}] ${p.id} ${name(p.id)} @(${p.x},${p.y}) ${p.w}\xD7${p.h} ${p.source}${p.estimated ? " estimado" : ""}`;
25292
+ rows.set(p.row, [...rows.get(p.row) ?? [], item]);
25293
+ }
25294
+ for (const [r, items] of rows) lines.push(`- linha ${r + 1}: ${items.join(" \xB7 ")}`);
25295
+ const resized = a.placed.filter((p) => p.floorW !== doc.nodes[p.id].size.w);
25296
+ if (resized.length) lines.push(`Largura (size.w): ${resized.map((p) => `${name(p.id)} ${doc.nodes[p.id].size.w}\u2192${p.floorW}`).join(", ")}.`);
25297
+ const titles = a.placed.filter((p) => p.role === "title" && doc.nodes[p.id].data[KIND_FIELD] === "label" && doc.nodes[p.id].data.size !== "titulo");
25298
+ if (titles.length) {
25299
+ lines.push(
25300
+ `T\xEDtulo sem o texto de t\xEDtulo: ${titles.map((p) => name(p.id)).join(", ")}. Troque com canvas_update_node size:"titulo" (o arranjo s\xF3 grava posi\xE7\xE3o e tamanho).`
25301
+ );
25302
+ }
25303
+ return lines.join("\n");
25304
+ }
25305
+ function registerCanvasLayoutTools(server, deps) {
25306
+ const { api, run, edit } = deps;
25307
+ server.registerTool(
25308
+ "canvas_arrange_rows",
25309
+ {
25310
+ title: "Canvas: arrumar em linhas",
25311
+ description: "Arruma n\xF3s em linhas, uma por vers\xE3o (revis\xE3o de imagem) ou por take (anima\xE7\xE3o), de cima para baixo a partir de origin: title em cima; \xE0 esquerda a coluna left (prompt; m\xEDdia/imagem que alimenta o prompt ou o gerador, como a refer\xEAncia e o frame de entrada, fica alinhada \xE0 direita embaixo dele); \xE0 direita a coluna right (gerador) com o topo no do prompt. Qualquer tipo entra em qualquer coluna. Usa o tamanho medido da tela (ou o gravado, avisando) e p\xF5e a largura pelo tipo real do n\xF3: prompt 1024, gerador de imagem 560, de v\xEDdeo 560, refer\xEAncia (m\xEDdia) 320, frame (m\xEDdia/imagem ligada na porta frame do v\xEDdeo) 320, t\xEDtulo 1024. Grava s\xF3 posi\xE7\xE3o e tamanho num passo, sem pedir releitura. Devolve as caixas, as sobreposi\xE7\xF5es com os tamanhos usados e os n\xF3s sem medida (a altura deles pode crescer quando o board abrir: rode de novo depois). Se a \xE1rea encostar em outro n\xF3 do board, n\xE3o grava nada e sugere uma origem livre. dry_run: true s\xF3 calcula.",
25312
+ inputSchema: {
25313
+ board_id: external_exports.string().uuid(),
25314
+ origin: external_exports.object({ x: external_exports.number().finite(), y: external_exports.number().finite() }).describe("Canto de cima \xE0 esquerda da \xE1rea."),
25315
+ rows: external_exports.array(
25316
+ external_exports.object({
25317
+ title: nodeIdArg.optional().describe("N\xF3 do t\xEDtulo da linha (Texto livre)."),
25318
+ left: external_exports.array(nodeIdArg).max(20).optional().describe("Coluna da esquerda, de cima para baixo: prompt, depois a refer\xEAncia."),
25319
+ right: external_exports.array(nodeIdArg).max(20).optional().describe("Coluna da direita: gerador(es).")
25320
+ }).strict()
25321
+ ).min(1).max(50),
25322
+ gap: external_exports.number().finite().min(0).max(1e3).optional().describe(`Espa\xE7o entre cart\xF5es (padr\xE3o ${DEFAULT_GAP}); o dobro entre linhas.`),
25323
+ widths: external_exports.object({
25324
+ prompt: width.optional(),
25325
+ image: width.optional(),
25326
+ video: width.optional(),
25327
+ generator: width.optional().describe("Atalho: image e video juntos."),
25328
+ reference: width.optional(),
25329
+ frame: width.optional(),
25330
+ title: width.optional()
25331
+ }).strict().optional().describe("Troca larguras da conven\xE7\xE3o (prompt 1024, image 560, video 560, reference 320, frame 320, title 1024)."),
25332
+ dry_run: external_exports.boolean().optional().describe("true: s\xF3 calcula e mostra, sem gravar.")
25333
+ },
25334
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
25335
+ },
25336
+ async (args) => run(async () => {
25337
+ const rows = args.rows.map((r) => ({ title: r.title, left: r.left ?? [], right: r.right ?? [] }));
25338
+ const { generator, ...own } = args.widths ?? {};
25339
+ const widths = { ...DEFAULT_WIDTHS, ...generator ? { image: generator, video: generator } : {}, ...own };
25340
+ const gap = args.gap ?? DEFAULT_GAP;
25341
+ const plan = (doc) => {
25342
+ checkRows(doc, rows);
25343
+ const a = arrangeRows(doc, rows, args.origin, gap, widths);
25344
+ return { a, ops: layoutOps(doc, a), text: report(doc, a) };
25345
+ };
25346
+ const count = (ops) => new Set(ops.map((o) => o.type === "node.set" ? o.nodeId : "")).size;
25347
+ const blocked = (a) => `A \xE1rea encosta em n\xF3s que n\xE3o est\xE3o no arranjo. Nada foi gravado. Origem livre sugerida: {"x":${a.freeOrigin.x},"y":${a.freeOrigin.y}} (abaixo de tudo nessa faixa), ou tire esses n\xF3s do caminho.`;
25348
+ if (args.dry_run) {
25349
+ const { a, ops, text: text2 } = plan((await api.snapshot(args.board_id)).doc);
25350
+ const head = `Pr\xE9via, nada gravado: ${ops.length ? `${count(ops)} n\xF3(s) mudariam de lugar ou largura` : "tudo j\xE1 est\xE1 assim"}.`;
25351
+ return [head, ...a.outer.length ? [blocked(a)] : [], text2].join("\n");
25352
+ }
25353
+ let text = "";
25354
+ const done = await edit(
25355
+ args.board_id,
25356
+ async (doc) => {
25357
+ const p = plan(doc);
25358
+ text = p.text;
25359
+ if (p.a.outer.length) throw new CanvasToolError(`${blocked(p.a)}
25360
+ ${p.text}`);
25361
+ return {
25362
+ ops: p.ops,
25363
+ targets: [],
25364
+ done: `Arranjo: ${rows.length} linha(s), ${p.a.placed.length} n\xF3(s), ${count(p.ops)} mudaram de lugar ou largura`
25365
+ };
25366
+ },
25367
+ { split: true, layoutOnly: true }
25368
+ );
25369
+ return `${done}
25370
+ ${text}`;
25371
+ })
25372
+ );
25373
+ }
25374
+
25052
25375
  // src/canvas/upload.ts
25053
25376
  import { randomUUID as randomUUID2 } from "crypto";
25054
25377
  import { basename as basename2, extname as extname3 } from "path";
@@ -25257,6 +25580,137 @@ ${payload(u)}`
25257
25580
  );
25258
25581
  }
25259
25582
 
25583
+ // src/canvas/mentions.ts
25584
+ var VIA_MENTION = "viaMention";
25585
+ var PROMPT_REFS_INPUT = "refs";
25586
+ var PROMPT_MENTION_REFS_INPUT = "mentionRefs";
25587
+ var MENTION_KEYS = ["template", "fields", "mentions"];
25588
+ var VARIABLE_SOURCE = String.raw`\{\{[ \t]*([\p{L}\p{N}_-]+)[ \t]*\}\}`;
25589
+ function extractVariables(text) {
25590
+ const names = /* @__PURE__ */ new Set();
25591
+ for (const m of text.matchAll(new RegExp(VARIABLE_SOURCE, "gu"))) if (m[1]) names.add(m[1]);
25592
+ return [...names];
25593
+ }
25594
+ function allVariableNames(data) {
25595
+ const names = new Set(extractVariables(typeof data.template === "string" ? data.template : ""));
25596
+ const fields = data.fields;
25597
+ if (fields && typeof fields === "object" && !Array.isArray(fields)) {
25598
+ for (const [id, text] of Object.entries(fields)) {
25599
+ if (id !== "" && typeof text === "string" && text !== "") for (const n of extractVariables(text)) names.add(n);
25600
+ }
25601
+ }
25602
+ return [...names];
25603
+ }
25604
+ function normalizeMentions(value) {
25605
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
25606
+ const out = {};
25607
+ for (const [name, entry] of Object.entries(value)) {
25608
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
25609
+ const { node, port } = entry;
25610
+ if (name.trim() !== "" && typeof node === "string" && node !== "" && typeof port === "string" && port !== "") {
25611
+ out[name] = { node, port };
25612
+ }
25613
+ }
25614
+ return out;
25615
+ }
25616
+ function mentionEdgeId(e) {
25617
+ return `mention:${e.source}:${e.sourcePort}>${e.target}:${e.targetPort}`;
25618
+ }
25619
+ var edgeKey = (e) => `${e.source}\0${e.sourcePort}\0${e.target}\0${e.targetPort}`;
25620
+ var isViaMention = (e) => e.data[VIA_MENTION] === true;
25621
+ function reachability(doc) {
25622
+ const adj = /* @__PURE__ */ new Map();
25623
+ const link = (s, t) => adj.set(s, (adj.get(s) ?? /* @__PURE__ */ new Set()).add(t));
25624
+ for (const e of Object.values(doc.edges)) link(e.source, e.target);
25625
+ const reaches = (from, to) => {
25626
+ const seen = /* @__PURE__ */ new Set();
25627
+ const stack = [from];
25628
+ while (stack.length) {
25629
+ for (const next of adj.get(stack.pop()) ?? []) {
25630
+ if (next === to) return true;
25631
+ if (!seen.has(next)) {
25632
+ seen.add(next);
25633
+ stack.push(next);
25634
+ }
25635
+ }
25636
+ }
25637
+ return false;
25638
+ };
25639
+ return { wouldCycle: (s, t) => s === t || reaches(t, s), accept: link };
25640
+ }
25641
+ function originHasPort(doc, origin) {
25642
+ const node = doc.nodes[origin.node];
25643
+ if (!node) return false;
25644
+ const kind = typeof node.data[KIND_FIELD] === "string" ? node.data[KIND_FIELD] : null;
25645
+ if (!kind || !KINDS[kind]) return false;
25646
+ return knownPort(kind, "out", origin.port) !== false;
25647
+ }
25648
+ function mentionEdgeOps(doc, promptIds, actorId, now = Date.now()) {
25649
+ const out = { ops: [], added: [], removed: [], skipped: [] };
25650
+ const reach = reachability(doc);
25651
+ const edges = Object.values(doc.edges);
25652
+ const desired = /* @__PURE__ */ new Map();
25653
+ const managed = [];
25654
+ for (const id of [...new Set(promptIds)]) {
25655
+ const prompt = doc.nodes[id];
25656
+ if (!prompt || prompt.data[KIND_FIELD] !== "prompt") continue;
25657
+ const into = edges.filter((e) => e.target === id);
25658
+ const vars = new Set(allVariableNames(prompt.data));
25659
+ const mentions2 = normalizeMentions(prompt.data.mentions);
25660
+ const mentionPorts = new Set(into.filter(isViaMention).map((e) => e.targetPort));
25661
+ managed.push(...into.filter((e) => isViaMention(e) && e.targetPort !== PROMPT_REFS_INPUT && e.targetPort !== PROMPT_MENTION_REFS_INPUT));
25662
+ for (const port of /* @__PURE__ */ new Set([...vars, ...mentionPorts])) {
25663
+ if (port === PROMPT_REFS_INPUT || port === PROMPT_MENTION_REFS_INPUT) continue;
25664
+ const origin = vars.has(port) ? mentions2[port] : void 0;
25665
+ if (!origin) continue;
25666
+ if (into.some((e) => e.targetPort === port && !isViaMention(e))) {
25667
+ out.skipped.push(`{{${port}}}: a porta j\xE1 recebe uma liga\xE7\xE3o feita \xE0 m\xE3o`);
25668
+ continue;
25669
+ }
25670
+ if (!doc.nodes[origin.node]) {
25671
+ out.skipped.push(`{{${port}}}: o n\xF3 de origem ${origin.node} n\xE3o existe`);
25672
+ continue;
25673
+ }
25674
+ if (!originHasPort(doc, origin)) {
25675
+ out.skipped.push(`{{${port}}}: a porta "${origin.port}" n\xE3o existe em ${origin.node}`);
25676
+ continue;
25677
+ }
25678
+ if (reach.wouldCycle(origin.node, id)) {
25679
+ out.skipped.push(`{{${port}}}: a liga\xE7\xE3o fecharia um ciclo`);
25680
+ continue;
25681
+ }
25682
+ const link = { source: origin.node, sourcePort: origin.port, target: id, targetPort: port };
25683
+ desired.set(edgeKey(link), link);
25684
+ reach.accept(origin.node, id);
25685
+ }
25686
+ }
25687
+ for (const e of managed) {
25688
+ if (desired.has(edgeKey(e))) continue;
25689
+ out.ops.push({ type: "edge.remove", edgeId: e.id });
25690
+ out.removed.push(e);
25691
+ }
25692
+ for (const [key, link] of desired) {
25693
+ if (edges.some((e) => edgeKey(e) === key)) continue;
25694
+ const edgeId = mentionEdgeId(link);
25695
+ out.ops.push({
25696
+ type: "edge.add",
25697
+ edgeId,
25698
+ edge: { id: edgeId, ...link, data: { [VIA_MENTION]: true }, createdBy: actorId, createdAt: now }
25699
+ });
25700
+ out.added.push(link);
25701
+ }
25702
+ return out;
25703
+ }
25704
+ function describeMentionSync(sync, name) {
25705
+ const parts = [];
25706
+ if (sync.added.length) {
25707
+ parts.push(`liga\xE7\xE3o de men\xE7\xE3o criada: ${sync.added.map((l) => `${name(l.source)}.${l.sourcePort} \u2192 {{${l.targetPort}}}`).join(", ")}`);
25708
+ }
25709
+ if (sync.removed.length) parts.push(`liga\xE7\xE3o de men\xE7\xE3o removida: ${sync.removed.map((e) => `{{${e.targetPort}}}`).join(", ")}`);
25710
+ if (sync.skipped.length) parts.push(`men\xE7\xE3o sem liga\xE7\xE3o: ${sync.skipped.join("; ")}`);
25711
+ return parts.length ? ` ${parts.join(". ")}.` : "";
25712
+ }
25713
+
25260
25714
  // src/tools/canvas.ts
25261
25715
  var REJECT_TEXT = {
25262
25716
  "node-missing": "o n\xF3 n\xE3o existe mais (algu\xE9m apagou)",
@@ -25265,6 +25719,7 @@ var REJECT_TEXT = {
25265
25719
  "too-large": "opera\xE7\xE3o grande demais (64 KB) ou com m\xEDdia embutida"
25266
25720
  };
25267
25721
  var READ = "Exige canvas_read_board nesta sess\xE3o (e de novo depois de uma recusa). ";
25722
+ var MENTION_NOTE = 'Prompt: {{var}} no template + data.mentions {var: {node, port}} (o !! de um Bloco \xE9 port "text") j\xE1 cria, no mesmo passo, a liga\xE7\xE3o fina de men\xE7\xE3o que o Canvas desenha; tirar a var ou a men\xE7\xE3o tira a liga\xE7\xE3o. ';
25268
25723
  var MONTAGE_NOTE = "Montagem: musicGainDb, duckingDb e fadeMs valem s\xF3 para a montagem autom\xE1tica (clipes ligados) e n\xE3o mexem na timeline do editor (data.timeline; data.sequence \xE9 o legado); o volume dela fica nos clipes (gainDb, gainKeyframes, fades) e nas faixas (gainDb de cada faixa, master).";
25269
25724
  function dataSegments(path) {
25270
25725
  const p = path?.trim() ?? "";
@@ -25283,8 +25738,26 @@ function legacySequenceGuard(node, key) {
25283
25738
  var SECTION_Z = -1e3;
25284
25739
  var GAP = 80;
25285
25740
  var boardId2 = external_exports.string().uuid().describe("Id (uuid) do board, como sai em canvas_list_boards.");
25286
- var nodeIdArg = external_exports.string().min(1).max(128);
25741
+ var nodeIdArg2 = external_exports.string().min(1).max(128);
25287
25742
  var point = external_exports.object({ x: external_exports.number().finite(), y: external_exports.number().finite() });
25743
+ var LABEL_TEXT_SIZES = ["titulo", "subtitulo", "corpo"];
25744
+ var dim = external_exports.number().finite().min(16).max(2e4);
25745
+ var sizeArg = external_exports.union([external_exports.object({ w: dim.optional(), h: dim.optional() }).strict(), external_exports.enum(LABEL_TEXT_SIZES)]).describe(
25746
+ '{w, h}: caixa do n\xF3 (piso: o cart\xE3o cresce com o conte\xFAdo; um lado s\xF3 vale). "titulo" | "subtitulo" | "corpo": tamanho do texto do Texto livre (label), gravado em data.size.'
25747
+ );
25748
+ var isLayoutOp = (op) => op.type === "batch" ? op.ops.every(isLayoutOp) : op.type === "node.set" && /^(position\.[xy]|size\.[wh])$/.test(op.path);
25749
+ function textSizeFor(kind, size) {
25750
+ if (kind !== "label") {
25751
+ throw new CanvasToolError(
25752
+ `size "${size}" \xE9 o tamanho do texto do Texto livre (label); para ${kind ?? "este n\xF3"} use size {w, h}.`
25753
+ );
25754
+ }
25755
+ return size;
25756
+ }
25757
+ function boxOf(size) {
25758
+ if (size.w === void 0 && size.h === void 0) throw new CanvasToolError("size {w, h}: informe w, h ou os dois.");
25759
+ return size;
25760
+ }
25288
25761
  function registerCanvasTools(server, client, cfg) {
25289
25762
  const api = new CanvasApi(client, cfg);
25290
25763
  const seen = /* @__PURE__ */ new Map();
@@ -25308,7 +25781,7 @@ function registerCanvasTools(server, client, cfg) {
25308
25781
  }
25309
25782
  async function edit(id, build, opts = {}) {
25310
25783
  const last = seen.get(id);
25311
- if (last === void 0) {
25784
+ if (last === void 0 && !opts.layoutOnly) {
25312
25785
  throw new CanvasToolError("Leia o board com canvas_read_board antes de editar (a leitura vale para a sess\xE3o).");
25313
25786
  }
25314
25787
  const snap = await api.snapshot(id);
@@ -25316,10 +25789,11 @@ function registerCanvasTools(server, client, cfg) {
25316
25789
  const doc = snap.doc;
25317
25790
  const { ops, done, targets } = await build(doc, snap);
25318
25791
  if (!ops.length) return `Nada a mudar. ${done}`;
25792
+ if (opts.layoutOnly && !ops.every(isLayoutOp)) throw new Error("layout_only com opera\xE7\xE3o fora de posi\xE7\xE3o/tamanho");
25319
25793
  let note = "";
25320
- if (snap.seq > last) {
25794
+ if (!opts.layoutOnly && last !== void 0 && snap.seq > last) {
25321
25795
  const since = await api.opsAfter(id, last);
25322
- const theirs = since?.filter((e) => e.clientId !== CLIENT_ID) ?? null;
25796
+ const theirs = since === null ? null : withoutMeasureNoise(since.filter((e) => e.clientId !== CLIENT_ID));
25323
25797
  if (theirs === null) {
25324
25798
  seen.delete(id);
25325
25799
  throw new CanvasToolError(
@@ -25337,21 +25811,21 @@ function registerCanvasTools(server, client, cfg) {
25337
25811
  if (theirs.length) note = `
25338
25812
  Aviso: ${changesSince(theirs, doc)}.`;
25339
25813
  }
25340
- if (opts.split) return commitSplit(id, ops, done, note);
25814
+ if (opts.split) return commitSplit(id, ops, done, note, !!opts.layoutOnly);
25341
25815
  const env = envelope(id, batchOf(ops), await api.me());
25342
25816
  const bad = checkEnvelope(env);
25343
25817
  if (bad) throw new CanvasToolError(`Edi\xE7\xE3o recusada antes de enviar: ${bad}.`);
25344
25818
  const res = await api.commit(id, [env]);
25345
25819
  const outcome = res.outcomes.find((o) => o.opId === env.id);
25346
25820
  if (!outcome || "rejected" in outcome) {
25347
- seen.delete(id);
25821
+ if (!opts.layoutOnly) seen.delete(id);
25348
25822
  const why = outcome && "rejected" in outcome ? REJECT_TEXT[outcome.rejected] ?? outcome.rejected : "sem resposta do servidor";
25349
25823
  throw new CanvasToolError(`O servidor recusou a edi\xE7\xE3o: ${why}. Nada foi aplicado. Releia com canvas_read_board.`);
25350
25824
  }
25351
- seen.set(id, res.seq);
25825
+ if (!opts.layoutOnly) seen.set(id, res.seq);
25352
25826
  return `\u2705 ${done} (seq ${res.seq}).${note}`;
25353
25827
  }
25354
- async function commitSplit(id, ops, done, note) {
25828
+ async function commitSplit(id, ops, done, note, layoutOnly = false) {
25355
25829
  let envs;
25356
25830
  try {
25357
25831
  envs = packEnvelopes(id, ops, await api.me());
@@ -25373,7 +25847,8 @@ Aviso: ${changesSince(theirs, doc)}.`;
25373
25847
  for (const o of res.outcomes) outcomes.set(o.opId, o);
25374
25848
  last = res.seq;
25375
25849
  }
25376
- const pathsOf = (op) => op.type === "batch" ? op.ops.flatMap(pathsOf) : op.type === "node.set" ? [op.path.replace(/^data\./, "")] : [op.type];
25850
+ const setNodes = new Set(ops.flatMap((op) => op.type === "batch" ? op.ops : [op]).flatMap((op) => op.type === "node.set" ? [op.nodeId] : []));
25851
+ const pathsOf = (op) => op.type === "batch" ? op.ops.flatMap(pathsOf) : op.type === "node.set" ? [`${setNodes.size > 1 ? `${op.nodeId}:` : ""}${op.path.replace(/^data\./, "")}`] : [op.type];
25377
25852
  let failed = 0;
25378
25853
  const lines = envs.map((env, i) => {
25379
25854
  const paths = pathsOf(env.op);
@@ -25384,14 +25859,14 @@ Aviso: ${changesSince(theirs, doc)}.`;
25384
25859
  return `- envelope ${i + 1}/${envs.length}: ${paths.length} caminho(s) (${span}), ${kb} KB \u2192 ${result}`;
25385
25860
  });
25386
25861
  if (failed) {
25387
- seen.delete(id);
25862
+ if (!layoutOnly) seen.delete(id);
25388
25863
  throw new CanvasToolError(
25389
25864
  `${done}: ${envs.length - failed} de ${envs.length} envelope(s) entraram, ${failed} recusado(s).
25390
25865
  ${lines.join("\n")}
25391
- Os aceitos ficam. Releia com canvas_read_board e canvas_read_node e reenvie s\xF3 os caminhos dos envelopes recusados.` + note
25866
+ ` + (layoutOnly ? "Os aceitos ficam. Rode de novo: posi\xE7\xE3o e tamanho n\xE3o pedem releitura." : "Os aceitos ficam. Releia com canvas_read_board e canvas_read_node e reenvie s\xF3 os caminhos dos envelopes recusados.") + note
25392
25867
  );
25393
25868
  }
25394
- if (last !== null) seen.set(id, last);
25869
+ if (last !== null && !layoutOnly) seen.set(id, last);
25395
25870
  return `\u2705 ${done} em ${envs.length} envelope(s) (seq at\xE9 ${last}).
25396
25871
  ${lines.join("\n")}${note}`;
25397
25872
  }
@@ -25445,11 +25920,53 @@ ${lines.join("\n")}${note}`;
25445
25920
  const out = {};
25446
25921
  for (const [k, v] of Object.entries(raw ?? {})) {
25447
25922
  if (k === KIND_FIELD) throw new CanvasToolError("data.kind n\xE3o muda: apague o n\xF3 e crie outro do tipo certo.");
25923
+ if (k === MEASURED_KEY) {
25924
+ throw new CanvasToolError(`data.${MEASURED_KEY} \xE9 o tamanho medido pela tela do Canvas: s\xF3 o Canvas grava. Para mudar a caixa, use size {w, h}.`);
25925
+ }
25448
25926
  if (!isJson(v)) throw new CanvasToolError(`data.${k} n\xE3o \xE9 JSON v\xE1lido.`);
25449
25927
  out[k] = v;
25450
25928
  }
25451
25929
  return out;
25452
25930
  }
25931
+ function changeOps(node, change) {
25932
+ const ops = [];
25933
+ const patch = parseData(change.data);
25934
+ for (const key of Object.keys(patch)) legacySequenceGuard(node, key);
25935
+ if (change.label) patch.label = change.label;
25936
+ let box;
25937
+ if (typeof change.size === "string") patch.size = textSizeFor(nodeKind(node), change.size);
25938
+ else if (change.size) box = boxOf(change.size);
25939
+ const data = { ...node.data };
25940
+ for (const [k, v] of Object.entries(patch)) {
25941
+ const path = `data.${k}`;
25942
+ if (v === null) {
25943
+ if (Object.hasOwn(node.data, k)) ops.push({ type: "node.set", nodeId: node.id, path, value: unsetValue() });
25944
+ delete data[k];
25945
+ } else if (JSON.stringify(node.data[k]) !== JSON.stringify(v)) {
25946
+ ops.push({ type: "node.set", nodeId: node.id, path, value: setValue(v) });
25947
+ data[k] = v;
25948
+ }
25949
+ }
25950
+ const set = (path, now, next) => {
25951
+ if (next !== void 0 && next !== now) ops.push({ type: "node.set", nodeId: node.id, path, value: setValue(next) });
25952
+ };
25953
+ set("position.x", node.position.x, change.position?.x);
25954
+ set("position.y", node.position.y, change.position?.y);
25955
+ set("size.w", node.size.w, box?.w);
25956
+ set("size.h", node.size.h, box?.h);
25957
+ const keys = ops.map((o) => o.type === "node.set" ? o.path.replace(/^data\./, "") : "");
25958
+ return { ops, keys, data, dataKeys: Object.keys(patch) };
25959
+ }
25960
+ async function mentionOps(doc, changed, newEdges = []) {
25961
+ const prompts = changed.filter((c) => nodeKind(c.node) === "prompt" && c.keys.some((k) => MENTION_KEYS.includes(k)));
25962
+ if (!prompts.length) return { ops: [], note: "" };
25963
+ const after = { ...doc, nodes: { ...doc.nodes }, edges: { ...doc.edges } };
25964
+ for (const c of changed) after.nodes[c.node.id] = { ...c.node, data: c.data };
25965
+ for (const e of newEdges) after.edges[e.id] = e;
25966
+ const sync = mentionEdgeOps(after, prompts.map((c) => c.node.id), await api.me());
25967
+ const name = (id) => after.nodes[id] ? `"${oneLine(nodeName(after.nodes[id]), 40)}"` : id;
25968
+ return { ops: sync.ops, note: describeMentionSync(sync, name) };
25969
+ }
25453
25970
  server.registerTool(
25454
25971
  "canvas_list_boards",
25455
25972
  {
@@ -25468,10 +25985,10 @@ ${lines.join("\n")}${note}`;
25468
25985
  "canvas_read_board",
25469
25986
  {
25470
25987
  title: "Canvas: ler board",
25471
- description: "L\xEA um board: n\xF3s (id, tipo, nome, campos principais), liga\xE7\xF5es e v\xEDnculo com o IA Lab, sem m\xEDdia. Obrigat\xF3rio antes de editar (vale para a sess\xE3o). node_ids devolve o data desses n\xF3s, com cada corte marcado (path=\u2026); o valor inteiro (ex.: data.sequence da Montagem) sai no canvas_read_node.",
25988
+ description: 'L\xEA um board: n\xF3s (id, tipo, nome, posi\xE7\xE3o, tamanho, campos principais), liga\xE7\xF5es e v\xEDnculo com o IA Lab, sem m\xEDdia. Tamanho "medido" \xE9 o da tela; "gravado" \xE9 o piso (o cart\xE3o cresce com o conte\xFAdo). Obrigat\xF3rio antes de editar (vale para a sess\xE3o). node_ids devolve o data desses n\xF3s, com cada corte marcado (path=\u2026); o valor inteiro (ex.: data.sequence da Montagem) sai no canvas_read_node.',
25472
25989
  inputSchema: {
25473
25990
  board_id: boardId2,
25474
- node_ids: external_exports.array(nodeIdArg).max(20).optional().describe("N\xF3s para ver o data (sem blobs, cortes marcados).")
25991
+ node_ids: external_exports.array(nodeIdArg2).max(20).optional().describe("N\xF3s para ver o data (sem blobs, cortes marcados).")
25475
25992
  },
25476
25993
  annotations: { readOnlyHint: true, openWorldHint: false }
25477
25994
  },
@@ -25493,7 +26010,7 @@ ${lines.join("\n")}${note}`;
25493
26010
  description: 'Devolve o data de UM n\xF3 sem corte nenhum, em JSON compacto, para conferir e fazer diff. Na Montagem, sem path, vem a timeline do editor: data.timeline, ou data.sequence (legado) quando o n\xF3 ainda n\xE3o tem timeline, e o cabe\xE7alho diz qual veio. path escolhe um peda\xE7o ("items" da M\xEDdia, "timeline.meta"); "data" traz o data todo. Diz o tamanho total e o sha256; acima de max_chars vem em partes expl\xEDcitas (offset). N\xE3o substitui o canvas_read_board, que continua obrigat\xF3rio antes de editar. ' + MONTAGE_NOTE,
25494
26011
  inputSchema: {
25495
26012
  board_id: boardId2,
25496
- node_id: nodeIdArg,
26013
+ node_id: nodeIdArg2,
25497
26014
  path: external_exports.string().trim().max(200).optional().describe('Caminho dentro do data, com pontos (ex.: "timeline.meta", "items.0"). "data" = o data todo.'),
25498
26015
  offset: external_exports.number().int().min(0).optional().describe("In\xEDcio da parte, em caracteres (padr\xE3o 0)."),
25499
26016
  max_chars: external_exports.number().int().min(1e3).max(6e4).optional().describe("Tamanho da parte (padr\xE3o 40000).")
@@ -25504,8 +26021,11 @@ ${lines.join("\n")}${note}`;
25504
26021
  const snap = await api.snapshot(board_id);
25505
26022
  const node = snap.doc.nodes[node_id];
25506
26023
  if (!node) throw new CanvasToolError(`N\xF3 ${node_id} n\xE3o existe no board. Releia com canvas_read_board.`);
25507
- let value = node.data;
26024
+ let value = visibleData(node.data);
25508
26025
  let segments = dataSegments(path);
26026
+ if (segments[0] === MEASURED_KEY) {
26027
+ throw new CanvasToolError(`data.${MEASURED_KEY} \xE9 interno do Canvas (tamanho medido da tela): ele sai no cabe\xE7alho desta leitura.`);
26028
+ }
25509
26029
  let which = "";
25510
26030
  if (!path?.trim() && nodeKind(node) === "montage") {
25511
26031
  if (node.data.timeline !== void 0) {
@@ -25522,9 +26042,9 @@ ${lines.join("\n")}${note}`;
25522
26042
  const here = value;
25523
26043
  value = Array.isArray(here) ? here[Number(seg)] : here && typeof here === "object" ? here[seg] : void 0;
25524
26044
  if (value === void 0) {
25525
- const at = segments.slice(0, i).join(".");
26045
+ const at2 = segments.slice(0, i).join(".");
25526
26046
  const keys = Array.isArray(here) ? `\xEDndices 0 a ${here.length - 1}` : here && typeof here === "object" ? Object.keys(here).join(", ") : "\u2014";
25527
- throw new CanvasToolError(`"${seg}" n\xE3o existe em data${at ? `.${at}` : ""}. Existe: ${keys || "\u2014"}.`);
26047
+ throw new CanvasToolError(`"${seg}" n\xE3o existe em data${at2 ? `.${at2}` : ""}. Existe: ${keys || "\u2014"}.`);
25528
26048
  }
25529
26049
  }
25530
26050
  const json2 = JSON.stringify(value);
@@ -25534,7 +26054,8 @@ ${lines.join("\n")}${note}`;
25534
26054
  const to = Math.min(from + size, total);
25535
26055
  const hash = createHash("sha256").update(json2).digest("hex").slice(0, 12);
25536
26056
  const where = `data${segments.length ? `.${segments.join(".")}` : ""}`;
25537
- const head = `${node.id} \xB7 ${nodeKind(node) ?? node.type} \xB7 "${oneLine(nodeName(node), 60)}" \xB7 board seq ${snap.seq} \xB7 ${where}${which}: ${total} caracteres (JSON compacto) \xB7 sha256 ${hash}`;
26057
+ const at = readMeasured(node)?.at;
26058
+ const head = `${node.id} \xB7 ${nodeKind(node) ?? node.type} \xB7 "${oneLine(nodeName(node), 60)}" \xB7 board seq ${snap.seq} \xB7 tamanho ${sizeLabel(node)}${at ? ` \xB7 medido em ${at}` : ""} \xB7 ${where}${which}: ${total} caracteres (JSON compacto) \xB7 sha256 ${hash}`;
25538
26059
  const part = from === 0 && to === total ? "Inteiro, abaixo." : `Parte: caracteres ${from} a ${to} de ${total}. ${to < total ? `Falta o resto: chame de novo com offset=${to}.` : "Esta \xE9 a \xFAltima parte."}`;
25539
26060
  return `${head}
25540
26061
  ${part}
@@ -25595,15 +26116,16 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
25595
26116
  "canvas_add_node",
25596
26117
  {
25597
26118
  title: "Canvas: adicionar n\xF3",
25598
- description: `Adiciona um n\xF3 (n\xE3o executa). ${READ}kind: ${kindList()}. data completa o padr\xE3o do tipo; label d\xE1 o nome. Sem position, nasce ao lado do n\xF3 ligado ou \xE0 direita do board. inputs/outputs j\xE1 ligam o n\xF3 no mesmo passo. ` + MONTAGE_NOTE,
26119
+ description: `Adiciona um n\xF3 (n\xE3o executa). ${READ}kind: ${kindList()}. data completa o padr\xE3o do tipo; label d\xE1 o nome. Sem position, nasce ao lado do n\xF3 ligado ou \xE0 direita do board. inputs/outputs j\xE1 ligam o n\xF3 no mesmo passo. ` + MENTION_NOTE + MONTAGE_NOTE,
25599
26120
  inputSchema: {
25600
26121
  board_id: boardId2,
25601
26122
  kind: external_exports.string().min(1).max(40),
25602
26123
  label: external_exports.string().trim().min(1).max(120).optional().describe("Nome do cart\xE3o (data.label)."),
25603
26124
  data: external_exports.record(external_exports.unknown()).optional().describe("Campos do data. Ex.: narration {blocks:[{id,text}]}, text {text}, prompt {template}."),
25604
26125
  position: point.optional(),
25605
- inputs: external_exports.array(external_exports.object({ from: nodeIdArg, from_port: external_exports.string().min(1), to_port: external_exports.string().min(1) })).max(20).optional().describe("Liga\xE7\xF5es que CHEGAM no n\xF3 novo: from.from_port \u2192 novo.to_port."),
25606
- outputs: external_exports.array(external_exports.object({ from_port: external_exports.string().min(1), to: nodeIdArg, to_port: external_exports.string().min(1) })).max(20).optional().describe("Liga\xE7\xF5es que SAEM do n\xF3 novo: novo.from_port \u2192 to.to_port.")
26126
+ size: sizeArg.optional(),
26127
+ inputs: external_exports.array(external_exports.object({ from: nodeIdArg2, from_port: external_exports.string().min(1), to_port: external_exports.string().min(1) })).max(20).optional().describe("Liga\xE7\xF5es que CHEGAM no n\xF3 novo: from.from_port \u2192 novo.to_port."),
26128
+ outputs: external_exports.array(external_exports.object({ from_port: external_exports.string().min(1), to: nodeIdArg2, to_port: external_exports.string().min(1) })).max(20).optional().describe("Liga\xE7\xF5es que SAEM do n\xF3 novo: novo.from_port \u2192 to.to_port.")
25607
26129
  },
25608
26130
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
25609
26131
  },
@@ -25614,10 +26136,16 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
25614
26136
  const me = await api.me();
25615
26137
  const data = { ...spec.data, ...parseData(args.data), [KIND_FIELD]: args.kind };
25616
26138
  if (args.label) data.label = args.label;
26139
+ let box = spec.size;
26140
+ if (typeof args.size === "string") data.size = textSizeFor(args.kind, args.size);
26141
+ else if (args.size) {
26142
+ const b = boxOf(args.size);
26143
+ box = { w: b.w ?? spec.size.w, h: b.h ?? spec.size.h };
26144
+ }
25617
26145
  const near = args.inputs?.[0] ? doc.nodes[args.inputs[0].from] : args.outputs?.[0] ? doc.nodes[args.outputs[0].to] : void 0;
25618
- const position = args.position ?? placeFor(doc, spec.size, near, args.inputs?.[0] ? "right" : "left");
26146
+ const position = args.position ?? placeFor(doc, box, near, args.inputs?.[0] ? "right" : "left");
25619
26147
  const zIndex = args.kind === "section" ? SECTION_Z : Object.values(doc.nodes).reduce((t, n) => Math.max(t, n.zIndex), 0) + 1;
25620
- const node = makeNode({ type: WORKFLOW_NODE_TYPE, data, position, size: spec.size, zIndex, createdBy: me });
26148
+ const node = makeNode({ type: WORKFLOW_NODE_TYPE, data, position, size: box, zIndex, createdBy: me });
25621
26149
  const links = [
25622
26150
  ...(args.inputs ?? []).map((l) => ({ source: l.from, sourcePort: l.from_port, target: node.id, targetPort: l.to_port })),
25623
26151
  ...(args.outputs ?? []).map((l) => ({ source: node.id, sourcePort: l.from_port, target: l.to, targetPort: l.to_port }))
@@ -25631,10 +26159,12 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
25631
26159
  }
25632
26160
  }
25633
26161
  const edges = edgeOps(doc, links, me, /* @__PURE__ */ new Set([node.id]));
26162
+ const drawn = edges.ops.flatMap((op) => op.type === "edge.add" ? [op.edge] : []);
26163
+ const mention = await mentionOps(doc, [{ node, data: node.data, keys: Object.keys(args.data ?? {}) }], drawn);
25634
26164
  return {
25635
- ops: [{ type: "node.add", nodeId: node.id, node }, ...edges.ops],
26165
+ ops: [{ type: "node.add", nodeId: node.id, node }, ...edges.ops, ...mention.ops],
25636
26166
  targets: links.map((l) => l.source === node.id ? l.target : l.source),
25637
- done: `N\xF3 ${args.kind} "${nodeName(node)}" criado: ${node.id}${edges.ops.length ? ` com ${edges.ops.length} liga\xE7\xE3o(\xF5es)` : ""}.${edges.warn}`
26167
+ done: `N\xF3 ${args.kind} "${nodeName(node)}" criado: ${node.id}${edges.ops.length ? ` com ${edges.ops.length} liga\xE7\xE3o(\xF5es)` : ""}.${edges.warn}${mention.note}`
25638
26168
  };
25639
26169
  })
25640
26170
  )
@@ -25643,48 +26173,90 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
25643
26173
  "canvas_update_node",
25644
26174
  {
25645
26175
  title: "Canvas: editar n\xF3",
25646
- description: `Muda campos do data de um n\xF3, chave a chave (null apaga a chave), sem executar. ${READ}label renomeia. N\xE3o troca o tipo. Cada chave \xE9 gravada inteira: leia antes com canvas_read_node e mande o valor completo j\xE1 revisado, para n\xE3o apagar o que outra pessoa escreveu. Para mudar partes de uma estrutura grande (clipes da timeline), use canvas_patch_node, por caminho. ` + MONTAGE_NOTE,
26176
+ description: `Muda campos do data de um n\xF3, chave a chave (null apaga a chave), sem executar. ${READ}label renomeia; position move; size {w, h} muda a caixa (piso: o cart\xE3o cresce com o conte\xFAdo). N\xE3o troca o tipo. Cada chave \xE9 gravada inteira: leia antes com canvas_read_node e mande o valor completo j\xE1 revisado, para n\xE3o apagar o que outra pessoa escreveu. Para mudar partes de uma estrutura grande (clipes da timeline), use canvas_patch_node, por caminho; v\xE1rios n\xF3s de uma vez, canvas_update_nodes. ` + MENTION_NOTE + MONTAGE_NOTE,
25647
26177
  inputSchema: {
25648
26178
  board_id: boardId2,
25649
- node_id: nodeIdArg,
26179
+ node_id: nodeIdArg2,
25650
26180
  data: external_exports.record(external_exports.unknown()).optional().describe("Chaves de data a gravar. Valor null apaga a chave."),
25651
26181
  label: external_exports.string().trim().min(1).max(120).optional(),
25652
- position: point.optional()
26182
+ position: point.optional(),
26183
+ size: sizeArg.optional()
25653
26184
  },
25654
26185
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
25655
26186
  },
25656
26187
  async (args) => run(
25657
26188
  () => edit(args.board_id, async (doc) => {
25658
26189
  const node = needNode(doc, args.node_id);
25659
- const ops = [];
25660
- const patch = parseData(args.data);
25661
- for (const key of Object.keys(patch)) legacySequenceGuard(node, key);
25662
- if (args.label) patch.label = args.label;
25663
- for (const [k, v] of Object.entries(patch)) {
25664
- const path = `data.${k}`;
25665
- if (v === null) {
25666
- if (Object.hasOwn(node.data, k)) ops.push({ type: "node.set", nodeId: node.id, path, value: unsetValue() });
25667
- } else if (JSON.stringify(node.data[k]) !== JSON.stringify(v)) {
25668
- ops.push({ type: "node.set", nodeId: node.id, path, value: setValue(v) });
25669
- }
25670
- }
25671
- if (args.position) {
25672
- ops.push({ type: "node.set", nodeId: node.id, path: "position.x", value: setValue(args.position.x) });
25673
- ops.push({ type: "node.set", nodeId: node.id, path: "position.y", value: setValue(args.position.y) });
25674
- }
25675
- const keys = ops.map((o) => o.type === "node.set" ? o.path.replace(/^data\./, "") : "").join(", ");
25676
- return { ops, targets: [node.id], done: `N\xF3 "${nodeName(node)}" atualizado: ${keys || "nada mudou"}` };
26190
+ const c = changeOps(node, args);
26191
+ const mention = await mentionOps(doc, [{ node, data: c.data, keys: c.dataKeys }]);
26192
+ return {
26193
+ ops: [...c.ops, ...mention.ops],
26194
+ targets: [node.id],
26195
+ done: `N\xF3 "${nodeName(node)}" atualizado: ${c.keys.join(", ") || "nada mudou"}${mention.note}`
26196
+ };
25677
26197
  })
25678
26198
  )
25679
26199
  );
26200
+ server.registerTool(
26201
+ "canvas_update_nodes",
26202
+ {
26203
+ title: "Canvas: editar v\xE1rios n\xF3s",
26204
+ description: 'Muda v\xE1rios n\xF3s num passo s\xF3, cada um como no canvas_update_node (position, size, label, data com null apagando). layout_only: true aceita s\xF3 position e size {w, h}, n\xE3o exige leitura e n\xE3o recusa por "outra pessoa mexeu" (nesses dois campos vale quem grava por \xFAltimo). Sem layout_only, exige canvas_read_board nesta sess\xE3o e recusa se outra pessoa mexeu em algum desses n\xF3s. Quebra em envelopes de at\xE9 64 KB e relata cada um. Para arrumar linhas inteiras (t\xEDtulo, prompt, refer\xEAncia, gerador), use canvas_arrange_rows.',
26205
+ inputSchema: {
26206
+ board_id: boardId2,
26207
+ nodes: external_exports.array(
26208
+ external_exports.object({
26209
+ node_id: nodeIdArg2,
26210
+ position: point.optional(),
26211
+ size: sizeArg.optional(),
26212
+ label: external_exports.string().trim().min(1).max(120).optional(),
26213
+ data: external_exports.record(external_exports.unknown()).optional().describe("Chaves de data a gravar. Valor null apaga a chave.")
26214
+ }).strict()
26215
+ ).min(1).max(200),
26216
+ layout_only: external_exports.boolean().optional().describe("true: s\xF3 position e size, sem releitura nem recusa por conflito.")
26217
+ },
26218
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
26219
+ },
26220
+ async (args) => run(
26221
+ () => edit(
26222
+ args.board_id,
26223
+ async (doc) => {
26224
+ const ids = args.nodes.map((n) => n.node_id);
26225
+ const dup = ids.find((id, i) => ids.indexOf(id) !== i);
26226
+ if (dup) throw new CanvasToolError(`O n\xF3 ${dup} aparece duas vezes: junte as mudan\xE7as dele num item s\xF3.`);
26227
+ if (args.layout_only) {
26228
+ const bad = args.nodes.find((n) => n.data !== void 0 || n.label !== void 0 || typeof n.size === "string");
26229
+ if (bad) {
26230
+ throw new CanvasToolError(
26231
+ `layout_only aceita s\xF3 position e size {w, h} (o n\xF3 ${bad.node_id} traz ${bad.data !== void 0 ? "data" : bad.label !== void 0 ? "label" : `size "${String(bad.size)}"`}). Tire layout_only para gravar data e label com a checagem de conflito.`
26232
+ );
26233
+ }
26234
+ }
26235
+ const changes = args.nodes.map((n) => {
26236
+ const node = needNode(doc, n.node_id);
26237
+ return { node, ...changeOps(node, n) };
26238
+ });
26239
+ const mention = args.layout_only ? { ops: [], note: "" } : await mentionOps(doc, changes.map((c) => ({ node: c.node, data: c.data, keys: c.dataKeys })));
26240
+ const touched = changes.filter((c) => c.ops.length);
26241
+ const list = touched.slice(0, 12).map((c) => `"${oneLine(nodeName(c.node), 30)}" (${c.keys.join(", ")})`).join("; ");
26242
+ return {
26243
+ ops: [...changes.flatMap((c) => c.ops), ...mention.ops],
26244
+ targets: ids,
26245
+ done: `${touched.length} de ${ids.length} n\xF3(s) atualizado(s)${args.layout_only ? " (layout_only)" : ""}` + (list ? `: ${list}${touched.length > 12 ? `; \u2026 +${touched.length - 12}` : ""}` : "") + mention.note
26246
+ };
26247
+ },
26248
+ { split: true, layoutOnly: !!args.layout_only }
26249
+ )
26250
+ )
26251
+ );
25680
26252
  server.registerTool(
25681
26253
  "canvas_patch_node",
25682
26254
  {
25683
26255
  title: "Canvas: gravar por caminho",
25684
- description: `Grava v\xE1rios caminhos dentro do data de um n\xF3, sem executar: {path, value} grava, {path, unset: true} apaga. ${READ}O caminho atravessa objetos (o n\xEDvel que falta \xE9 criado) e n\xE3o entra em lista: para mudar um item de lista, grave a lista inteira. Quebra sozinho em envelopes de at\xE9 64 KB, em ordem, e devolve o resultado de cada um; entre envelopes n\xE3o h\xE1 atomicidade (o que entrou fica). Serve para mexer em partes de uma estrutura grande, como clipes da timeline da Montagem, sem reenviar tudo. Leia antes com canvas_read_node. ` + MONTAGE_NOTE,
26256
+ description: `Grava v\xE1rios caminhos dentro do data de um n\xF3, sem executar: {path, value} grava, {path, unset: true} apaga. ${READ}O caminho atravessa objetos (o n\xEDvel que falta \xE9 criado) e n\xE3o entra em lista: para mudar um item de lista, grave a lista inteira. Quebra sozinho em envelopes de at\xE9 64 KB, em ordem, e devolve o resultado de cada um; entre envelopes n\xE3o h\xE1 atomicidade (o que entrou fica). Serve para mexer em partes de uma estrutura grande, como clipes da timeline da Montagem, sem reenviar tudo. Leia antes com canvas_read_node. ` + MENTION_NOTE + MONTAGE_NOTE,
25685
26257
  inputSchema: {
25686
26258
  board_id: boardId2,
25687
- node_id: nodeIdArg,
26259
+ node_id: nodeIdArg2,
25688
26260
  sets: external_exports.array(
25689
26261
  external_exports.object({
25690
26262
  path: external_exports.string().trim().min(1).max(500).describe('Caminho dentro do data (ex.: "timeline.meta.fps"); aceita o prefixo "data.".'),
@@ -25704,6 +26276,8 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
25704
26276
  const ops = [];
25705
26277
  let same = 0;
25706
26278
  let absent = 0;
26279
+ const mentionKeys = /* @__PURE__ */ new Set();
26280
+ let mentionAt = -1;
25707
26281
  for (const [i, item] of args.sets.entries()) {
25708
26282
  const at = `sets[${i}] (${item.path})`;
25709
26283
  const segs = dataSegments(item.path);
@@ -25713,6 +26287,7 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
25713
26287
  const path = `data.${segs.join(".")}`;
25714
26288
  const badPath = checkNodePath(path);
25715
26289
  if (badPath) throw new CanvasToolError(`${at}: ${badPath}.`);
26290
+ if (MENTION_KEYS.includes(segs[0])) mentionKeys.add(segs[0]);
25716
26291
  const unset = item.unset === true;
25717
26292
  const hasValue = item.value !== void 0;
25718
26293
  if (unset === hasValue) throw new CanvasToolError(`${at}: mande value OU unset: true.`);
@@ -25747,6 +26322,7 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
25747
26322
  }
25748
26323
  delete cur[leaf];
25749
26324
  ops.push({ type: "node.set", nodeId: node.id, path, value: unsetValue() });
26325
+ if (MENTION_KEYS.includes(segs[0])) mentionAt = ops.length;
25750
26326
  continue;
25751
26327
  }
25752
26328
  const value = item.value;
@@ -25756,12 +26332,16 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
25756
26332
  }
25757
26333
  if (cur) cur[leaf] = value;
25758
26334
  ops.push({ type: "node.set", nodeId: node.id, path, value: setValue(value) });
26335
+ if (MENTION_KEYS.includes(segs[0])) mentionAt = ops.length;
25759
26336
  }
25760
26337
  const skipped = [same ? `${same} igual(is) ao que j\xE1 estava` : "", absent ? `${absent} unset de caminho que n\xE3o existe` : ""].filter(Boolean).join(", ");
26338
+ const written = ops.length;
26339
+ const mention = await mentionOps(doc, [{ node, data: work, keys: [...mentionKeys] }]);
26340
+ ops.splice(mentionAt < 0 ? ops.length : mentionAt, 0, ...mention.ops);
25761
26341
  return {
25762
26342
  ops,
25763
26343
  targets: [node.id],
25764
- done: `N\xF3 "${oneLine(nodeName(node), 40)}" (${node.id}): ${ops.length} caminho(s) gravado(s)` + (skipped ? ` (ignorados: ${skipped})` : "")
26344
+ done: `N\xF3 "${oneLine(nodeName(node), 40)}" (${node.id}): ${written} caminho(s) gravado(s)` + (skipped ? ` (ignorados: ${skipped})` : "") + mention.note
25765
26345
  };
25766
26346
  },
25767
26347
  { split: true }
@@ -25775,9 +26355,9 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
25775
26355
  description: `Liga a sa\xEDda de um n\xF3 \xE0 entrada de outro (ids e portas de canvas_read_board). ${READ}Liga\xE7\xE3o repetida \xE9 ignorada.`,
25776
26356
  inputSchema: {
25777
26357
  board_id: boardId2,
25778
- source: nodeIdArg,
26358
+ source: nodeIdArg2,
25779
26359
  source_port: external_exports.string().min(1).max(80),
25780
- target: nodeIdArg,
26360
+ target: nodeIdArg2,
25781
26361
  target_port: external_exports.string().min(1).max(80)
25782
26362
  },
25783
26363
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
@@ -25802,8 +26382,8 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
25802
26382
  description: `Apaga n\xF3s (com as liga\xE7\xF5es e varia\xE7\xF5es deles) e/ou liga\xE7\xF5es, num passo s\xF3. Irrevers\xEDvel pelo MCP: confirme com o usu\xE1rio antes. ${READ}N\xE3o apaga o board.`,
25803
26383
  inputSchema: {
25804
26384
  board_id: boardId2,
25805
- node_ids: external_exports.array(nodeIdArg).max(200).optional(),
25806
- edge_ids: external_exports.array(nodeIdArg).max(200).optional()
26385
+ node_ids: external_exports.array(nodeIdArg2).max(200).optional(),
26386
+ edge_ids: external_exports.array(nodeIdArg2).max(200).optional()
25807
26387
  },
25808
26388
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
25809
26389
  },
@@ -25904,13 +26484,14 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
25904
26484
  );
25905
26485
  registerCanvasCommentTools(server, api, run);
25906
26486
  registerCanvasMediaTools(server, { api, cfg, run, edit, seen, placeFor });
26487
+ registerCanvasLayoutTools(server, { api, run, edit });
25907
26488
  }
25908
26489
 
25909
26490
  // src/version.ts
25910
26491
  import { createRequire } from "module";
25911
26492
  var require2 = createRequire(import.meta.url);
25912
26493
  function readVersion() {
25913
- if ("0.28.0") return "0.28.0";
26494
+ if ("0.30.0") return "0.30.0";
25914
26495
  try {
25915
26496
  const pkg = require2("../package.json");
25916
26497
  return pkg.version ?? "0.0.0-unknown";
@@ -25926,6 +26507,7 @@ var SERVER_INSTRUCTIONS = [
25926
26507
  'M\xEDdia: generate_* gasta diamantes; confira com estimate_cost antes de lotes grandes. Sem project, a gera\xE7\xE3o cai no projeto ativo; billing:"personal" cobra do usu\xE1rio.',
25927
26508
  "Calls: search_calls acha, get_call traz a transcri\xE7\xE3o.",
25928
26509
  "Canvas online (canvas_*): leia com canvas_read_board antes de editar e siga o estado lido. Cada edi\xE7\xE3o vai num passo s\xF3; se outra pessoa mexeu nos mesmos n\xF3s, a ferramenta recusa e pede releitura. As ferramentas canvas_* n\xE3o geram m\xEDdia nem gastam diamantes: quem roda os n\xF3s \xE9 o usu\xE1rio no Canvas. D\xEA nome (label) aos n\xF3s, n\xE3o invente tipos nem portas, e confirme antes de canvas_delete.",
26510
+ 'Arrumar o board: o size gravado \xE9 piso (o cart\xE3o cresce com o conte\xFAdo); use o tamanho "medido" do canvas_read_board. \xC1rea de revis\xE3o: canvas_arrange_rows (uma linha por vers\xE3o); posi\xE7\xE3o/tamanho em lote: canvas_update_nodes com layout_only. N\xE3o afirme que nada se sobrep\xF5e com n\xF3 sem medida.',
25929
26511
  '"Aplica os coment\xE1rios" do board: canvas_list_comments (abertos) \u2192 para cada um, leia o n\xF3 ancorado com canvas_read_board (node_ids), aplique a edi\xE7\xE3o pelas canvas_* e feche com canvas_resolve_comment dizendo em uma frase o que fez. Coment\xE1rio amb\xEDguo ou com alvo sumido: responda com pergunta (canvas_reply_comment) e n\xE3o resolva. D\xFAvida sua sobre um n\xF3: canvas_comment ancorado nele. As escritas j\xE1 saem espa\xE7adas (60/min); se ainda vier limite, pare e diga de onde seguir.'
25930
26512
  ].join("\n");
25931
26513
  function buildServer(cfg) {
@@ -26020,7 +26602,7 @@ function serversNaming(entry) {
26020
26602
  if (!servers || typeof servers !== "object") return [];
26021
26603
  return Object.entries(servers).filter(([name, value]) => name === "beplus" || JSON.stringify(value ?? null).includes("beplus-mcp")).map(([name]) => name);
26022
26604
  }
26023
- function checkConfigs(report) {
26605
+ function checkConfigs(report2) {
26024
26606
  let found = 0;
26025
26607
  for (const { path, client } of configCandidates()) {
26026
26608
  let raw;
@@ -26033,7 +26615,7 @@ function checkConfigs(report) {
26033
26615
  try {
26034
26616
  parsed = JSON.parse(raw);
26035
26617
  } catch {
26036
- report.line("aviso", `Config ilegivel: ${path}`, "O arquivo existe mas nao e JSON valido.");
26618
+ report2.line("aviso", `Config ilegivel: ${path}`, "O arquivo existe mas nao e JSON valido.");
26037
26619
  continue;
26038
26620
  }
26039
26621
  const names = new Set(serversNaming(parsed));
@@ -26043,25 +26625,25 @@ function checkConfigs(report) {
26043
26625
  }
26044
26626
  if (names.size === 0) continue;
26045
26627
  found += 1;
26046
- report.line("ok", `Config detectado: ${path}`, `${client}, servidor ${[...names].join(", ")}`);
26628
+ report2.line("ok", `Config detectado: ${path}`, `${client}, servidor ${[...names].join(", ")}`);
26047
26629
  }
26048
26630
  if (found === 0) {
26049
- report.line(
26631
+ report2.line(
26050
26632
  "aviso",
26051
26633
  "Nenhum config de cliente MCP com o beplus foi encontrado.",
26052
26634
  "Isso e normal se voce ainda nao instalou o servidor, ou se o cliente guarda o config em outro lugar."
26053
26635
  );
26054
26636
  }
26055
26637
  }
26056
- async function checkApiReachable(report, baseUrl) {
26638
+ async function checkApiReachable(report2, baseUrl) {
26057
26639
  const controller = new AbortController();
26058
26640
  const timer = setTimeout(() => controller.abort(), 1e4);
26059
26641
  try {
26060
26642
  const res = await fetch(`${baseUrl}/api/v1/mcp/me`, { signal: controller.signal });
26061
- report.line("ok", `API responde: ${baseUrl}`, `GET /api/v1/mcp/me sem token devolveu HTTP ${res.status}.`);
26643
+ report2.line("ok", `API responde: ${baseUrl}`, `GET /api/v1/mcp/me sem token devolveu HTTP ${res.status}.`);
26062
26644
  } catch (e) {
26063
26645
  const aborted2 = e?.name === "AbortError";
26064
- report.line(
26646
+ report2.line(
26065
26647
  "erro",
26066
26648
  `API nao respondeu: ${baseUrl}`,
26067
26649
  aborted2 ? "Tempo esgotado em 10s. Confira a rede, o proxy ou a VPN." : `Falha de rede: ${e?.message ?? "desconhecida"}. Confira a rede e o valor de BEPLUS_API_URL.`
@@ -26070,10 +26652,10 @@ async function checkApiReachable(report, baseUrl) {
26070
26652
  clearTimeout(timer);
26071
26653
  }
26072
26654
  }
26073
- async function checkToken(report) {
26655
+ async function checkToken(report2) {
26074
26656
  const token = process.env.BEPLUS_API_TOKEN?.trim();
26075
26657
  if (!token) {
26076
- report.line(
26658
+ report2.line(
26077
26659
  "aviso",
26078
26660
  "BEPLUS_API_TOKEN nao esta definido.",
26079
26661
  "Gere um Personal Access Token em Tokens de acesso, no painel BePlus, e coloque no config do MCP."
@@ -26082,46 +26664,46 @@ async function checkToken(report) {
26082
26664
  }
26083
26665
  try {
26084
26666
  const me = await new BeplusClient(loadConfig()).me();
26085
- report.line(
26667
+ report2.line(
26086
26668
  "ok",
26087
26669
  `Token valido (${maskToken(token)})`,
26088
26670
  `Conta: ${me.name ?? me.email ?? me.userId}, ${me.diamonds} diamantes.`
26089
26671
  );
26090
26672
  } catch (e) {
26091
26673
  if (e instanceof ApiError && e.httpStatus === 401) {
26092
- report.line(
26674
+ report2.line(
26093
26675
  "erro",
26094
26676
  `Token invalido ou revogado (${maskToken(token)})`,
26095
26677
  "Gere um novo Personal Access Token no painel BePlus e atualize BEPLUS_API_TOKEN no config do MCP."
26096
26678
  );
26097
26679
  return;
26098
26680
  }
26099
- report.line("aviso", "Nao deu para validar o token agora.", e?.message ?? "erro desconhecido");
26681
+ report2.line("aviso", "Nao deu para validar o token agora.", e?.message ?? "erro desconhecido");
26100
26682
  }
26101
26683
  }
26102
26684
  async function runDoctor() {
26103
- const report = new Report();
26685
+ const report2 = new Report();
26104
26686
  process.stdout.write(`beplus-mcp doctor, versao ${VERSION}
26105
26687
 
26106
26688
  `);
26107
26689
  if (isSupportedNode()) {
26108
- report.line("ok", `Node ${process.versions.node}`, `Minimo exigido: Node ${MIN_NODE_MAJOR}.`);
26690
+ report2.line("ok", `Node ${process.versions.node}`, `Minimo exigido: Node ${MIN_NODE_MAJOR}.`);
26109
26691
  } else {
26110
- report.line("erro", `Node ${process.versions.node}`, unsupportedNodeMessage());
26692
+ report2.line("erro", `Node ${process.versions.node}`, unsupportedNodeMessage());
26111
26693
  }
26112
26694
  let baseUrl = null;
26113
26695
  try {
26114
26696
  baseUrl = resolveBaseUrl();
26115
- report.line("ok", `BEPLUS_API_URL: ${baseUrl}`);
26697
+ report2.line("ok", `BEPLUS_API_URL: ${baseUrl}`);
26116
26698
  } catch (e) {
26117
- report.line("erro", "BEPLUS_API_URL invalida", e.message);
26699
+ report2.line("erro", "BEPLUS_API_URL invalida", e.message);
26118
26700
  }
26119
26701
  if (baseUrl) {
26120
- await checkApiReachable(report, baseUrl);
26121
- await checkToken(report);
26702
+ await checkApiReachable(report2, baseUrl);
26703
+ await checkToken(report2);
26122
26704
  }
26123
- checkConfigs(report);
26124
- const code = report.exitCode;
26705
+ checkConfigs(report2);
26706
+ const code = report2.exitCode;
26125
26707
  process.stdout.write(code === 0 ? "\nTudo certo.\n" : "\nTem item com [erro] acima. Resolva antes de usar o MCP.\n");
26126
26708
  return code;
26127
26709
  }