@de-otio/epimethian-mcp 6.8.0 → 6.9.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/cli/index.js CHANGED
@@ -35481,7 +35481,7 @@ async function getPage(pageId, includeBody) {
35481
35481
  async function _rawCreatePage(spaceId, title, body, parentId, clientLabel) {
35482
35482
  const cfg = await getConfig();
35483
35483
  const pageBody = normalizeBodyForSubmit(body);
35484
- const epimethianTag = `Epimethian v${"6.8.0"}`;
35484
+ const epimethianTag = `Epimethian v${"6.9.0"}`;
35485
35485
  const versionMsg = cfg.attribution && clientLabel ? `Created by ${clientLabel} (via ${epimethianTag})` : `Created by ${epimethianTag}`;
35486
35486
  const payload = {
35487
35487
  title,
@@ -35502,7 +35502,7 @@ async function _rawCreatePage(spaceId, title, body, parentId, clientLabel) {
35502
35502
  async function _rawUpdatePage(pageId, opts) {
35503
35503
  const cfg = await getConfig();
35504
35504
  const newVersion = opts.version + 1;
35505
- const epimethianTag = `Epimethian v${"6.8.0"}`;
35505
+ const epimethianTag = `Epimethian v${"6.9.0"}`;
35506
35506
  const effectiveClient = cfg.attribution ? opts.clientLabel : void 0;
35507
35507
  let versionMessage;
35508
35508
  if (opts.versionMessage && effectiveClient)
@@ -47715,7 +47715,7 @@ function enforceContentSafetyGuards(input) {
47715
47715
  if (oldLen > SHRINKAGE_GUARD_MIN_OLD_LEN && newLen < oldLen * SHRINKAGE_GUARD_MAX_RATIO && !confirmShrinkage) {
47716
47716
  const pct = Math.round((1 - newLen / oldLen) * 100);
47717
47717
  throw new ConverterError(
47718
- `Body would shrink from ${oldLen} to ${newLen} characters (${pct}% reduction). This may indicate accidental content loss. Re-submit with confirm_shrinkage: true to proceed, or omit replace_body to use token-aware preservation.`,
47718
+ `Body would shrink from ${oldLen} to ${newLen} characters (${pct}% reduction). This may indicate accidental content loss. Re-submit with confirm_shrinkage: true to proceed (accepted by update_page, update_page_section, and update_page_sections).`,
47719
47719
  SHRINKAGE_NOT_CONFIRMED
47720
47720
  );
47721
47721
  }
@@ -48428,6 +48428,7 @@ async function safePrepareBody(input) {
48428
48428
  confirmDeletions,
48429
48429
  confirmShrinkage,
48430
48430
  confirmStructureLoss,
48431
+ fullPageBody,
48431
48432
  replaceBody,
48432
48433
  allowRawHtml,
48433
48434
  confluenceBaseUrl
@@ -48556,9 +48557,15 @@ Pick one path:
48556
48557
  assertDeletionAckMatches(confirmDeletions, deletedTokens);
48557
48558
  }
48558
48559
  if (scope !== "additive" && currentBody !== void 0) {
48560
+ let guardOld = currentBody;
48561
+ let guardNew = finalStorage;
48562
+ if (scope === "section" && fullPageBody !== void 0 && fullPageBody.includes(currentBody)) {
48563
+ guardOld = fullPageBody;
48564
+ guardNew = fullPageBody.replace(currentBody, () => finalStorage);
48565
+ }
48559
48566
  enforceContentSafetyGuards({
48560
- oldStorage: currentBody,
48561
- newStorage: finalStorage,
48567
+ oldStorage: guardOld,
48568
+ newStorage: guardNew,
48562
48569
  confirmShrinkage,
48563
48570
  confirmStructureLoss: confirmStructureLoss || replaceBody === true,
48564
48571
  // confirmDeletions (any truthy form, incl. a non-empty string[]) OR
@@ -48822,6 +48829,8 @@ async function safePrepareMultiSectionBody(input) {
48822
48829
  currentStorage,
48823
48830
  sections,
48824
48831
  confirmDeletions,
48832
+ confirmShrinkage,
48833
+ confirmStructureLoss,
48825
48834
  allowRawHtml,
48826
48835
  confluenceBaseUrl
48827
48836
  } = input;
@@ -48904,6 +48913,10 @@ async function safePrepareMultiSectionBody(input) {
48904
48913
  currentBody: loc.body,
48905
48914
  scope: "section",
48906
48915
  confirmDeletions: confirmDeletions ? true : void 0,
48916
+ confirmShrinkage,
48917
+ confirmStructureLoss,
48918
+ // Measure each section's guards page-relative (see safePrepareBody).
48919
+ fullPageBody: currentStorage,
48907
48920
  ...allowRawHtml !== void 0 ? { allowRawHtml } : {},
48908
48921
  ...confluenceBaseUrl !== void 0 ? { confluenceBaseUrl } : {}
48909
48922
  });
@@ -50278,7 +50291,7 @@ __export(upgrade_exports, {
50278
50291
  runUpgrade: () => runUpgrade
50279
50292
  });
50280
50293
  async function runUpgrade() {
50281
- const currentVersion = "6.8.0";
50294
+ const currentVersion = "6.9.0";
50282
50295
  console.log(`epimethian-mcp upgrade: current version v${currentVersion}`);
50283
50296
  let pending = await getPendingUpdate();
50284
50297
  if (!pending) {
@@ -62761,6 +62774,8 @@ batch_token: ${batch.token}` + echo;
62761
62774
  ),
62762
62775
  version_message: external_exports.string().optional().describe("Optional version comment"),
62763
62776
  confirm_deletions: external_exports.boolean().default(false).describe("Set to true to acknowledge that your markdown removes preserved macros, emoticons, or rich elements from this section. Required when any preserved element would be deleted."),
62777
+ confirm_shrinkage: external_exports.boolean().default(false).describe("Set to true to acknowledge a large body reduction. The shrinkage guard is measured against the WHOLE page (not the isolated section), so a small edit to a short section will not trip it; this flag is only needed for a genuinely large page-level reduction."),
62778
+ confirm_structure_loss: external_exports.boolean().default(false).describe("Set to true to acknowledge a large drop in heading count, measured against the whole page."),
62764
62779
  confirm_token: external_exports.string().optional().describe("Soft-confirmation token from a prior SOFT_CONFIRMATION_REQUIRED response. Single-use; bound to this exact diff and page version."),
62765
62780
  batch_token: external_exports.string().optional().describe(
62766
62781
  "Batch authorisation token from a prior authorise_destructive_writes call. Bypasses the per-call confirmation gate when valid for this page_id. Validation failures fall through to the per-call confirm_token / soft-confirm flow."
@@ -62771,7 +62786,7 @@ batch_token: ${batch.token}` + echo;
62771
62786
  outputSchema: writeOutputSchema,
62772
62787
  annotations: { destructiveHint: false, idempotentHint: false }
62773
62788
  },
62774
- async ({ page_id, section, body, find_replace, version: version2, version_message, confirm_deletions, confirm_token, batch_token }) => {
62789
+ async ({ page_id, section, body, find_replace, version: version2, version_message, confirm_deletions, confirm_shrinkage, confirm_structure_loss, confirm_token, batch_token }) => {
62775
62790
  const blocked = writeGuard("update_page_section", config3);
62776
62791
  if (blocked) return blocked;
62777
62792
  let batchReservationId;
@@ -62863,7 +62878,12 @@ batch_token: ${batch.token}` + echo;
62863
62878
  }
62864
62879
  };
62865
62880
  }
62866
- if (confirm_deletions) {
62881
+ const sectionFlagsSet = listDestructiveFlagsSet({
62882
+ confirmShrinkage: confirm_shrinkage,
62883
+ confirmStructureLoss: confirm_structure_loss,
62884
+ confirmDeletions: confirm_deletions
62885
+ });
62886
+ if (sectionFlagsSet.length > 0) {
62867
62887
  const batchAttempt = await tryBatchTokenForWrite({
62868
62888
  batch_token,
62869
62889
  cloudId,
@@ -62871,7 +62891,7 @@ batch_token: ${batch.token}` + echo;
62871
62891
  });
62872
62892
  batchReservationId = batchAttempt.batchReservationId;
62873
62893
  if (batchReservationId === void 0) {
62874
- const deletionSummary = tryForecastDeletions(currentSectionBody, body, cfg.url);
62894
+ const deletionSummary = confirm_deletions && body ? tryForecastDeletions(currentSectionBody, body, cfg.url) : null;
62875
62895
  const diffHash = cloudId && pageVersion > 0 ? computeDiffHash(body ?? currentSectionBody, pageVersion) : void 0;
62876
62896
  const tokenResult = await maybeConsumeConfirmToken({
62877
62897
  confirm_token,
@@ -62889,11 +62909,11 @@ batch_token: ${batch.token}` + echo;
62889
62909
  } else if (tokenResult === "no_token") {
62890
62910
  await gateOperation(server, {
62891
62911
  tool: "update_page_section",
62892
- summary: `Update section "${section}" in page ${page_id} with confirm_deletions?`,
62912
+ summary: `Update section "${section}" in page ${page_id} with ${sectionFlagsSet.join(", ")}?`,
62893
62913
  details: {
62894
62914
  page_id,
62895
62915
  section,
62896
- source: "confirm_deletions",
62916
+ flags: sectionFlagsSet.join(","),
62897
62917
  ...deletionSummary ? { deletionSummary } : {}
62898
62918
  },
62899
62919
  cloudId,
@@ -62909,6 +62929,10 @@ batch_token: ${batch.token}` + echo;
62909
62929
  currentBody: currentSectionBody,
62910
62930
  scope: "section",
62911
62931
  confirmDeletions: confirm_deletions || void 0,
62932
+ confirmShrinkage: confirm_shrinkage,
62933
+ confirmStructureLoss: confirm_structure_loss,
62934
+ // Measure shrink/structure/floor guards against the whole page.
62935
+ fullPageBody: fullBody,
62912
62936
  confluenceBaseUrl: cfg.url
62913
62937
  });
62914
62938
  const newFullBody = replaceSection(fullBody, section, prepared.finalStorage);
@@ -62931,6 +62955,11 @@ batch_token: ${batch.token}` + echo;
62931
62955
  deletedTokens: prepared.deletedTokens,
62932
62956
  operation: "update_page_section",
62933
62957
  clientLabel: getClientLabel(server),
62958
+ // Recorded for the destructive-flag audit / version-message suffix;
62959
+ // guards already ran in safePrepareBody (page-relative).
62960
+ confirmShrinkage: confirm_shrinkage,
62961
+ confirmStructureLoss: confirm_structure_loss,
62962
+ confirmDeletions: confirm_deletions || void 0,
62934
62963
  cloudId
62935
62964
  });
62936
62965
  const warnings = [];
@@ -62985,6 +63014,8 @@ batch_token: ${batch.token}` + echo;
62985
63014
  confirm_deletions: external_exports.boolean().default(false).describe(
62986
63015
  "Set to true to acknowledge that the aggregated set of sections removes preserved macros, emoticons, or rich elements. Required when ANY section would delete a preserved element. The deletion-summary gate fires once on the AGGREGATE \u2014 a caller cannot bypass the gate by spreading deletions across sections."
62987
63016
  ),
63017
+ confirm_shrinkage: external_exports.boolean().default(false).describe("Set to true to acknowledge a large body reduction. Each section's shrinkage is measured against the WHOLE page, so small edits to short sections will not trip it; needed only for a genuinely large page-level reduction."),
63018
+ confirm_structure_loss: external_exports.boolean().default(false).describe("Set to true to acknowledge a large drop in heading count, measured against the whole page."),
62988
63019
  confirm_token: external_exports.string().optional().describe("Soft-confirmation token from a prior SOFT_CONFIRMATION_REQUIRED response. Single-use; bound to this exact diff and page version."),
62989
63020
  batch_token: external_exports.string().optional().describe(
62990
63021
  "Batch authorisation token from a prior authorise_destructive_writes call. Bypasses the per-call confirmation gate when valid for this page_id. Validation failures fall through to the per-call confirm_token / soft-confirm flow."
@@ -63002,7 +63033,7 @@ batch_token: ${batch.token}` + echo;
63002
63033
  },
63003
63034
  annotations: { destructiveHint: false, idempotentHint: false }
63004
63035
  },
63005
- async ({ page_id, version: version2, version_message, confirm_deletions, sections, confirm_token, batch_token }) => {
63036
+ async ({ page_id, version: version2, version_message, confirm_deletions, confirm_shrinkage, confirm_structure_loss, sections, confirm_token, batch_token }) => {
63006
63037
  const blocked = writeGuard("update_page_sections", config3);
63007
63038
  if (blocked) return blocked;
63008
63039
  let batchReservationId;
@@ -63020,7 +63051,12 @@ batch_token: ${batch.token}` + echo;
63020
63051
  `Could not resolve current version for page ${page_id} (server returned no version metadata)`
63021
63052
  );
63022
63053
  }
63023
- if (confirm_deletions) {
63054
+ const sectionsFlagsSet = listDestructiveFlagsSet({
63055
+ confirmShrinkage: confirm_shrinkage,
63056
+ confirmStructureLoss: confirm_structure_loss,
63057
+ confirmDeletions: confirm_deletions
63058
+ });
63059
+ if (sectionsFlagsSet.length > 0) {
63024
63060
  const batchAttempt = await tryBatchTokenForWrite({
63025
63061
  batch_token,
63026
63062
  cloudId,
@@ -63037,27 +63073,29 @@ batch_token: ${batch.token}` + echo;
63037
63073
  other: 0
63038
63074
  };
63039
63075
  let any = false;
63040
- for (const s of sections) {
63041
- let currentSectionBody = null;
63042
- try {
63043
- currentSectionBody = extractSectionBody(fullBody, s.section);
63044
- } catch {
63045
- currentSectionBody = null;
63046
- }
63047
- if (currentSectionBody === null) continue;
63048
- const summary = tryForecastDeletions(
63049
- currentSectionBody,
63050
- s.body,
63051
- cfg.url
63052
- );
63053
- if (summary !== null) {
63054
- summed.tocs += summary.tocs;
63055
- summed.links += summary.links;
63056
- summed.structuredMacros += summary.structuredMacros;
63057
- summed.codeMacros += summary.codeMacros;
63058
- summed.plainElements += summary.plainElements;
63059
- summed.other += summary.other;
63060
- any = true;
63076
+ if (confirm_deletions) {
63077
+ for (const s of sections) {
63078
+ let currentSectionBody = null;
63079
+ try {
63080
+ currentSectionBody = extractSectionBody(fullBody, s.section);
63081
+ } catch {
63082
+ currentSectionBody = null;
63083
+ }
63084
+ if (currentSectionBody === null) continue;
63085
+ const summary = tryForecastDeletions(
63086
+ currentSectionBody,
63087
+ s.body,
63088
+ cfg.url
63089
+ );
63090
+ if (summary !== null) {
63091
+ summed.tocs += summary.tocs;
63092
+ summed.links += summary.links;
63093
+ summed.structuredMacros += summary.structuredMacros;
63094
+ summed.codeMacros += summary.codeMacros;
63095
+ summed.plainElements += summary.plainElements;
63096
+ summed.other += summary.other;
63097
+ any = true;
63098
+ }
63061
63099
  }
63062
63100
  }
63063
63101
  const aggregateBody = sections.map((s) => s.body).join("\n");
@@ -63078,11 +63116,11 @@ batch_token: ${batch.token}` + echo;
63078
63116
  } else if (tokenResult === "no_token") {
63079
63117
  await gateOperation(server, {
63080
63118
  tool: "update_page_sections",
63081
- summary: `Update ${sections.length} section${sections.length === 1 ? "" : "s"} in page ${page_id} with confirm_deletions?`,
63119
+ summary: `Update ${sections.length} section${sections.length === 1 ? "" : "s"} in page ${page_id} with ${sectionsFlagsSet.join(", ")}?`,
63082
63120
  details: {
63083
63121
  page_id,
63084
63122
  section_count: sections.length,
63085
- source: "confirm_deletions",
63123
+ flags: sectionsFlagsSet.join(","),
63086
63124
  ...any ? { deletionSummary: summed } : {}
63087
63125
  },
63088
63126
  cloudId,
@@ -63097,6 +63135,8 @@ batch_token: ${batch.token}` + echo;
63097
63135
  currentStorage: fullBody,
63098
63136
  sections,
63099
63137
  confirmDeletions: confirm_deletions,
63138
+ confirmShrinkage: confirm_shrinkage,
63139
+ confirmStructureLoss: confirm_structure_loss,
63100
63140
  confluenceBaseUrl: cfg.url
63101
63141
  });
63102
63142
  const mergedVersionMessage = prepared.versionMessage && version_message ? `${version_message}; ${prepared.versionMessage}` : prepared.versionMessage || version_message || "";
@@ -63113,6 +63153,8 @@ batch_token: ${batch.token}` + echo;
63113
63153
  operation: "update_page_section",
63114
63154
  clientLabel: getClientLabel(server),
63115
63155
  confirmDeletions: confirm_deletions,
63156
+ confirmShrinkage: confirm_shrinkage,
63157
+ confirmStructureLoss: confirm_structure_loss,
63116
63158
  cloudId
63117
63159
  });
63118
63160
  const warnings = [];
@@ -63569,12 +63611,18 @@ ${truncated}`);
63569
63611
  "Name for the diagram file (e.g., 'architecture.drawio'). Will have .drawio appended if not present."
63570
63612
  ),
63571
63613
  append: external_exports.boolean().default(true).describe(
63572
- "If true, appends the diagram to existing page content. If false, replaces the page body."
63614
+ "If true, appends the diagram to the end of the page. If false, replaces the page body. Ignored when after_section or return_macro_only is set."
63615
+ ),
63616
+ after_section: external_exports.string().optional().describe(
63617
+ "Heading text of a section to place the diagram in. The diagram is inserted at the END of that section's content (before the next heading) instead of at the end of the page. Use headings_only / get_page to find section names. Takes precedence over `append`."
63618
+ ),
63619
+ return_macro_only: external_exports.boolean().default(false).describe(
63620
+ "If true, upload the diagram as an attachment but DO NOT modify the page body; instead return the draw.io macro storage markup so you can place it yourself with update_page / update_page_section. Use this when you need precise positioning the other options can't express. Takes precedence over `after_section` and `append`. (The attachment is created either way; if you never embed the returned macro it is left orphaned.)"
63573
63621
  )
63574
63622
  },
63575
63623
  annotations: { destructiveHint: false, idempotentHint: false }
63576
63624
  },
63577
- async ({ page_id, diagram_xml, diagram_name, append }) => {
63625
+ async ({ page_id, diagram_xml, diagram_name, append, after_section, return_macro_only }) => {
63578
63626
  const blocked = writeGuard("add_drawio_diagram", config3);
63579
63627
  if (blocked) return blocked;
63580
63628
  try {
@@ -63609,15 +63657,47 @@ ${truncated}`);
63609
63657
  ].join("\n");
63610
63658
  const current = await getPage(page_id, true);
63611
63659
  const existingBody = current.body?.storage?.value ?? current.body?.value ?? "";
63612
- const newBody = append ? `${existingBody}
63660
+ if (return_macro_only) {
63661
+ return toolResult(
63662
+ `Diagram "${filename}" uploaded to page ${current.title} (ID: ${page_id}, attachment ID: ${attachmentId}, macro ID: ${macroId}). The page body was NOT modified. Embed the diagram by inserting this macro where you want it (its baseUrl/pageId/diagramName are bound to this page):
63663
+
63664
+ ${macro}${echo}`
63665
+ );
63666
+ }
63667
+ let newBody;
63668
+ let placementNote = "";
63669
+ if (after_section !== void 0) {
63670
+ const sectionBody = extractSectionBody(existingBody, after_section);
63671
+ if (sectionBody === null) {
63672
+ return toolError(
63673
+ new Error(
63674
+ `Section "${after_section}" not found. Use headings_only to see available sections.`
63675
+ )
63676
+ );
63677
+ }
63678
+ const spliced = replaceSection(
63679
+ existingBody,
63680
+ after_section,
63681
+ `${sectionBody}
63682
+ ${macro}`
63683
+ );
63684
+ if (spliced === null) {
63685
+ return toolError(
63686
+ new Error(
63687
+ `Section "${after_section}" not found. Use headings_only to see available sections.`
63688
+ )
63689
+ );
63690
+ }
63691
+ newBody = spliced;
63692
+ placementNote = ` in section "${after_section}"`;
63693
+ } else {
63694
+ newBody = append ? `${existingBody}
63613
63695
  ${macro}` : macro;
63696
+ }
63614
63697
  const prepared = await safePrepareBody({
63615
63698
  body: newBody,
63616
63699
  currentBody: existingBody,
63617
63700
  scope: "full"
63618
- // append=true is additive but newBody already contains the concat,
63619
- // so "full" is correct: guards compare existingBody vs the complete
63620
- // new body, which is what we want for both branches.
63621
63701
  });
63622
63702
  const submitted = await safeSubmitPage({
63623
63703
  pageId: page_id,
@@ -63636,7 +63716,7 @@ ${macro}` : macro;
63636
63716
  const badgeResult = await markPageUnverified(submitted.page.id, config3);
63637
63717
  if (badgeResult.warning) warnings.push(badgeResult.warning);
63638
63718
  return toolResult(
63639
- appendWarnings(`Diagram "${filename}" added to page ${submitted.page.title} (ID: ${submitted.page.id}, version: ${submitted.newVersion}, attachment ID: ${attachmentId}, macro ID: ${macroId})`, warnings) + echo
63719
+ appendWarnings(`Diagram "${filename}" added to page ${submitted.page.title}${placementNote} (ID: ${submitted.page.id}, version: ${submitted.newVersion}, attachment ID: ${attachmentId}, macro ID: ${macroId})`, warnings) + echo
63640
63720
  );
63641
63721
  } catch (err) {
63642
63722
  return toolErrorWithContext(err, { operation: "add_drawio_diagram", resource: `page ${page_id}`, profile: config3.profile });
@@ -64423,7 +64503,7 @@ ${titleFenced}${echo2}`
64423
64503
  inputSchema: {}
64424
64504
  },
64425
64505
  async () => {
64426
- let text2 = `epimethian-mcp v${"6.8.0"}`;
64506
+ let text2 = `epimethian-mcp v${"6.9.0"}`;
64427
64507
  try {
64428
64508
  const pending = await getPendingUpdate();
64429
64509
  if (pending) {
@@ -64454,7 +64534,7 @@ ${label} update available: v${pending.current} \u2192 v${pending.latest}. Run \`
64454
64534
  const pending = await getPendingUpdate();
64455
64535
  if (!pending) {
64456
64536
  return toolResult(
64457
- `epimethian-mcp v${"6.8.0"} is already up to date.`
64537
+ `epimethian-mcp v${"6.9.0"} is already up to date.`
64458
64538
  );
64459
64539
  }
64460
64540
  const output = await performUpgrade(pending.latest);
@@ -64476,7 +64556,7 @@ async function startRecoveryServer(profile) {
64476
64556
  const server = new McpServer(
64477
64557
  {
64478
64558
  name: `confluence-${profile}-setup-needed`,
64479
- version: "6.8.0"
64559
+ version: "6.9.0"
64480
64560
  },
64481
64561
  {
64482
64562
  instructions: `The Confluence profile "${profile}" referenced by CONFLUENCE_PROFILE has no keychain entry, so no Confluence tools are available. Call the setup_profile tool for instructions to create it.`
@@ -64527,21 +64607,21 @@ async function main() {
64527
64607
  const serverName = config3.profile ? `confluence-${config3.profile}` : "confluence";
64528
64608
  const server = new McpServer({
64529
64609
  name: serverName,
64530
- version: "6.8.0"
64610
+ version: "6.9.0"
64531
64611
  });
64532
64612
  await registerTools(server, config3);
64533
64613
  const transport = new StdioServerTransport();
64534
64614
  await server.connect(transport);
64535
64615
  try {
64536
64616
  const pending = await getPendingUpdate();
64537
- if (pending && pending.current === "6.8.0") {
64617
+ if (pending && pending.current === "6.9.0") {
64538
64618
  console.error(
64539
64619
  `epimethian-mcp: update available: v${pending.current} \u2192 v${pending.latest} (${pending.type}). Run \`epimethian-mcp upgrade\` to install.`
64540
64620
  );
64541
64621
  }
64542
64622
  } catch {
64543
64623
  }
64544
- checkForUpdates("6.8.0").catch(() => {
64624
+ checkForUpdates("6.9.0").catch(() => {
64545
64625
  });
64546
64626
  }
64547
64627