beplus-mcp 0.24.0 → 0.26.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
@@ -13050,6 +13050,7 @@ function loadConfig() {
13050
13050
  return {
13051
13051
  baseUrl,
13052
13052
  apiBase: `${baseUrl}/api/v1/mcp`,
13053
+ canvasBase: `${baseUrl}/api/v1/canvas`,
13053
13054
  token,
13054
13055
  inlineImages,
13055
13056
  inlineImageMax: 4,
@@ -21200,8 +21201,9 @@ var BeplusClient = class {
21200
21201
  this.cfg = cfg;
21201
21202
  }
21202
21203
  cfg;
21204
+ /** Público para os módulos de rota própria (ex.: Canvas) reaproveitarem auth, timeout e erros. */
21203
21205
  async request(method, path, opts = {}) {
21204
- const url = new URL(`${this.cfg.apiBase}${path}`);
21206
+ const url = new URL(`${opts.base ?? this.cfg.apiBase}${path}`);
21205
21207
  if (opts.query) {
21206
21208
  for (const [k, v] of Object.entries(opts.query)) {
21207
21209
  if (v !== void 0 && v !== null && v !== "") url.searchParams.set(k, String(v));
@@ -22038,7 +22040,7 @@ ${BUDGET_BLOCKED_GUIDANCE}`);
22038
22040
  "generate_video",
22039
22041
  {
22040
22042
  title: "Gerar v\xEDdeo",
22041
- description: "Gera um v\xEDdeo curto usando o IA Lab da BePlus. LENTO (geralmente 1-5+ min). Aguarda at\xE9 ~2 min; se n\xE3o terminar, retorna o generation_id e o status \u2014 ent\xE3o use check_generation. Consome diamantes (mais caro para dura\xE7\xF5es/resolu\xE7\xF5es maiores). Suporta image-to-video (`first_frame_image`), \xFAltimo frame (`last_frame_image`), refer\xEAncias de imagem/v\xEDdeo/\xE1udio, prompt negativo, \xE1udio gerado e mais \u2014 os campos aceitos variam por modelo (consulte list_models). `kling-v2.6-motion-control` usa `first_frame_image` + `motion_video`. Imagens com rosto de pessoa REAL s\xE3o bloqueadas pela modera\xE7\xE3o do provedor (use ilustra\xE7\xF5es/personagens)." + TEAM_GATING_NOTE,
22043
+ description: "Gera um v\xEDdeo curto usando o IA Lab da BePlus. LENTO (geralmente 1-5+ min). Aguarda at\xE9 ~2 min; se n\xE3o terminar, retorna o generation_id e o status \u2014 ent\xE3o use check_generation. Consome diamantes (mais caro para dura\xE7\xF5es/resolu\xE7\xF5es maiores). Suporta image-to-video (`first_frame_image`), \xFAltimo frame (`last_frame_image`), refer\xEAncias de imagem/v\xEDdeo/\xE1udio, prompt negativo, \xE1udio gerado e mais \u2014 os campos aceitos variam por modelo (consulte list_models). `kling-v2.6-motion-control` usa `first_frame_image` + `motion_video`. Nos modelos Seedance, refer\xEAncia com rosto de pessoa REAL \xE9 bloqueada pelo provedor padr\xE3o (BytePlus) \u2014 use `real_people_references: true` para rotear pra fal.ai, que aceita (custa mais caro). Kling e outros provedores n\xE3o t\xEAm essa op\xE7\xE3o." + TEAM_GATING_NOTE,
22042
22044
  inputSchema: {
22043
22045
  // Sem teto local — ver nota em generate_image. O Seedance não publica
22044
22046
  // limite; o Kling publica 2500 e o backend recusa dizendo quanto cortar.
@@ -22074,6 +22076,9 @@ ${BUDGET_BLOCKED_GUIDANCE}`);
22074
22076
  video_task_type: external_exports.enum(["auto", "reference", "edit", "extend"]).optional().describe(
22075
22077
  "O que fazer com os v\xEDdeos de refer\xEAncia (s\xF3 seedance-2.5): `reference` gera um v\xEDdeo novo; `edit` edita o original (exige 1 v\xEDdeo de 4-30s, a dura\xE7\xE3o vira a dele); `extend` estende o original. Sem isto o modelo adivinha e um par\xE2metro incompat\xEDvel s\xF3 falha DEPOIS da fila."
22076
22078
  ),
22079
+ real_people_references: external_exports.boolean().optional().describe(
22080
+ 'S\xF3 Seedance (2.0/2.0-fast/2.0-mini/2.5): quando true, roteia pra fal.ai em vez da BytePlus \u2014 a BytePlus recusa refer\xEAncia com pessoa real, a fal aceita (mais caro). `first_frame_image` + `last_frame_image` continuam funcionando (viram image-to-video da fal, com end frame). Com isto ligado, `output_format` e `video_task_type` (edit/extend) n\xE3o t\xEAm equivalente na fal e s\xE3o recusados antes de cobrar \u2014 e `seedance-2.0-fast` com `resolution: "480p"` tamb\xE9m, porque a fal n\xE3o publica pre\xE7o para essa combina\xE7\xE3o.'
22081
+ ),
22077
22082
  project: external_exports.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.'),
22078
22083
  billing: BILLING_FIELD,
22079
22084
  draft: external_exports.boolean().optional().describe(
@@ -22118,6 +22123,24 @@ ${BUDGET_BLOCKED_GUIDANCE}`);
22118
22123
  if (!isSeedance25 && args.video_task_type && args.video_task_type !== "auto") {
22119
22124
  return errorResult(`Editar e estender v\xEDdeo s\xF3 existe no seedance-2.5 (voc\xEA pediu ${args.model}).`);
22120
22125
  }
22126
+ if (args.real_people_references && !args.model?.startsWith("bytedance/seedance-")) {
22127
+ return errorResult(`real_people_references s\xF3 existe nos modelos Seedance (voc\xEA pediu ${args.model}).`);
22128
+ }
22129
+ if (args.real_people_references) {
22130
+ const unsupported = [];
22131
+ if (args.output_format && args.output_format !== "mp4") unsupported.push("output_format");
22132
+ if (args.video_task_type && args.video_task_type !== "auto") unsupported.push("video_task_type (edit/extend)");
22133
+ if (args.model === "bytedance/seedance-2.0-fast" && args.resolution === "480p") {
22134
+ unsupported.push("resolution 480p (a fal n\xE3o publica pre\xE7o para seedance-2.0-fast nesta resolu\xE7\xE3o)");
22135
+ }
22136
+ if (args.draft) unsupported.push("draft (Modo Rascunho \u2014 s\xF3 existe na BytePlus)");
22137
+ if (args.draft_task_id) unsupported.push("draft_task_id (upscale de rascunho \u2014 s\xF3 existe na BytePlus)");
22138
+ if (unsupported.length > 0) {
22139
+ return errorResult(
22140
+ `Com real_people_references, a fal.ai n\xE3o suporta: ${unsupported.join(", ")}. Remova essas op\xE7\xF5es.`
22141
+ );
22142
+ }
22143
+ }
22121
22144
  if ((args.video_task_type === "edit" || args.video_task_type === "extend") && !(args.reference_videos && args.reference_videos.length > 0)) {
22122
22145
  return errorResult(`video_task_type "${args.video_task_type}" exige o v\xEDdeo original em reference_videos.`);
22123
22146
  }
@@ -22173,7 +22196,8 @@ ${BUDGET_BLOCKED_GUIDANCE}`);
22173
22196
  seed: args.seed,
22174
22197
  output_format: args.output_format,
22175
22198
  omni_reference_task_type: args.video_task_type,
22176
- draft: args.draft
22199
+ draft: args.draft,
22200
+ realPeopleReferences: args.real_people_references
22177
22201
  });
22178
22202
  const start = await client.startGeneration(
22179
22203
  compact({
@@ -23444,11 +23468,1052 @@ ${calls.map(callLine).join("\n\n")}`);
23444
23468
  );
23445
23469
  }
23446
23470
 
23471
+ // src/canvas/api.ts
23472
+ var PINNED_CORE_VERSION = "ca7b3b406184c7f679f555c3a008013197182b23";
23473
+ function errorRaw(err) {
23474
+ if (!(err instanceof ApiError)) return void 0;
23475
+ const raw = err.body?.error?.raw;
23476
+ return raw && typeof raw === "object" ? raw : void 0;
23477
+ }
23478
+ var CanvasApi = class {
23479
+ constructor(client, cfg) {
23480
+ this.client = client;
23481
+ this.cfg = cfg;
23482
+ }
23483
+ client;
23484
+ cfg;
23485
+ coreVersion = PINNED_CORE_VERSION;
23486
+ userId = null;
23487
+ req(method, path, opts = {}) {
23488
+ return this.client.request(method, path, { ...opts, base: this.cfg.canvasBase });
23489
+ }
23490
+ /** Token do `beplus-mcp login` (OAuth) não abre `/api/v1/canvas`: só PAT. */
23491
+ get usesPat() {
23492
+ return this.cfg.token.startsWith("pat_");
23493
+ }
23494
+ /** `createdBy` dos nós que o MCP cria. Nulo seria "motor de execução" no Canvas. */
23495
+ async me() {
23496
+ if (!this.userId) this.userId = (await this.client.me()).userId;
23497
+ return this.userId;
23498
+ }
23499
+ listBoards(kind = "board") {
23500
+ return this.req("GET", "/boards", { query: { kind } });
23501
+ }
23502
+ createBoard(body) {
23503
+ return this.req("POST", "/boards", { body });
23504
+ }
23505
+ snapshot(boardId2) {
23506
+ return this.req("GET", `/boards/${boardId2}`);
23507
+ }
23508
+ /** Replay depois de `after`. `null` quando o servidor manda reler a foto (410). */
23509
+ async opsAfter(boardId2, after) {
23510
+ try {
23511
+ const { envelopes } = await this.req("GET", `/boards/${boardId2}/ops`, {
23512
+ query: { after }
23513
+ });
23514
+ return envelopes;
23515
+ } catch (e) {
23516
+ if (e instanceof ApiError && e.httpStatus === 410) return null;
23517
+ throw e;
23518
+ }
23519
+ }
23520
+ async commit(boardId2, ops) {
23521
+ const send = () => this.req("POST", `/boards/${boardId2}/ops`, {
23522
+ body: { coreVersion: this.coreVersion, ops }
23523
+ });
23524
+ try {
23525
+ return await send();
23526
+ } catch (e) {
23527
+ const raw = errorRaw(e);
23528
+ if (!(e instanceof ApiError) || e.httpStatus !== 409 || raw?.reason !== "core-mismatch") throw e;
23529
+ const serverVersion = typeof raw.coreVersion === "string" ? raw.coreVersion : "";
23530
+ if (!serverVersion || serverVersion === this.coreVersion) throw new CoreMismatchError(serverVersion);
23531
+ this.coreVersion = serverVersion;
23532
+ try {
23533
+ return await send();
23534
+ } catch (again) {
23535
+ if (again instanceof ApiError && again.httpStatus === 409 && errorRaw(again)?.reason === "core-mismatch") {
23536
+ throw new CoreMismatchError(String(errorRaw(again)?.coreVersion ?? ""));
23537
+ }
23538
+ throw again;
23539
+ }
23540
+ }
23541
+ }
23542
+ members(boardId2) {
23543
+ return this.req("GET", `/boards/${boardId2}/members`);
23544
+ }
23545
+ putMember(boardId2, userId, role) {
23546
+ return this.req("PUT", `/boards/${boardId2}/members/${userId}`, { body: { role } });
23547
+ }
23548
+ removeMember(boardId2, userId) {
23549
+ return this.req("DELETE", `/boards/${boardId2}/members/${userId}`);
23550
+ }
23551
+ /** Só para os testes conferirem o aprendizado. */
23552
+ get currentCoreVersion() {
23553
+ return this.coreVersion;
23554
+ }
23555
+ };
23556
+ var CoreMismatchError = class extends Error {
23557
+ constructor(serverVersion) {
23558
+ super(
23559
+ "O servidor do Canvas recusou a vers\xE3o do canvas-core deste MCP (409 core-mismatch)" + (serverVersion ? ` e segue exigindo ${serverVersion.slice(0, 12)}` : " sem informar a vers\xE3o esperada") + ". Nada foi aplicado. Atualize o beplus-mcp (npx -y beplus-mcp@latest) ou avise o time."
23560
+ );
23561
+ this.serverVersion = serverVersion;
23562
+ this.name = "CoreMismatchError";
23563
+ }
23564
+ serverVersion;
23565
+ };
23566
+
23567
+ // src/canvas/catalog.ts
23568
+ var WORKFLOW_NODE_TYPE = "workflow";
23569
+ var KIND_FIELD = "kind";
23570
+ var KINDS = {
23571
+ text: { title: "Texto", size: { w: 300, h: 160 }, data: { text: "" }, inputs: [], outputs: ["text"], main: "text" },
23572
+ block: { title: "Bloco", size: { w: 260, h: 140 }, data: { text: "" }, inputs: [], outputs: ["text"], main: "text" },
23573
+ prompt: {
23574
+ title: "Prompt",
23575
+ size: { w: 340, h: 200 },
23576
+ data: { template: "" },
23577
+ inputs: ["refs"],
23578
+ outputs: ["text", "refs"],
23579
+ dynamic: "uma entrada de texto por {{var}} do template",
23580
+ main: "template"
23581
+ },
23582
+ llm: {
23583
+ title: "Redator",
23584
+ size: { w: 340, h: 300 },
23585
+ data: { model: "gemini-flash", system: "", instruction: "", outputMode: "text" },
23586
+ inputs: ["instruction", "media"],
23587
+ outputs: ["text"],
23588
+ main: "instruction"
23589
+ },
23590
+ brief: {
23591
+ title: "Diretor",
23592
+ size: { w: 420, h: 360 },
23593
+ data: {
23594
+ model: "gemini-flash",
23595
+ sceneCount: 4,
23596
+ aspect_ratio: "16:9",
23597
+ secondsPerScene: 5,
23598
+ tone: "Natural e pr\xF3ximo, como quem conta para um amigo",
23599
+ language: "pt-BR"
23600
+ },
23601
+ inputs: ["briefing", "refs"],
23602
+ outputs: ["script", "narration", "music", "scenes", "motion"]
23603
+ },
23604
+ narration: { title: "Narra\xE7\xE3o", size: { w: 360, h: 360 }, data: { blocks: [] }, inputs: [], outputs: ["text", "blocks"], main: "blocks" },
23605
+ shotlist: {
23606
+ title: "Roteiro",
23607
+ size: { w: 780, h: 410 },
23608
+ data: { rows: [], orphanedRows: [] },
23609
+ inputs: ["blocks"],
23610
+ outputs: ["shotlist"],
23611
+ dynamic: "uma sa\xEDda shot:<ideaId> por ideia",
23612
+ main: "rows"
23613
+ },
23614
+ context: { title: "Contexto", size: { w: 320, h: 220 }, data: { kitId: null }, inputs: [], outputs: ["context"] },
23615
+ copy: {
23616
+ title: "Textos da pe\xE7a",
23617
+ size: { w: 340, h: 320 },
23618
+ data: { title: "", body: "", cta: "", legal: "" },
23619
+ inputs: [],
23620
+ outputs: ["title", "body", "cta", "legal"],
23621
+ main: "title"
23622
+ },
23623
+ media: { title: "M\xEDdia", size: { w: 320, h: 280 }, data: { items: [] }, inputs: [], outputs: ["image"], dynamic: "sa\xEDda por tipo de item (image, video, audio, file)", main: "items" },
23624
+ image: {
23625
+ title: "Gerador de imagem",
23626
+ size: { w: 320, h: 380 },
23627
+ data: { model: "google/nano-banana-2", aspect_ratio: "1:1", count: 1 },
23628
+ inputs: ["prompt"],
23629
+ outputs: ["image"],
23630
+ dynamic: "refs e negative conforme o modelo"
23631
+ },
23632
+ video: {
23633
+ title: "Gerador de v\xEDdeo",
23634
+ size: { w: 360, h: 320 },
23635
+ data: { model: "bytedance/seedance-2.0-mini", aspect_ratio: "16:9", duration: 5, generateAudio: true },
23636
+ inputs: ["prompt"],
23637
+ outputs: ["video"],
23638
+ dynamic: "frame, refs e negative conforme o modelo"
23639
+ },
23640
+ tts: {
23641
+ title: "Locu\xE7\xE3o",
23642
+ size: { w: 320, h: 212 },
23643
+ data: { model: "gemini-3.1-flash-tts-preview", voice: "Algieba", speed: "normal" },
23644
+ inputs: ["text"],
23645
+ outputs: ["audio", "approved"],
23646
+ dynamic: "style conforme o modelo"
23647
+ },
23648
+ music: {
23649
+ title: "Trilha",
23650
+ size: { w: 320, h: 212 },
23651
+ data: { model: "suno/v5-5", instrumental: true, negativeTags: ["vocals", "choir", "whistling", "marching band", "epic orchestral"] },
23652
+ inputs: ["prompt", "negative"],
23653
+ outputs: ["audio", "approved"],
23654
+ dynamic: "lyrics quando n\xE3o \xE9 instrumental"
23655
+ },
23656
+ review: {
23657
+ title: "Revisor",
23658
+ size: { w: 400, h: 420 },
23659
+ data: {
23660
+ model: "gemini-flash",
23661
+ strictness: "normal",
23662
+ criteria: "",
23663
+ checks: { inventedText: true, inventedLogo: true, productFidelity: true, composition: true, artifacts: true }
23664
+ },
23665
+ inputs: ["items", "prompt", "refs"],
23666
+ outputs: ["reviewed"],
23667
+ main: "criteria"
23668
+ },
23669
+ approval: { title: "Aprova\xE7\xE3o", size: { w: 660, h: 580 }, data: {}, inputs: ["items"], outputs: ["approved"] },
23670
+ "audio-demo": { title: "Demo de \xE1udio", size: { w: 460, h: 500 }, data: { chosenKey: null }, inputs: ["voice", "music"], outputs: ["voice", "music"] },
23671
+ compose: {
23672
+ title: "Composi\xE7\xE3o",
23673
+ size: { w: 440, h: 320 },
23674
+ data: { formatIds: [], imageType: "jpg", jpgQuality: 0.92 },
23675
+ inputs: ["background", "title", "body", "cta", "legal", "logo"],
23676
+ outputs: ["pieces"]
23677
+ },
23678
+ montage: {
23679
+ title: "Montagem",
23680
+ size: { w: 460, h: 560 },
23681
+ data: {
23682
+ order: "chegada",
23683
+ musicGainDb: -14,
23684
+ duckingDb: -8,
23685
+ fadeMs: 1500,
23686
+ title: "",
23687
+ renderFilm: true,
23688
+ filmFps: 30,
23689
+ clipAudioGainDb: -12
23690
+ },
23691
+ inputs: ["clips", "voice", "music"],
23692
+ outputs: ["film", "cut", "clips", "voice", "music"],
23693
+ main: "title"
23694
+ },
23695
+ export: {
23696
+ title: "Exporta\xE7\xE3o",
23697
+ size: { w: 380, h: 420 },
23698
+ data: { client: "", campaign: "", version: 1, imageType: "png", jpgQuality: 90, manifest: true, flat: false },
23699
+ inputs: ["items"],
23700
+ outputs: ["package"]
23701
+ },
23702
+ note: { title: "Nota", size: { w: 220, h: 220 }, data: { text: "", color: "yellow", fontSize: "auto" }, inputs: [], outputs: [], annotation: true, main: "text" },
23703
+ label: {
23704
+ title: "Texto livre",
23705
+ size: { w: 320, h: 56 },
23706
+ data: { text: "", size: "titulo", weight: "bold", color: "neutral", align: "left" },
23707
+ inputs: [],
23708
+ outputs: [],
23709
+ annotation: true,
23710
+ main: "text"
23711
+ },
23712
+ section: { title: "Se\xE7\xE3o", size: { w: 880, h: 560 }, data: { title: "Se\xE7\xE3o", color: "violet" }, inputs: [], outputs: [], annotation: true, main: "title" },
23713
+ shape: {
23714
+ title: "Forma",
23715
+ size: { w: 200, h: 140 },
23716
+ data: { shape: "rounded", fill: "blue", stroke: "blue", strokeWidth: 2, text: "" },
23717
+ inputs: [],
23718
+ outputs: [],
23719
+ annotation: true,
23720
+ main: "text"
23721
+ }
23722
+ };
23723
+ function kindList() {
23724
+ return Object.entries(KINDS).map(([k, s]) => `${k} (${s.title})`).join(", ");
23725
+ }
23726
+ function knownPort(kind, side, port) {
23727
+ const spec = kind ? KINDS[kind] : void 0;
23728
+ if (!spec) return void 0;
23729
+ const list = side === "in" ? spec.inputs : spec.outputs;
23730
+ if (list.includes(port)) return true;
23731
+ if (spec.dynamic) {
23732
+ return void 0;
23733
+ }
23734
+ return false;
23735
+ }
23736
+
23737
+ // src/canvas/core.ts
23738
+ import { randomBytes, randomUUID } from "crypto";
23739
+ var SCHEMA_VERSION = 1;
23740
+ var MAX_OP_BYTES = 64 * 1024;
23741
+ var MAX_DATA_URL_BYTES = 8 * 1024;
23742
+ var CLIENT_ID = `mcp-${randomBytes(6).toString("hex")}`;
23743
+ var NODE_KEYS = ["id", "type", "position", "size", "zIndex", "parentId", "data", "createdBy", "createdAt", "updatedAt"];
23744
+ var EDGE_KEYS = ["id", "source", "sourcePort", "target", "targetPort", "data", "createdBy", "createdAt"];
23745
+ var FORBIDDEN_SEGMENTS = ["__proto__", "constructor", "prototype"];
23746
+ var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
23747
+ var isFiniteNumber = (v) => typeof v === "number" && Number.isFinite(v);
23748
+ var isStringOrNull = (v) => v === null || typeof v === "string";
23749
+ var hasOnlyKeys = (r, keys) => Object.keys(r).every((k) => keys.includes(k));
23750
+ function isJson(value) {
23751
+ if (value === null) return true;
23752
+ const kind = typeof value;
23753
+ if (kind === "boolean" || kind === "string") return true;
23754
+ if (kind === "number") return Number.isFinite(value);
23755
+ if (Array.isArray(value)) return value.every(isJson);
23756
+ if (isRecord(value)) return Object.values(value).every(isJson);
23757
+ return false;
23758
+ }
23759
+ var isJsonBag = (v) => isRecord(v) && Object.values(v).every(isJson);
23760
+ var isPoint = (v) => isRecord(v) && hasOnlyKeys(v, ["x", "y"]) && isFiniteNumber(v.x) && isFiniteNumber(v.y);
23761
+ var isSize = (v) => isRecord(v) && hasOnlyKeys(v, ["w", "h"]) && isFiniteNumber(v.w) && isFiniteNumber(v.h);
23762
+ function isValidNode(key, v) {
23763
+ return isRecord(v) && hasOnlyKeys(v, NODE_KEYS) && v.id === key && typeof v.type === "string" && isPoint(v.position) && isSize(v.size) && isFiniteNumber(v.zIndex) && isStringOrNull(v.parentId) && isJsonBag(v.data) && isStringOrNull(v.createdBy) && isFiniteNumber(v.createdAt) && isFiniteNumber(v.updatedAt);
23764
+ }
23765
+ function isValidEdge(key, v) {
23766
+ 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);
23767
+ }
23768
+ function checkNodePath(path) {
23769
+ const segments = path.split(".");
23770
+ if (segments.some((s) => s.length === 0)) return `caminho inv\xE1lido "${path}"`;
23771
+ if (segments.some((s) => FORBIDDEN_SEGMENTS.includes(s))) return `segmento proibido em "${path}"`;
23772
+ const [root, ...rest] = segments;
23773
+ if (root === "data" && rest.length >= 1) return null;
23774
+ if (root === "position" && rest.length === 1 && ["x", "y"].includes(rest[0]) || root === "size" && rest.length === 1 && ["w", "h"].includes(rest[0])) return null;
23775
+ return `s\xF3 d\xE1 para editar data.*, position.x/y e size.w/h (recebido "${path}")`;
23776
+ }
23777
+ function hasLargeDataUrl(value) {
23778
+ const stack = [value];
23779
+ while (stack.length) {
23780
+ const cur = stack.pop();
23781
+ if (typeof cur === "string") {
23782
+ if (cur.length > MAX_DATA_URL_BYTES && cur.startsWith("data:")) return true;
23783
+ } else if (Array.isArray(cur)) stack.push(...cur);
23784
+ else if (isRecord(cur)) stack.push(...Object.values(cur));
23785
+ }
23786
+ return false;
23787
+ }
23788
+ function checkOp(op) {
23789
+ switch (op.type) {
23790
+ case "node.add":
23791
+ if (op.node.id !== op.nodeId || !isValidNode(op.nodeId, op.node)) return `n\xF3 ${op.nodeId} fora do formato do Canvas`;
23792
+ return null;
23793
+ case "edge.add":
23794
+ if (op.edge.id !== op.edgeId || !isValidEdge(op.edgeId, op.edge)) return `liga\xE7\xE3o ${op.edgeId} fora do formato do Canvas`;
23795
+ return null;
23796
+ case "node.set": {
23797
+ const bad = checkNodePath(op.path);
23798
+ if (bad) return bad;
23799
+ if (op.value.kind === "value") {
23800
+ if (!isJson(op.value.value)) return `valor de ${op.path} n\xE3o \xE9 JSON`;
23801
+ if (!op.path.startsWith("data.") && !isFiniteNumber(op.value.value)) return `${op.path} precisa ser n\xFAmero`;
23802
+ } else if (!op.path.startsWith("data.")) return `${op.path} n\xE3o pode ser apagado`;
23803
+ return null;
23804
+ }
23805
+ case "board.set":
23806
+ if (op.field === "title" && (op.value.kind !== "value" || typeof op.value.value !== "string" || !op.value.value.trim()))
23807
+ return "t\xEDtulo vazio";
23808
+ return null;
23809
+ case "batch":
23810
+ for (const inner of op.ops) {
23811
+ const bad = checkOp(inner);
23812
+ if (bad) return bad;
23813
+ }
23814
+ return null;
23815
+ default:
23816
+ return null;
23817
+ }
23818
+ }
23819
+ function envelope(boardId2, op, actorId) {
23820
+ return { id: randomUUID(), boardId: boardId2, clientId: CLIENT_ID, actorId, seq: null, ts: Date.now(), op };
23821
+ }
23822
+ function checkEnvelope(env) {
23823
+ const bad = checkOp(env.op);
23824
+ if (bad) return bad;
23825
+ if (Buffer.byteLength(JSON.stringify(env)) > MAX_OP_BYTES)
23826
+ return "opera\xE7\xE3o maior que 64 KB: divida em partes menores";
23827
+ if (hasLargeDataUrl(env.op)) return "m\xEDdia embutida (data:) no board: suba o arquivo e use a URL";
23828
+ return null;
23829
+ }
23830
+ function emptyBoard(title, now = Date.now()) {
23831
+ return {
23832
+ id: randomUUID(),
23833
+ schemaVersion: SCHEMA_VERSION,
23834
+ title,
23835
+ ownerId: "",
23836
+ folderId: null,
23837
+ version: 0,
23838
+ nodes: {},
23839
+ edges: {},
23840
+ results: {},
23841
+ nodeHistory: {},
23842
+ createdAt: now,
23843
+ updatedAt: now
23844
+ };
23845
+ }
23846
+ function makeNode(init) {
23847
+ const now = init.now ?? Date.now();
23848
+ return {
23849
+ id: init.id ?? randomUUID(),
23850
+ type: init.type,
23851
+ position: { x: init.position.x, y: init.position.y },
23852
+ size: { w: init.size.w, h: init.size.h },
23853
+ zIndex: init.zIndex,
23854
+ parentId: null,
23855
+ data: init.data,
23856
+ createdBy: init.createdBy,
23857
+ createdAt: now,
23858
+ updatedAt: now
23859
+ };
23860
+ }
23861
+ function makeEdge(init) {
23862
+ return {
23863
+ id: randomUUID(),
23864
+ source: init.source,
23865
+ sourcePort: init.sourcePort,
23866
+ target: init.target,
23867
+ targetPort: init.targetPort,
23868
+ data: {},
23869
+ createdBy: init.createdBy,
23870
+ createdAt: init.now ?? Date.now()
23871
+ };
23872
+ }
23873
+ var setValue = (value) => ({ kind: "value", value });
23874
+ var unsetValue = () => ({ kind: "unset" });
23875
+ function removeNodeOps(doc, nodeId) {
23876
+ const ops = [];
23877
+ const edgeIds = Object.keys(doc.edges).filter((id) => doc.edges[id].source === nodeId || doc.edges[id].target === nodeId).sort();
23878
+ for (const edgeId of edgeIds) ops.push({ type: "edge.remove", edgeId });
23879
+ const history = Object.hasOwn(doc.nodeHistory, nodeId) ? doc.nodeHistory[nodeId] : void 0;
23880
+ if (history) for (const resultId of [...history].reverse()) ops.push({ type: "history.remove", nodeId, resultId });
23881
+ ops.push({ type: "node.remove", nodeId });
23882
+ return ops;
23883
+ }
23884
+ function batchOf(ops) {
23885
+ return ops.length === 1 ? ops[0] : { type: "batch", ops };
23886
+ }
23887
+
23888
+ // src/canvas/summary.ts
23889
+ var MAX_TEXT = 160;
23890
+ var MAX_BLOCKS = 40;
23891
+ var HEAVY_KEYS = /* @__PURE__ */ new Set([
23892
+ "history",
23893
+ "pinned",
23894
+ "main",
23895
+ "lastFrame",
23896
+ "sequence",
23897
+ "points",
23898
+ "extra",
23899
+ "mentions",
23900
+ "mediaMentions",
23901
+ "refs",
23902
+ "fields",
23903
+ "orphanedRows",
23904
+ "holds",
23905
+ "appearance",
23906
+ "settings",
23907
+ "enabledKeys",
23908
+ "checks",
23909
+ "versions"
23910
+ ]);
23911
+ var NAME_KEYS = /* @__PURE__ */ new Set([KIND_FIELD, "label", "nodeTitle"]);
23912
+ function oneLine(text, max = MAX_TEXT) {
23913
+ const flat = text.replace(/\s+/g, " ").trim();
23914
+ return flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat;
23915
+ }
23916
+ function nodeKind(node) {
23917
+ const k = node.data[KIND_FIELD];
23918
+ return typeof k === "string" && k ? k : null;
23919
+ }
23920
+ function nodeName(node) {
23921
+ const { label, nodeTitle } = node.data;
23922
+ if (typeof label === "string" && label.trim()) return label.trim();
23923
+ if (typeof nodeTitle === "string" && nodeTitle.trim()) return nodeTitle.trim();
23924
+ const kind = nodeKind(node);
23925
+ return kind && KINDS[kind]?.title || kind || node.type;
23926
+ }
23927
+ function scalar(v) {
23928
+ if (typeof v === "string") {
23929
+ if (v.startsWith("data:")) return "[data-url]";
23930
+ if (/^https?:\/\//.test(v)) return "[url]";
23931
+ return JSON.stringify(oneLine(v, 60));
23932
+ }
23933
+ if (typeof v === "number" || typeof v === "boolean" || v === null) return String(v);
23934
+ return null;
23935
+ }
23936
+ function blocksLines(blocks) {
23937
+ if (!Array.isArray(blocks)) return [];
23938
+ const out = blocks.slice(0, MAX_BLOCKS).map((b) => {
23939
+ const rec = b ?? {};
23940
+ return ` \xB7 ${String(rec.id ?? "?")}: ${oneLine(String(rec.text ?? ""), 120)}`;
23941
+ });
23942
+ if (blocks.length > MAX_BLOCKS) out.push(` \xB7 \u2026 +${blocks.length - MAX_BLOCKS} blocos`);
23943
+ return out;
23944
+ }
23945
+ function rowsLines(rows) {
23946
+ if (!Array.isArray(rows)) return [];
23947
+ const out = [];
23948
+ for (const r of rows.slice(0, MAX_BLOCKS)) {
23949
+ const rec = r ?? {};
23950
+ const ideas = Array.isArray(rec.ideas) ? rec.ideas : [];
23951
+ const ideaText = ideas.slice(0, 6).map((i) => {
23952
+ const idea = i ?? {};
23953
+ return `${String(idea.id ?? "?")}="${oneLine(String(idea.text ?? ""), 60)}"`;
23954
+ }).join("; ");
23955
+ out.push(` \xB7 linha ${String(rec.id ?? "?")} (bloco ${String(rec.narrationBlockId ?? "?")}): ${ideas.length} ideia(s) ${ideaText}`);
23956
+ }
23957
+ if (rows.length > MAX_BLOCKS) out.push(` \xB7 \u2026 +${rows.length - MAX_BLOCKS} linhas`);
23958
+ return out;
23959
+ }
23960
+ function nodeLines(node, run) {
23961
+ const kind = nodeKind(node);
23962
+ const spec = kind ? KINDS[kind] : void 0;
23963
+ const head = `- ${node.id} \xB7 ${kind ?? node.type} \xB7 "${oneLine(nodeName(node), 60)}" @(${Math.round(node.position.x)},${Math.round(node.position.y)})`;
23964
+ const facts = [];
23965
+ const extra = [];
23966
+ const d = node.data;
23967
+ if (d.archived) facts.push("ARQUIVADO");
23968
+ const approval = d.approval?.state;
23969
+ if (typeof approval === "string") facts.push(`aprova\xE7\xE3o: ${approval}`);
23970
+ if (Array.isArray(d.history)) facts.push(`${d.history.length} gera\xE7\xE3o(\xF5es)`);
23971
+ if (Array.isArray(d.items)) {
23972
+ const names = d.items.slice(0, 5).map((i) => String(i?.name ?? "?"));
23973
+ facts.push(`${d.items.length} arquivo(s)${names.length ? `: ${names.join(", ")}` : ""}`);
23974
+ }
23975
+ if (Array.isArray(d.blocks)) {
23976
+ facts.push(`${d.blocks.length} bloco(s)`);
23977
+ extra.push(...blocksLines(d.blocks));
23978
+ }
23979
+ if (Array.isArray(d.rows)) {
23980
+ facts.push(`${d.rows.length} linha(s)`);
23981
+ extra.push(...rowsLines(d.rows));
23982
+ }
23983
+ const main2 = spec?.main;
23984
+ if (main2 && typeof d[main2] === "string" && d[main2]) facts.push(`${main2}: "${oneLine(d[main2])}"`);
23985
+ for (const [key, value] of Object.entries(d)) {
23986
+ if (NAME_KEYS.has(key) || HEAVY_KEYS.has(key) || key === main2) continue;
23987
+ if (["blocks", "rows", "items", "archived", "approval"].includes(key)) continue;
23988
+ const s = scalar(value);
23989
+ if (s !== null) facts.push(`${key}=${s}`);
23990
+ else if (Array.isArray(value)) facts.push(`${key}=[${value.length}]`);
23991
+ else if (value && typeof value === "object") facts.push(`${key}={\u2026}`);
23992
+ if (facts.length > 12) break;
23993
+ }
23994
+ if (run?.claimedBy) facts.push("rodando agora");
23995
+ return [facts.length ? `${head} \xB7 ${facts.join(" \xB7 ")}` : head, ...extra];
23996
+ }
23997
+ function boardSummary(snap) {
23998
+ const doc = snap.doc;
23999
+ const nodes = Object.values(doc.nodes).sort((a, b) => a.position.x - b.position.x || a.position.y - b.position.y);
24000
+ const edges = Object.values(doc.edges);
24001
+ const runs = new Map(snap.runs.map((r) => [r.nodeId, r]));
24002
+ const lines = [
24003
+ `Board "${snap.title}" \xB7 id ${snap.id} \xB7 seq ${snap.seq} \xB7 seu papel: ${snap.role} \xB7 ${nodes.length} n\xF3(s) \xB7 ${edges.length} liga\xE7\xE3o(\xF5es)`
24004
+ ];
24005
+ const ialab = doc.meta?.ialab;
24006
+ if (ialab === null) lines.push("IA Lab: sem v\xEDnculo (gera\xE7\xF5es saem dos diamantes de quem roda).");
24007
+ else if (ialab && typeof ialab === "object") {
24008
+ const label = ["code", "name", "projectCode", "projectName"].map((k) => ialab[k]).find((v) => typeof v === "string");
24009
+ lines.push(`IA Lab: vinculado${label ? ` a ${String(label)}` : ""}.`);
24010
+ }
24011
+ if (doc.meta?.guide) lines.push(`Guia: ${oneLine(doc.meta.guide)}`);
24012
+ lines.push("", "N\xF3s:");
24013
+ if (!nodes.length) lines.push("(vazio)");
24014
+ for (const n of nodes) lines.push(...nodeLines(n, runs.get(n.id)));
24015
+ lines.push("", "Liga\xE7\xF5es:");
24016
+ if (!edges.length) lines.push("(nenhuma)");
24017
+ for (const e of edges) lines.push(`- ${e.id}: ${e.source}.${e.sourcePort} \u2192 ${e.target}.${e.targetPort}`);
24018
+ return lines.join("\n");
24019
+ }
24020
+ function nodeDetail(node) {
24021
+ const strip = (v, depth) => {
24022
+ if (typeof v === "string") {
24023
+ if (v.startsWith("data:")) return `[data-url ${v.length}b]`;
24024
+ return v.length > 2e3 ? `${v.slice(0, 2e3)}\u2026 [+${v.length - 2e3}]` : v;
24025
+ }
24026
+ if (Array.isArray(v)) {
24027
+ if (depth > 4) return `[${v.length} itens]`;
24028
+ const head = v.slice(0, 60).map((x) => strip(x, depth + 1));
24029
+ return v.length > 60 ? [...head, `\u2026 +${v.length - 60}`] : head;
24030
+ }
24031
+ if (v && typeof v === "object") {
24032
+ if (depth > 4) return "{\u2026}";
24033
+ const out = {};
24034
+ for (const [k, x] of Object.entries(v)) {
24035
+ out[k] = k === "points" || k === "sequence" ? "[omitido]" : strip(x, depth + 1);
24036
+ }
24037
+ return out;
24038
+ }
24039
+ return v;
24040
+ };
24041
+ return `${node.id} \xB7 ${nodeKind(node) ?? node.type} \xB7 "${nodeName(node)}"
24042
+ ${JSON.stringify(strip(node.data, 0))}`;
24043
+ }
24044
+ function changesSince(envelopes, doc) {
24045
+ const count = /* @__PURE__ */ new Map();
24046
+ for (const e of envelopes) {
24047
+ const key = e.op.type;
24048
+ count.set(key, (count.get(key) ?? 0) + 1);
24049
+ }
24050
+ const parts = [...count].map(([t, n]) => `${n}\xD7 ${t}`);
24051
+ const names = [...touchedNodes(envelopes)].map((id) => doc.nodes[id] ? `"${oneLine(nodeName(doc.nodes[id]), 40)}"` : `${id} (apagado)`).slice(0, 8);
24052
+ return `${envelopes.length} opera\xE7\xE3o(\xF5es) de outras pessoas desde a sua leitura (${parts.join(", ")})` + (names.length ? `; n\xF3s tocados: ${names.join(", ")}` : "");
24053
+ }
24054
+ function touchedNodes(envelopes) {
24055
+ const out = /* @__PURE__ */ new Set();
24056
+ const visit = (op) => {
24057
+ if (op.type === "batch" && Array.isArray(op.ops)) {
24058
+ for (const inner of op.ops) visit(inner);
24059
+ return;
24060
+ }
24061
+ if (typeof op.nodeId === "string") out.add(op.nodeId);
24062
+ const edge = op.edge;
24063
+ if (edge && typeof edge.source === "string") out.add(edge.source);
24064
+ if (edge && typeof edge.target === "string") out.add(edge.target);
24065
+ };
24066
+ for (const e of envelopes) visit(e.op);
24067
+ return out;
24068
+ }
24069
+
24070
+ // src/tools/canvas.ts
24071
+ var REJECT_TEXT = {
24072
+ "node-missing": "o n\xF3 n\xE3o existe mais (algu\xE9m apagou)",
24073
+ "edge-endpoint-missing": "uma ponta da liga\xE7\xE3o n\xE3o existe mais",
24074
+ "invalid-op": "opera\xE7\xE3o inv\xE1lida ou repetida",
24075
+ "too-large": "opera\xE7\xE3o grande demais (64 KB) ou com m\xEDdia embutida"
24076
+ };
24077
+ var SECTION_Z = -1e3;
24078
+ var GAP = 80;
24079
+ var CanvasToolError = class extends Error {
24080
+ };
24081
+ var boardId = external_exports.string().uuid().describe("Id (uuid) do board, como sai em canvas_list_boards.");
24082
+ var nodeIdArg = external_exports.string().min(1).max(128);
24083
+ var point = external_exports.object({ x: external_exports.number().finite(), y: external_exports.number().finite() });
24084
+ function registerCanvasTools(server, client, cfg) {
24085
+ const api = new CanvasApi(client, cfg);
24086
+ const seen = /* @__PURE__ */ new Map();
24087
+ function fail(e) {
24088
+ if (e instanceof CanvasToolError || e instanceof CoreMismatchError) return errorResult(e.message);
24089
+ if (e instanceof ApiError && e.httpStatus === 401 && !api.usesPat) {
24090
+ return errorResult(
24091
+ "O acesso pelo navegador expirou ou foi revogado. Rode `beplus-mcp login` de novo e autorize."
24092
+ );
24093
+ }
24094
+ if (e instanceof ApiError && e.httpStatus === 404) {
24095
+ return errorResult("Board n\xE3o encontrado, ou voc\xEA n\xE3o \xE9 membro dele. Confira o id em canvas_list_boards.");
24096
+ }
24097
+ return errorResult(apiErrorToUserMessage(e));
24098
+ }
24099
+ async function run(fn) {
24100
+ try {
24101
+ return textResult(await fn());
24102
+ } catch (e) {
24103
+ return fail(e);
24104
+ }
24105
+ }
24106
+ async function edit(id, build) {
24107
+ const last = seen.get(id);
24108
+ if (last === void 0) {
24109
+ throw new CanvasToolError("Leia o board com canvas_read_board antes de editar (a leitura vale para a sess\xE3o).");
24110
+ }
24111
+ const snap = await api.snapshot(id);
24112
+ if (snap.role === "viewer") throw new CanvasToolError("Voc\xEA \xE9 s\xF3 leitor neste board: pe\xE7a ao dono para te dar papel de editor.");
24113
+ const doc = snap.doc;
24114
+ const { ops, done, targets } = await build(doc, snap);
24115
+ if (!ops.length) return `Nada a mudar. ${done}`;
24116
+ let note = "";
24117
+ if (snap.seq > last) {
24118
+ const since = await api.opsAfter(id, last);
24119
+ const theirs = since?.filter((e) => e.clientId !== CLIENT_ID) ?? null;
24120
+ if (theirs === null) {
24121
+ seen.delete(id);
24122
+ throw new CanvasToolError(
24123
+ `O board mudou muito desde a sua leitura (seq ${last} \u2192 ${snap.seq}). Releia com canvas_read_board antes de editar.`
24124
+ );
24125
+ }
24126
+ const clash = [...touchedNodes(theirs)].filter((n) => targets.includes(n));
24127
+ if (clash.length) {
24128
+ seen.delete(id);
24129
+ const names = clash.map((n) => doc.nodes[n] ? `"${oneLine(nodeName(doc.nodes[n]), 40)}"` : n).join(", ");
24130
+ throw new CanvasToolError(
24131
+ `Outra pessoa mexeu em ${names} depois da sua leitura (seq ${last} \u2192 ${snap.seq}). Nada foi aplicado: releia com canvas_read_board e refa\xE7a a edi\xE7\xE3o em cima do estado atual.`
24132
+ );
24133
+ }
24134
+ if (theirs.length) note = `
24135
+ Aviso: ${changesSince(theirs, doc)}.`;
24136
+ }
24137
+ const env = envelope(id, batchOf(ops), await api.me());
24138
+ const bad = checkEnvelope(env);
24139
+ if (bad) throw new CanvasToolError(`Edi\xE7\xE3o recusada antes de enviar: ${bad}.`);
24140
+ const res = await api.commit(id, [env]);
24141
+ const outcome = res.outcomes.find((o) => o.opId === env.id);
24142
+ if (!outcome || "rejected" in outcome) {
24143
+ seen.delete(id);
24144
+ const why = outcome && "rejected" in outcome ? REJECT_TEXT[outcome.rejected] ?? outcome.rejected : "sem resposta do servidor";
24145
+ throw new CanvasToolError(`O servidor recusou a edi\xE7\xE3o: ${why}. Nada foi aplicado. Releia com canvas_read_board.`);
24146
+ }
24147
+ seen.set(id, res.seq);
24148
+ return `\u2705 ${done} (seq ${res.seq}).${note}`;
24149
+ }
24150
+ function needNode(doc, nodeId) {
24151
+ const node = doc.nodes[nodeId];
24152
+ if (!node) throw new CanvasToolError(`N\xF3 ${nodeId} n\xE3o existe no board. Releia com canvas_read_board.`);
24153
+ return node;
24154
+ }
24155
+ function portWarning(doc, source, sourcePort, target, targetPort) {
24156
+ const warn = [];
24157
+ const s = knownPort(nodeKind(doc.nodes[source]), "out", sourcePort);
24158
+ const t = knownPort(nodeKind(doc.nodes[target]), "in", targetPort);
24159
+ if (s === false) warn.push(`sa\xEDda "${sourcePort}" n\xE3o \xE9 porta conhecida de ${nodeKind(doc.nodes[source])}`);
24160
+ if (t === false) warn.push(`entrada "${targetPort}" n\xE3o \xE9 porta conhecida de ${nodeKind(doc.nodes[target])}`);
24161
+ return warn.length ? ` Aten\xE7\xE3o: ${warn.join("; ")}.` : "";
24162
+ }
24163
+ function edgeOps(doc, links, createdBy, pending) {
24164
+ const ops = [];
24165
+ let warn = "";
24166
+ for (const l of links) {
24167
+ for (const end of [l.source, l.target]) {
24168
+ if (!doc.nodes[end] && !pending.has(end)) throw new CanvasToolError(`N\xF3 ${end} n\xE3o existe no board. Releia com canvas_read_board.`);
24169
+ }
24170
+ const dup = Object.values(doc.edges).some(
24171
+ (e) => e.source === l.source && e.sourcePort === l.sourcePort && e.target === l.target && e.targetPort === l.targetPort
24172
+ );
24173
+ if (dup) continue;
24174
+ if (doc.nodes[l.source] && doc.nodes[l.target]) warn += portWarning(doc, l.source, l.sourcePort, l.target, l.targetPort);
24175
+ const edge = makeEdge({ ...l, createdBy });
24176
+ ops.push({ type: "edge.add", edgeId: edge.id, edge });
24177
+ }
24178
+ return { ops, warn };
24179
+ }
24180
+ function placeFor(doc, size, near, side = "right") {
24181
+ if (near) {
24182
+ const x = side === "right" ? near.position.x + near.size.w + GAP : near.position.x - size.w - GAP;
24183
+ let y = near.position.y;
24184
+ const nodes2 = Object.values(doc.nodes);
24185
+ const hits = (yy) => nodes2.some((n) => x < n.position.x + n.size.w && x + size.w > n.position.x && yy < n.position.y + n.size.h && yy + size.h > n.position.y);
24186
+ for (let i = 0; i < 50 && hits(y); i++) y += GAP;
24187
+ return { x, y };
24188
+ }
24189
+ const nodes = Object.values(doc.nodes).filter((n) => nodeKind(n) !== "section");
24190
+ if (!nodes.length) return { x: 0, y: 0 };
24191
+ return {
24192
+ x: Math.max(...nodes.map((n) => n.position.x + n.size.w)) + GAP,
24193
+ y: Math.min(...nodes.map((n) => n.position.y))
24194
+ };
24195
+ }
24196
+ function parseData(raw) {
24197
+ const out = {};
24198
+ for (const [k, v] of Object.entries(raw ?? {})) {
24199
+ if (k === KIND_FIELD) throw new CanvasToolError("data.kind n\xE3o muda: apague o n\xF3 e crie outro do tipo certo.");
24200
+ if (!isJson(v)) throw new CanvasToolError(`data.${k} n\xE3o \xE9 JSON v\xE1lido.`);
24201
+ out[k] = v;
24202
+ }
24203
+ return out;
24204
+ }
24205
+ server.registerTool(
24206
+ "canvas_list_boards",
24207
+ {
24208
+ title: "Canvas: listar boards",
24209
+ description: "Lista os boards do Canvas online de que voc\xEA \xE9 membro (id, t\xEDtulo, seu papel, seq, \xFAltima mudan\xE7a). S\xF3 leitura.",
24210
+ inputSchema: { kind: external_exports.enum(["board", "template"]).optional().describe("board (padr\xE3o) ou template (moldes).") },
24211
+ annotations: { readOnlyHint: true, openWorldHint: false }
24212
+ },
24213
+ async ({ kind }) => run(async () => {
24214
+ const boards = await api.listBoards(kind ?? "board");
24215
+ if (!boards.length) return "Nenhum board. Crie um com canvas_create_board.";
24216
+ return boards.map((b) => `- ${b.id} \xB7 "${b.title}" \xB7 ${b.role} \xB7 seq ${b.seq} \xB7 ${String(b.updatedAt).slice(0, 16).replace("T", " ")}`).join("\n");
24217
+ })
24218
+ );
24219
+ server.registerTool(
24220
+ "canvas_read_board",
24221
+ {
24222
+ title: "Canvas: ler board",
24223
+ 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 completo desses n\xF3s.",
24224
+ inputSchema: {
24225
+ board_id: boardId,
24226
+ node_ids: external_exports.array(nodeIdArg).max(20).optional().describe("N\xF3s para ver o data inteiro (sem blobs).")
24227
+ },
24228
+ annotations: { readOnlyHint: true, openWorldHint: false }
24229
+ },
24230
+ async ({ board_id, node_ids }) => run(async () => {
24231
+ const snap = await api.snapshot(board_id);
24232
+ seen.set(board_id, snap.seq);
24233
+ const parts = [boardSummary(snap)];
24234
+ if (node_ids?.length) {
24235
+ parts.push("", "Detalhe:");
24236
+ for (const n of node_ids) parts.push(snap.doc.nodes[n] ? nodeDetail(snap.doc.nodes[n]) : `${n}: n\xE3o existe`);
24237
+ }
24238
+ return parts.join("\n");
24239
+ })
24240
+ );
24241
+ server.registerTool(
24242
+ "canvas_create_board",
24243
+ {
24244
+ title: "Canvas: criar board",
24245
+ description: 'Cria um board online (voc\xEA vira dono). template "narration" j\xE1 cria Contexto, Narra\xE7\xE3o, Roteiro e Locu\xE7\xE3o ligados (Narra\xE7\xE3o.blocks \u2192 Roteiro.blocks, Narra\xE7\xE3o.text \u2192 Locu\xE7\xE3o.text). O kit do Contexto a pessoa escolhe no Canvas. N\xE3o gera m\xEDdia.',
24246
+ inputSchema: {
24247
+ title: external_exports.string().trim().min(1).max(200),
24248
+ template: external_exports.enum(["blank", "narration"]).optional().describe("blank (padr\xE3o) ou narration.")
24249
+ },
24250
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
24251
+ },
24252
+ async ({ title, template }) => run(async () => {
24253
+ const doc = emptyBoard(title);
24254
+ const ids = {};
24255
+ if (template === "narration") {
24256
+ const me = await api.me();
24257
+ let zIndex = 1;
24258
+ const add = (kind, x, y) => {
24259
+ const spec = KINDS[kind];
24260
+ const node = makeNode({
24261
+ type: WORKFLOW_NODE_TYPE,
24262
+ data: { ...spec.data, [KIND_FIELD]: kind },
24263
+ position: { x, y },
24264
+ size: spec.size,
24265
+ zIndex: zIndex++,
24266
+ createdBy: me
24267
+ });
24268
+ doc.nodes[node.id] = node;
24269
+ ids[kind] = node.id;
24270
+ return node.id;
24271
+ };
24272
+ add("context", 200, 200);
24273
+ const narration = add("narration", 650, 200);
24274
+ const shotlist = add("shotlist", 1150, 200);
24275
+ const tts = add("tts", 650, 750);
24276
+ for (const l of [
24277
+ { source: narration, sourcePort: "blocks", target: shotlist, targetPort: "blocks" },
24278
+ { source: narration, sourcePort: "text", target: tts, targetPort: "text" }
24279
+ ]) {
24280
+ const edge = makeEdge({ ...l, createdBy: me });
24281
+ doc.edges[edge.id] = edge;
24282
+ }
24283
+ }
24284
+ const created = await api.createBoard({ title, kind: "board", doc });
24285
+ seen.set(created.id, created.seq);
24286
+ const idLine = Object.keys(ids).length ? `
24287
+ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : "";
24288
+ return `\u2705 Board criado: "${created.title}" \xB7 id ${created.id} \xB7 seq ${created.seq}.${idLine}`;
24289
+ })
24290
+ );
24291
+ server.registerTool(
24292
+ "canvas_add_node",
24293
+ {
24294
+ title: "Canvas: adicionar n\xF3",
24295
+ description: `Adiciona um n\xF3 (n\xE3o executa). 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.`,
24296
+ inputSchema: {
24297
+ board_id: boardId,
24298
+ kind: external_exports.string().min(1).max(40),
24299
+ label: external_exports.string().trim().min(1).max(120).optional().describe("Nome do cart\xE3o (data.label)."),
24300
+ data: external_exports.record(external_exports.unknown()).optional().describe("Campos do data. Ex.: narration {blocks:[{id,text}]}, text {text}, prompt {template}."),
24301
+ position: point.optional(),
24302
+ 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."),
24303
+ 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.")
24304
+ },
24305
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
24306
+ },
24307
+ async (args) => run(
24308
+ () => edit(args.board_id, async (doc) => {
24309
+ const spec = KINDS[args.kind];
24310
+ if (!spec) throw new CanvasToolError(`Tipo "${args.kind}" desconhecido. Tipos: ${kindList()}.`);
24311
+ const me = await api.me();
24312
+ const data = { ...spec.data, ...parseData(args.data), [KIND_FIELD]: args.kind };
24313
+ if (args.label) data.label = args.label;
24314
+ const near = args.inputs?.[0] ? doc.nodes[args.inputs[0].from] : args.outputs?.[0] ? doc.nodes[args.outputs[0].to] : void 0;
24315
+ const position = args.position ?? placeFor(doc, spec.size, near, args.inputs?.[0] ? "right" : "left");
24316
+ const zIndex = args.kind === "section" ? SECTION_Z : Object.values(doc.nodes).reduce((t, n) => Math.max(t, n.zIndex), 0) + 1;
24317
+ const node = makeNode({ type: WORKFLOW_NODE_TYPE, data, position, size: spec.size, zIndex, createdBy: me });
24318
+ const links = [
24319
+ ...(args.inputs ?? []).map((l) => ({ source: l.from, sourcePort: l.from_port, target: node.id, targetPort: l.to_port })),
24320
+ ...(args.outputs ?? []).map((l) => ({ source: node.id, sourcePort: l.from_port, target: l.to, targetPort: l.to_port }))
24321
+ ];
24322
+ for (const l of links) {
24323
+ const warn = knownPort(args.kind, l.source === node.id ? "out" : "in", l.source === node.id ? l.sourcePort : l.targetPort);
24324
+ if (warn === false) {
24325
+ throw new CanvasToolError(
24326
+ `Porta "${l.source === node.id ? l.sourcePort : l.targetPort}" n\xE3o existe em ${args.kind}. Entradas: ${spec.inputs.join(", ") || "\u2014"}; sa\xEDdas: ${spec.outputs.join(", ") || "\u2014"}.`
24327
+ );
24328
+ }
24329
+ }
24330
+ const edges = edgeOps(doc, links, me, /* @__PURE__ */ new Set([node.id]));
24331
+ return {
24332
+ ops: [{ type: "node.add", nodeId: node.id, node }, ...edges.ops],
24333
+ targets: links.map((l) => l.source === node.id ? l.target : l.source),
24334
+ done: `N\xF3 ${args.kind} "${nodeName(node)}" criado: ${node.id}${edges.ops.length ? ` com ${edges.ops.length} liga\xE7\xE3o(\xF5es)` : ""}.${edges.warn}`
24335
+ };
24336
+ })
24337
+ )
24338
+ );
24339
+ server.registerTool(
24340
+ "canvas_update_node",
24341
+ {
24342
+ title: "Canvas: editar n\xF3",
24343
+ description: "Muda campos do data de um n\xF3, chave a chave (null apaga a chave), sem executar. label renomeia. N\xE3o troca o tipo. Leia o n\xF3 com node_ids antes para n\xE3o sobrescrever o que outra pessoa escreveu.",
24344
+ inputSchema: {
24345
+ board_id: boardId,
24346
+ node_id: nodeIdArg,
24347
+ data: external_exports.record(external_exports.unknown()).optional().describe("Chaves de data a gravar. Valor null apaga a chave."),
24348
+ label: external_exports.string().trim().min(1).max(120).optional(),
24349
+ position: point.optional()
24350
+ },
24351
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
24352
+ },
24353
+ async (args) => run(
24354
+ () => edit(args.board_id, async (doc) => {
24355
+ const node = needNode(doc, args.node_id);
24356
+ const ops = [];
24357
+ const patch = parseData(args.data);
24358
+ if (args.label) patch.label = args.label;
24359
+ for (const [k, v] of Object.entries(patch)) {
24360
+ const path = `data.${k}`;
24361
+ if (v === null) {
24362
+ if (Object.hasOwn(node.data, k)) ops.push({ type: "node.set", nodeId: node.id, path, value: unsetValue() });
24363
+ } else if (JSON.stringify(node.data[k]) !== JSON.stringify(v)) {
24364
+ ops.push({ type: "node.set", nodeId: node.id, path, value: setValue(v) });
24365
+ }
24366
+ }
24367
+ if (args.position) {
24368
+ ops.push({ type: "node.set", nodeId: node.id, path: "position.x", value: setValue(args.position.x) });
24369
+ ops.push({ type: "node.set", nodeId: node.id, path: "position.y", value: setValue(args.position.y) });
24370
+ }
24371
+ const keys = ops.map((o) => o.type === "node.set" ? o.path.replace(/^data\./, "") : "").join(", ");
24372
+ return { ops, targets: [node.id], done: `N\xF3 "${nodeName(node)}" atualizado: ${keys || "nada mudou"}` };
24373
+ })
24374
+ )
24375
+ );
24376
+ server.registerTool(
24377
+ "canvas_connect",
24378
+ {
24379
+ title: "Canvas: ligar portas",
24380
+ description: "Liga a sa\xEDda de um n\xF3 \xE0 entrada de outro (ids e portas de canvas_read_board). Liga\xE7\xE3o repetida \xE9 ignorada.",
24381
+ inputSchema: {
24382
+ board_id: boardId,
24383
+ source: nodeIdArg,
24384
+ source_port: external_exports.string().min(1).max(80),
24385
+ target: nodeIdArg,
24386
+ target_port: external_exports.string().min(1).max(80)
24387
+ },
24388
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
24389
+ },
24390
+ async (args) => run(
24391
+ () => edit(args.board_id, async (doc) => {
24392
+ if (args.source === args.target) throw new CanvasToolError("Um n\xF3 n\xE3o se liga nele mesmo.");
24393
+ const link = { source: args.source, sourcePort: args.source_port, target: args.target, targetPort: args.target_port };
24394
+ const { ops, warn } = edgeOps(doc, [link], await api.me(), /* @__PURE__ */ new Set());
24395
+ return {
24396
+ ops,
24397
+ targets: [args.source, args.target],
24398
+ done: ops.length ? `Ligado ${nodeName(doc.nodes[args.source])}.${args.source_port} \u2192 ${nodeName(doc.nodes[args.target])}.${args.target_port}.${warn}` : "A liga\xE7\xE3o j\xE1 existia."
24399
+ };
24400
+ })
24401
+ )
24402
+ );
24403
+ server.registerTool(
24404
+ "canvas_delete",
24405
+ {
24406
+ title: "Canvas: apagar n\xF3s/liga\xE7\xF5es",
24407
+ 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. N\xE3o apaga o board.",
24408
+ inputSchema: {
24409
+ board_id: boardId,
24410
+ node_ids: external_exports.array(nodeIdArg).max(200).optional(),
24411
+ edge_ids: external_exports.array(nodeIdArg).max(200).optional()
24412
+ },
24413
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
24414
+ },
24415
+ async (args) => run(
24416
+ () => edit(args.board_id, async (doc) => {
24417
+ const nodeIds = [...new Set(args.node_ids ?? [])];
24418
+ const edgeIds = [...new Set(args.edge_ids ?? [])];
24419
+ if (!nodeIds.length && !edgeIds.length) throw new CanvasToolError("Informe node_ids e/ou edge_ids.");
24420
+ const missing = [...nodeIds.filter((n) => !doc.nodes[n]), ...edgeIds.filter((e) => !doc.edges[e])];
24421
+ if (missing.length) throw new CanvasToolError(`N\xE3o existem no board: ${missing.join(", ")}. Releia com canvas_read_board.`);
24422
+ const ops = [];
24423
+ const gone = /* @__PURE__ */ new Set();
24424
+ for (const e of edgeIds) {
24425
+ const edge = doc.edges[e];
24426
+ if (nodeIds.includes(edge.source) || nodeIds.includes(edge.target)) continue;
24427
+ ops.push({ type: "edge.remove", edgeId: e });
24428
+ gone.add(e);
24429
+ }
24430
+ for (const n of nodeIds) {
24431
+ for (const op of removeNodeOps(doc, n)) {
24432
+ if (op.type === "edge.remove") {
24433
+ if (gone.has(op.edgeId)) continue;
24434
+ gone.add(op.edgeId);
24435
+ }
24436
+ ops.push(op);
24437
+ }
24438
+ }
24439
+ const names = nodeIds.map((n) => `"${oneLine(nodeName(doc.nodes[n]), 40)}"`);
24440
+ return {
24441
+ ops,
24442
+ targets: nodeIds,
24443
+ done: `Apagado: ${nodeIds.length} n\xF3(s)${names.length ? ` (${names.join(", ")})` : ""} e ${gone.size} liga\xE7\xE3o(\xF5es)`
24444
+ };
24445
+ })
24446
+ )
24447
+ );
24448
+ server.registerTool(
24449
+ "canvas_rename_board",
24450
+ {
24451
+ title: "Canvas: renomear board",
24452
+ description: "Troca o t\xEDtulo do board (board.set). Precisa de papel de editor ou dono.",
24453
+ inputSchema: { board_id: boardId, title: external_exports.string().trim().min(1).max(200) },
24454
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
24455
+ },
24456
+ async ({ board_id, title }) => run(
24457
+ () => edit(board_id, async (doc) => ({
24458
+ ops: doc.title === title ? [] : [{ type: "board.set", field: "title", value: setValue(title) }],
24459
+ targets: [],
24460
+ done: `Board renomeado: "${doc.title}" \u2192 "${title}"`
24461
+ }))
24462
+ )
24463
+ );
24464
+ server.registerTool(
24465
+ "canvas_list_members",
24466
+ {
24467
+ title: "Canvas: membros do board",
24468
+ description: "Lista quem tem acesso ao board (userId, nome, papel). S\xF3 leitura.",
24469
+ inputSchema: { board_id: boardId },
24470
+ annotations: { readOnlyHint: true, openWorldHint: false }
24471
+ },
24472
+ async ({ board_id }) => run(async () => {
24473
+ const members = await api.members(board_id);
24474
+ return members.map((m) => {
24475
+ const name = [m.firstName, m.lastName].filter(Boolean).join(" ") || m.nickname || "(sem nome)";
24476
+ return `- ${m.userId} \xB7 ${name} \xB7 ${m.role}`;
24477
+ }).join("\n");
24478
+ })
24479
+ );
24480
+ server.registerTool(
24481
+ "canvas_set_member",
24482
+ {
24483
+ title: "Canvas: adicionar membro",
24484
+ description: "D\xE1 acesso ao board (ou troca o papel) para um usu\xE1rio pelo userId (uuid). S\xF3 o dono pode. Ainda n\xE3o h\xE1 busca por e-mail: pe\xE7a o userId ou copie de canvas_list_members de outro board.",
24485
+ inputSchema: {
24486
+ board_id: boardId,
24487
+ user_id: external_exports.string().uuid(),
24488
+ role: external_exports.enum(["editor", "viewer"]).describe("editor edita; viewer s\xF3 v\xEA.")
24489
+ },
24490
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
24491
+ },
24492
+ async ({ board_id, user_id, role }) => run(async () => {
24493
+ const r = await api.putMember(board_id, user_id, role);
24494
+ return `\u2705 ${r.userId} agora \xE9 ${r.role} no board.`;
24495
+ })
24496
+ );
24497
+ server.registerTool(
24498
+ "canvas_remove_member",
24499
+ {
24500
+ title: "Canvas: remover membro",
24501
+ description: "Tira o acesso de algu\xE9m ao board (o dono tira qualquer um; quem n\xE3o \xE9 dono s\xF3 sai sozinho). O dono n\xE3o sai.",
24502
+ inputSchema: { board_id: boardId, user_id: external_exports.string().uuid() },
24503
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
24504
+ },
24505
+ async ({ board_id, user_id }) => run(async () => {
24506
+ await api.removeMember(board_id, user_id);
24507
+ return `\u2705 ${user_id} n\xE3o tem mais acesso ao board.`;
24508
+ })
24509
+ );
24510
+ }
24511
+
23447
24512
  // src/version.ts
23448
24513
  import { createRequire } from "module";
23449
24514
  var require2 = createRequire(import.meta.url);
23450
24515
  function readVersion() {
23451
- if ("0.24.0") return "0.24.0";
24516
+ if ("0.26.0") return "0.26.0";
23452
24517
  try {
23453
24518
  const pkg = require2("../package.json");
23454
24519
  return pkg.version ?? "0.0.0-unknown";
@@ -23459,8 +24524,14 @@ function readVersion() {
23459
24524
  var VERSION = readVersion();
23460
24525
 
23461
24526
  // src/server.ts
24527
+ var SERVER_INSTRUCTIONS = [
24528
+ "BePlus: tudo roda pela conta do usu\xE1rio (diamantes, limites e hist\xF3rico na plataforma).",
24529
+ '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.',
24530
+ "Calls: search_calls acha, get_call traz a transcri\xE7\xE3o.",
24531
+ "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."
24532
+ ].join("\n");
23462
24533
  function buildServer(cfg) {
23463
- const server = new McpServer({ name: "beplus", version: VERSION });
24534
+ const server = new McpServer({ name: "beplus", version: VERSION }, { instructions: SERVER_INSTRUCTIONS });
23464
24535
  const client = new BeplusClient(cfg);
23465
24536
  registerGenerationTools(server, client, cfg);
23466
24537
  registerUpscaleTools(server, client, cfg);
@@ -23471,6 +24542,7 @@ function buildServer(cfg) {
23471
24542
  registerProjectTools(server, client, cfg);
23472
24543
  registerClientTools(server, client, cfg);
23473
24544
  registerCallTools(server, client, cfg);
24545
+ registerCanvasTools(server, client, cfg);
23474
24546
  return { server, client };
23475
24547
  }
23476
24548