@attentionform/astra-mcp-server 0.5.0 → 0.5.1
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
|
@@ -212,6 +212,44 @@ async function refreshAstInPlace(target, parser2, options = {}) {
|
|
|
212
212
|
return notes;
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
+
// ../engine/src/heading-style.ts
|
|
216
|
+
function headingLevelOfStyle(lowerStyleId) {
|
|
217
|
+
const id = lowerStyleId.replace(/\s+/g, "");
|
|
218
|
+
const numbered = /^heading([1-9])$/.exec(id);
|
|
219
|
+
if (numbered) return Number(numbered[1]);
|
|
220
|
+
if (id === "subtitle") return 2;
|
|
221
|
+
return 1;
|
|
222
|
+
}
|
|
223
|
+
function headingLevelOfStyleId(styleId) {
|
|
224
|
+
if (!styleId) return null;
|
|
225
|
+
const lower = styleId.toLowerCase();
|
|
226
|
+
if (!(lower.includes("heading") || lower.includes("title") || lower.includes("subtitle"))) return null;
|
|
227
|
+
return headingLevelOfStyle(lower);
|
|
228
|
+
}
|
|
229
|
+
function ownParagraphStyleId(paragraph) {
|
|
230
|
+
if (paragraph.tagName !== "w:p") return void 0;
|
|
231
|
+
for (let i = 0; i < paragraph.childNodes.length; i++) {
|
|
232
|
+
const pPr = paragraph.childNodes[i];
|
|
233
|
+
if (pPr.nodeType !== 1 || pPr.tagName !== "w:pPr") continue;
|
|
234
|
+
for (let k = 0; k < pPr.childNodes.length; k++) {
|
|
235
|
+
const el = pPr.childNodes[k];
|
|
236
|
+
if (el.nodeType === 1 && el.tagName === "w:pStyle") return el.getAttribute("w:val") || void 0;
|
|
237
|
+
}
|
|
238
|
+
return void 0;
|
|
239
|
+
}
|
|
240
|
+
return void 0;
|
|
241
|
+
}
|
|
242
|
+
function headingOfNode(node) {
|
|
243
|
+
const style = ownParagraphStyleId(node.xml);
|
|
244
|
+
const level = headingLevelOfStyleId(style);
|
|
245
|
+
return style !== void 0 && level !== null ? { style, level } : null;
|
|
246
|
+
}
|
|
247
|
+
function countHeadings(ast) {
|
|
248
|
+
let n = 0;
|
|
249
|
+
for (const sec of ast.sections) for (const node of sec.nodes) if (headingOfNode(node)) n += 1;
|
|
250
|
+
return n;
|
|
251
|
+
}
|
|
252
|
+
|
|
215
253
|
// ../engine/src/ooxml/revision-ids.ts
|
|
216
254
|
var REVISION_TAGS = [
|
|
217
255
|
"ins",
|
|
@@ -3033,11 +3071,12 @@ function sortContainerChildren(container, order) {
|
|
|
3033
3071
|
const after = next.filter((node) => node.nodeType === 1).map((node) => node.tagName);
|
|
3034
3072
|
return { reordered: true, before, after };
|
|
3035
3073
|
}
|
|
3036
|
-
function passSorts(doc, entries) {
|
|
3074
|
+
function passSorts(doc, entries, reorderedContainers) {
|
|
3037
3075
|
for (const { tag, order } of SORT_CONTAINERS) {
|
|
3038
3076
|
for (const container of snapshotElements(doc, tag)) {
|
|
3039
3077
|
const { reordered, before, after } = sortContainerChildren(container, order);
|
|
3040
3078
|
if (reordered) {
|
|
3079
|
+
reorderedContainers?.push(elementXPath(container));
|
|
3041
3080
|
entries.push(`Reordered ${tag} into schema order${locationSuffix(container)}: [${before.join(",")}] \u2192 [${after.join(",")}]`);
|
|
3042
3081
|
}
|
|
3043
3082
|
}
|
|
@@ -3100,6 +3139,9 @@ function normalizePartXml(partName, xml) {
|
|
|
3100
3139
|
if (entries.length === 0) return { xml, normalized: [] };
|
|
3101
3140
|
return { xml: partSerializer.serializeToString(doc), normalized: entries };
|
|
3102
3141
|
}
|
|
3142
|
+
var ORDERED_CONTAINERS = new Map(
|
|
3143
|
+
SORT_CONTAINERS.map((c) => [c.tag.slice(c.tag.indexOf(":") + 1), new Set(c.order)])
|
|
3144
|
+
);
|
|
3103
3145
|
|
|
3104
3146
|
// ../engine/src/ooxml/omml.ts
|
|
3105
3147
|
var OMML_MAX_DEPTH = 6;
|
|
@@ -4562,14 +4604,16 @@ var OoxmlValidationError = class extends Error {
|
|
|
4562
4604
|
errors;
|
|
4563
4605
|
firstError;
|
|
4564
4606
|
code = "E_VALIDATION";
|
|
4565
|
-
remedy
|
|
4607
|
+
remedy;
|
|
4566
4608
|
retryable = false;
|
|
4567
|
-
|
|
4609
|
+
/** `remedy` replaces the default text for callers that know a better next step (PDF export does). */
|
|
4610
|
+
constructor(errors, context = "Document", remedy) {
|
|
4568
4611
|
const first = errors[0] || {};
|
|
4569
4612
|
const xp = typeof first.xPath === "string" && first.xPath || typeof first.path === "string" && first.path || "unknown";
|
|
4570
4613
|
const hint = tableIndexHintForXPath(first.xPath ?? first.path);
|
|
4571
4614
|
const detail = `${first.description || "Schema error"} @ ${xp}${hint ? ` (${hint})` : ""}`;
|
|
4572
4615
|
super(`[OOXML Schema Violation] ${context} has ${errors.length} validation error(s): ${detail}`);
|
|
4616
|
+
this.remedy = remedy ?? "re-export the file from Word (or pick a valid .docx) and retry";
|
|
4573
4617
|
this.name = "OoxmlValidationError";
|
|
4574
4618
|
this.errors = errors;
|
|
4575
4619
|
this.firstError = first;
|
|
@@ -10912,22 +10956,10 @@ var DocxParser = class _DocxParser {
|
|
|
10912
10956
|
}
|
|
10913
10957
|
let isHeading = false;
|
|
10914
10958
|
let headingLevel = void 0;
|
|
10915
|
-
const
|
|
10916
|
-
if (
|
|
10917
|
-
|
|
10918
|
-
|
|
10919
|
-
if (lowerVal.includes("heading") || lowerVal.includes("title") || lowerVal.includes("subtitle")) {
|
|
10920
|
-
isHeading = true;
|
|
10921
|
-
if (lowerVal.includes("heading 1") || lowerVal === "heading1" || lowerVal === "h1" || lowerVal.includes("title")) {
|
|
10922
|
-
headingLevel = 1;
|
|
10923
|
-
} else if (lowerVal.includes("heading 2") || lowerVal === "heading2" || lowerVal === "h2" || lowerVal.includes("subtitle")) {
|
|
10924
|
-
headingLevel = 2;
|
|
10925
|
-
} else if (lowerVal.includes("heading 3") || lowerVal === "heading3" || lowerVal === "h3") {
|
|
10926
|
-
headingLevel = 3;
|
|
10927
|
-
} else {
|
|
10928
|
-
headingLevel = 1;
|
|
10929
|
-
}
|
|
10930
|
-
}
|
|
10959
|
+
const ownLevel = headingLevelOfStyleId(ownParagraphStyleId(node));
|
|
10960
|
+
if (ownLevel !== null) {
|
|
10961
|
+
isHeading = true;
|
|
10962
|
+
headingLevel = ownLevel;
|
|
10931
10963
|
}
|
|
10932
10964
|
const hasTrackedChanges = nodeHasTrackedChanges(node);
|
|
10933
10965
|
if (hasTrackedChanges) summary.hasTrackedChanges = true;
|
|
@@ -11223,8 +11255,7 @@ var DocxParser = class _DocxParser {
|
|
|
11223
11255
|
}
|
|
11224
11256
|
}
|
|
11225
11257
|
}
|
|
11226
|
-
const
|
|
11227
|
-
const namedStyle = pStyleEl ? pStyleEl.getAttribute("w:val") || void 0 : void 0;
|
|
11258
|
+
const namedStyle = ownParagraphStyleId(element);
|
|
11228
11259
|
let nodeType = element.tagName === "w:p" ? "paragraph" : "table";
|
|
11229
11260
|
let chartData = void 0;
|
|
11230
11261
|
let detectedChart = void 0;
|
|
@@ -15535,6 +15566,89 @@ function assertNoCellAddress(action, fields, ast) {
|
|
|
15535
15566
|
// ../engine/src/operations/table.ts
|
|
15536
15567
|
var import_xmldom12 = require("@xmldom/xmldom");
|
|
15537
15568
|
var crypto3 = __toESM(require("crypto"));
|
|
15569
|
+
|
|
15570
|
+
// ../engine/src/operations/colour.ts
|
|
15571
|
+
var COLOUR_NAMES = {
|
|
15572
|
+
red: "FF0000",
|
|
15573
|
+
green: "008000",
|
|
15574
|
+
blue: "0000FF",
|
|
15575
|
+
black: "000000",
|
|
15576
|
+
white: "FFFFFF",
|
|
15577
|
+
yellow: "FFFF00",
|
|
15578
|
+
orange: "FFA500",
|
|
15579
|
+
gray: "808080",
|
|
15580
|
+
grey: "808080",
|
|
15581
|
+
purple: "800080"
|
|
15582
|
+
};
|
|
15583
|
+
var HEX = /^#?([0-9A-Fa-f]{6})$/;
|
|
15584
|
+
function normalizeColour(input, options = {}) {
|
|
15585
|
+
if (typeof input !== "string") return void 0;
|
|
15586
|
+
const text2 = input.trim();
|
|
15587
|
+
const hex = HEX.exec(text2);
|
|
15588
|
+
if (hex) return hex[1];
|
|
15589
|
+
const lower = text2.toLowerCase();
|
|
15590
|
+
if (options.allowAuto && lower === "auto") return "auto";
|
|
15591
|
+
return Object.prototype.hasOwnProperty.call(COLOUR_NAMES, lower) ? COLOUR_NAMES[lower] : void 0;
|
|
15592
|
+
}
|
|
15593
|
+
function resolveColourInput(input, options = {}) {
|
|
15594
|
+
const value = normalizeColour(input, options);
|
|
15595
|
+
return value === void 0 ? input : value;
|
|
15596
|
+
}
|
|
15597
|
+
function colourForms(options = {}) {
|
|
15598
|
+
const names = Object.keys(COLOUR_NAMES).join(", ");
|
|
15599
|
+
return `6 hex digits such as FF0000 (optionally with #), ${options.allowAuto ? '"auto", ' : ""}or one of: ${names}`;
|
|
15600
|
+
}
|
|
15601
|
+
function colourError(field, value, options = {}) {
|
|
15602
|
+
return new Error(
|
|
15603
|
+
`E_VALIDATION: ${field} must be ${colourForms(options)}. Got ${JSON.stringify(value)}. Remedy: pass ${field} as hex, for example "FF0000", or a colour name such as "red".`
|
|
15604
|
+
);
|
|
15605
|
+
}
|
|
15606
|
+
function requireColour(field, input, options = {}) {
|
|
15607
|
+
const value = normalizeColour(input, options);
|
|
15608
|
+
if (value === void 0) throw colourError(field, input, options);
|
|
15609
|
+
return value;
|
|
15610
|
+
}
|
|
15611
|
+
var BORDER_WIDTH_MIN = 1;
|
|
15612
|
+
var BORDER_WIDTH_MAX = 96;
|
|
15613
|
+
function requireBorderSize(field, input, fallback) {
|
|
15614
|
+
if (input === void 0) return fallback;
|
|
15615
|
+
const n = typeof input === "number" ? input : typeof input === "string" && input.trim() !== "" ? Number(input) : Number.NaN;
|
|
15616
|
+
if (!Number.isInteger(n) || n < BORDER_WIDTH_MIN || n > BORDER_WIDTH_MAX) {
|
|
15617
|
+
throw new Error(
|
|
15618
|
+
`E_VALIDATION: ${field}: border width must be a whole number of eighths of a point from ${BORDER_WIDTH_MIN} to ${BORDER_WIDTH_MAX} (got ${JSON.stringify(input)}). Remedy: pass sz:4 for a 0.5 pt line, sz:8 for 1 pt, sz:96 for 12 pt (the widest).`
|
|
15619
|
+
);
|
|
15620
|
+
}
|
|
15621
|
+
return n;
|
|
15622
|
+
}
|
|
15623
|
+
function colourPattern(options = {}) {
|
|
15624
|
+
const ci = (word) => [...word].map((c) => `[${c.toLowerCase()}${c.toUpperCase()}]`).join("");
|
|
15625
|
+
const words = [...Object.keys(COLOUR_NAMES), ...options.allowAuto ? ["auto"] : []].map(ci);
|
|
15626
|
+
return `^\\s*(#?[0-9A-Fa-f]{6}|${words.join("|")})\\s*$`;
|
|
15627
|
+
}
|
|
15628
|
+
function colourDescription(options = {}) {
|
|
15629
|
+
return `A colour: ${colourForms(options)}.`;
|
|
15630
|
+
}
|
|
15631
|
+
var HIGHLIGHT_COLOURS = [
|
|
15632
|
+
"yellow",
|
|
15633
|
+
"green",
|
|
15634
|
+
"cyan",
|
|
15635
|
+
"magenta",
|
|
15636
|
+
"blue",
|
|
15637
|
+
"red",
|
|
15638
|
+
"darkBlue",
|
|
15639
|
+
"darkCyan",
|
|
15640
|
+
"darkGreen",
|
|
15641
|
+
"darkMagenta",
|
|
15642
|
+
"darkRed",
|
|
15643
|
+
"darkYellow",
|
|
15644
|
+
"darkGray",
|
|
15645
|
+
"lightGray",
|
|
15646
|
+
"black",
|
|
15647
|
+
"white",
|
|
15648
|
+
"none"
|
|
15649
|
+
];
|
|
15650
|
+
|
|
15651
|
+
// ../engine/src/operations/table.ts
|
|
15538
15652
|
function isTableReproducible() {
|
|
15539
15653
|
try {
|
|
15540
15654
|
return resolveReproducibleMode(void 0);
|
|
@@ -15934,13 +16048,13 @@ async function executeTable(opIn, ast, guide) {
|
|
|
15934
16048
|
afterIndex: Number(pick("afterIndex", op.afterIndex) ?? rows.length - 1),
|
|
15935
16049
|
cells: cellsArray,
|
|
15936
16050
|
index: Number(pick("index", op.index) ?? 0),
|
|
15937
|
-
fill: String(pick("fill", op.fill) ?? "F7F9FC").replace(/^#/, ""),
|
|
15938
|
-
headerFill: pick("headerFill", op.headerFill)?.replace(/^#/, ""),
|
|
16051
|
+
fill: String(resolveColourInput(pick("fill", op.fill) ?? "F7F9FC")).replace(/^#/, ""),
|
|
16052
|
+
headerFill: resolveColourInput(pick("headerFill", op.headerFill))?.replace(/^#/, ""),
|
|
15939
16053
|
zebra: pick("zebra", op.zebra),
|
|
15940
16054
|
// Explicit header/single-cell text color for setShading/setCellShading
|
|
15941
16055
|
// (flat or nested in params; wins over the guide per the precedence
|
|
15942
16056
|
// invariant). Validated below before any mutation.
|
|
15943
|
-
textColor: pick("textColor", op.textColor)
|
|
16057
|
+
textColor: resolveColourInput(pick("textColor", op.textColor))
|
|
15944
16058
|
};
|
|
15945
16059
|
const warnings = [];
|
|
15946
16060
|
const revisionIds = [];
|
|
@@ -15954,7 +16068,7 @@ async function executeTable(opIn, ast, guide) {
|
|
|
15954
16068
|
const normText = String(P.textColor).replace(/^#/, "");
|
|
15955
16069
|
if (!/^[0-9A-Fa-f]{6}$/.test(normText)) {
|
|
15956
16070
|
throw new Error(
|
|
15957
|
-
`E_VALIDATION: ${resolvedOp} requires textColor as
|
|
16071
|
+
`E_VALIDATION: ${resolvedOp} requires textColor as ${colourForms()} (got textColor='${String(P.textColor)}'). Remedy: pass textColor as 6 hex digits, e.g. textColor:'FFFFFF', or a colour name such as 'white'.`
|
|
15958
16072
|
);
|
|
15959
16073
|
}
|
|
15960
16074
|
explicitTextColor = normText.toUpperCase();
|
|
@@ -16536,16 +16650,8 @@ async function executeTable(opIn, ast, guide) {
|
|
|
16536
16650
|
}
|
|
16537
16651
|
case "setBorder": {
|
|
16538
16652
|
const borderType = String(pick("border", void 0) || "all");
|
|
16539
|
-
const color =
|
|
16540
|
-
const
|
|
16541
|
-
const szNum = Number(rawSz);
|
|
16542
|
-
if (!Number.isInteger(szNum) || szNum < 1) {
|
|
16543
|
-
throw toError(
|
|
16544
|
-
"E_VALIDATION",
|
|
16545
|
-
`setBorder sz must be a whole number of eighths of a point (got ${String(rawSz)}).`,
|
|
16546
|
-
"Pass sz:4 for a 0.5 pt line or sz:8 for 1 pt."
|
|
16547
|
-
);
|
|
16548
|
-
}
|
|
16653
|
+
const color = requireColour("color", pick("color", void 0) || "D1D5DB", { allowAuto: true });
|
|
16654
|
+
const szNum = requireBorderSize("setBorder sz", pick("sz", void 0) ?? raw.size, 4);
|
|
16549
16655
|
const sz = String(szNum);
|
|
16550
16656
|
const val = String(raw.val || "single");
|
|
16551
16657
|
let tblPr = directChildren5(tblEl, "w:tblPr")[0];
|
|
@@ -16803,7 +16909,7 @@ async function executeTable(opIn, ast, guide) {
|
|
|
16803
16909
|
);
|
|
16804
16910
|
}
|
|
16805
16911
|
const tc = resolveTableCell(tr, colIndex, "setCellShading");
|
|
16806
|
-
const fillInput = raw.fill ?? op.fill;
|
|
16912
|
+
const fillInput = resolveColourInput(raw.fill ?? op.fill);
|
|
16807
16913
|
if (fillInput === void 0) {
|
|
16808
16914
|
throw new Error(
|
|
16809
16915
|
`E_VALIDATION: setCellShading requires fill (got no fill). Remedy: pass fill as 6 hex digits without '#', e.g. fill:'FF0000'.`
|
|
@@ -16812,7 +16918,7 @@ async function executeTable(opIn, ast, guide) {
|
|
|
16812
16918
|
const fill = String(fillInput).replace(/^#/, "");
|
|
16813
16919
|
if (!/^[0-9A-Fa-f]{6}$/.test(fill)) {
|
|
16814
16920
|
throw new Error(
|
|
16815
|
-
`E_VALIDATION: setCellShading requires a
|
|
16921
|
+
`E_VALIDATION: setCellShading requires a fill that is ${colourForms()} (got fill='${String(fillInput)}'). Remedy: pass fill as 6 hex digits, e.g. fill:'FF0000' or fill:'#FF0000', or a colour name such as 'red'.`
|
|
16816
16922
|
);
|
|
16817
16923
|
}
|
|
16818
16924
|
const normFill = fill.toUpperCase();
|
|
@@ -17121,19 +17227,20 @@ async function executeTable(opIn, ast, guide) {
|
|
|
17121
17227
|
break;
|
|
17122
17228
|
}
|
|
17123
17229
|
case "setCellTextColor": {
|
|
17124
|
-
const colorInput = raw.color ?? op.color ?? raw.textColor ?? op.textColor;
|
|
17230
|
+
const colorInput = resolveColourInput(raw.color ?? op.color ?? raw.textColor ?? op.textColor, { allowAuto: true });
|
|
17125
17231
|
if (colorInput === void 0 || colorInput === null || String(colorInput).trim() === "") {
|
|
17126
17232
|
throw new Error(
|
|
17127
17233
|
`E_VALIDATION: setCellTextColor requires color (got no color). Remedy: pass color as 6 hex digits without '#', e.g. color:'FFFFFF' for white header text.`
|
|
17128
17234
|
);
|
|
17129
17235
|
}
|
|
17130
17236
|
const color = String(colorInput).replace(/^#/, "");
|
|
17131
|
-
|
|
17237
|
+
const autoColor = color === "auto";
|
|
17238
|
+
if (!autoColor && !/^[0-9A-Fa-f]{6}$/.test(color)) {
|
|
17132
17239
|
throw new Error(
|
|
17133
|
-
`E_VALIDATION: setCellTextColor requires
|
|
17240
|
+
`E_VALIDATION: setCellTextColor requires ${colourForms({ allowAuto: true })} (got color='${String(colorInput)}'). Remedy: pass color as 6 hex digits, e.g. color:'FFFFFF' or color:'#FFFFFF', or a colour name such as 'white'.`
|
|
17134
17241
|
);
|
|
17135
17242
|
}
|
|
17136
|
-
const normColor = color.toUpperCase();
|
|
17243
|
+
const normColor = autoColor ? "auto" : color.toUpperCase();
|
|
17137
17244
|
const cellFillOf2 = (tc) => {
|
|
17138
17245
|
const tcPr = directChildren5(tc, "w:tcPr")[0];
|
|
17139
17246
|
const shd = tcPr ? directChildren5(tcPr, "w:shd")[0] : void 0;
|
|
@@ -17172,7 +17279,7 @@ async function executeTable(opIn, ast, guide) {
|
|
|
17172
17279
|
}
|
|
17173
17280
|
}
|
|
17174
17281
|
}
|
|
17175
|
-
warnIfLowContrast(warnings, warnedContrastPairs, cellFillOf2(tc), normColor, `cell (${rIdx + 1},${cIdx + 1}) of table '${tableNode.id}'`);
|
|
17282
|
+
if (!autoColor) warnIfLowContrast(warnings, warnedContrastPairs, cellFillOf2(tc), normColor, `cell (${rIdx + 1},${cIdx + 1}) of table '${tableNode.id}'`);
|
|
17176
17283
|
};
|
|
17177
17284
|
const tRowSrc = rowIndexAlias(raw, op);
|
|
17178
17285
|
const tColSrc = colIndexAlias(raw, op);
|
|
@@ -18355,6 +18462,8 @@ function generateEmbeddedWorkbook(seriesList, categories, scatterSeriesXValues)
|
|
|
18355
18462
|
var DEFAULT_SERIES_PALETTE = ["0066CC", "10B981", "F59E0B", "EF4444", "8B5CF6", "06B6D4", "EC4899", "84CC16"];
|
|
18356
18463
|
function defaultSeriesColor(color, seriesIdx) {
|
|
18357
18464
|
if (color) {
|
|
18465
|
+
const named = normalizeColour(color);
|
|
18466
|
+
if (named !== void 0) return named;
|
|
18358
18467
|
const raw = color.replace(/^#/, "");
|
|
18359
18468
|
if (isShorthandHex(raw)) return expandShorthandHex(raw);
|
|
18360
18469
|
return raw;
|
|
@@ -19585,7 +19694,7 @@ function sameStrings(a, b) {
|
|
|
19585
19694
|
}
|
|
19586
19695
|
function normalizeHex(value) {
|
|
19587
19696
|
if (typeof value !== "string") return void 0;
|
|
19588
|
-
const raw = value.replace(/^#/, "").trim();
|
|
19697
|
+
const raw = (normalizeColour(value) ?? value.replace(/^#/, "")).trim();
|
|
19589
19698
|
return raw.length > 0 ? raw.toUpperCase() : void 0;
|
|
19590
19699
|
}
|
|
19591
19700
|
function isNumericText(value) {
|
|
@@ -19828,7 +19937,7 @@ function patchSeriesList(xml, req, patches, scope) {
|
|
|
19828
19937
|
const haveColor = normalizeHex(existing?.color);
|
|
19829
19938
|
if (wantColor !== void 0 && wantColor !== haveColor) {
|
|
19830
19939
|
const lineSeries = combo ? (patch.chartType ?? existing?.chartType) === "line" : baseLineSeries;
|
|
19831
|
-
serXml = patchSeriesColor(serXml, patch.color.replace(/^#/, ""), lineSeries, scope);
|
|
19940
|
+
serXml = patchSeriesColor(serXml, normalizeColour(patch.color) ?? patch.color.replace(/^#/, ""), lineSeries, scope);
|
|
19832
19941
|
}
|
|
19833
19942
|
pieces.push(serXml);
|
|
19834
19943
|
cursor = spans[i].end;
|
|
@@ -20337,7 +20446,7 @@ function withGuideSeriesColors(series, palette) {
|
|
|
20337
20446
|
return series.map((s, i) => {
|
|
20338
20447
|
const raw = typeof s?.color === "string" ? s.color.replace(/^#/, "") : "";
|
|
20339
20448
|
const existing = isShorthandHex(raw) ? expandShorthandHex(raw) : raw;
|
|
20340
|
-
if (/^[0-9A-Fa-f]{6}$/.test(existing)) return s;
|
|
20449
|
+
if (/^[0-9A-Fa-f]{6}$/.test(existing) || typeof s?.color === "string" && normalizeColour(s.color) !== void 0) return s;
|
|
20341
20450
|
return { ...s, color: guidePaletteColor(palette, i, taken) };
|
|
20342
20451
|
});
|
|
20343
20452
|
}
|
|
@@ -21614,10 +21723,15 @@ var twipsOrPoints = import_zod2.z.union([
|
|
|
21614
21723
|
import_zod2.z.number().finite(),
|
|
21615
21724
|
import_zod2.z.string().regex(FONT_SIZE_PT_REGEX, 'must be a number of twips (20 per point) or a string such as "12pt"')
|
|
21616
21725
|
]).describe('Twips (20 per point, so 240 is 12 pt), or a string in points such as "12pt".');
|
|
21726
|
+
var MAX_FONT_HALF_POINTS = 3276;
|
|
21617
21727
|
var fontSizeOrSzSchema = import_zod2.z.union([
|
|
21618
|
-
import_zod2.z.number().finite().nonnegative(),
|
|
21619
|
-
import_zod2.z.string().regex(FONT_SIZE_PT_REGEX, 'Font size string must match format like "12pt" or "10.5pt"')
|
|
21620
|
-
]);
|
|
21728
|
+
import_zod2.z.number().finite().nonnegative().max(MAX_FONT_HALF_POINTS, "font size must be at most 1638 pt (3276 half-points)"),
|
|
21729
|
+
import_zod2.z.string().regex(FONT_SIZE_PT_REGEX, 'Font size string must match format like "12pt" or "10.5pt"').refine((v) => (parseFontSize(v) ?? 0) <= MAX_FONT_HALF_POINTS, "font size must be at most 1638 pt (3276 half-points)")
|
|
21730
|
+
]).describe('Font size: a number of half-points (24 is 12 pt) or a string in points such as "12pt"; at most 1638 pt.');
|
|
21731
|
+
var borderSizeSchema = import_zod2.z.number("border width is a whole number of eighths of a point, e.g. 8 for 1 pt").int("border width is a whole number of eighths of a point, e.g. 8 for 1 pt").min(BORDER_WIDTH_MIN, "border width must be at least 1 (an eighth of a point)").max(BORDER_WIDTH_MAX, "border width must be at most 96 (12 pt)").describe("Border width in eighths of a point (8 is 1 pt), a whole number from 1 to 96.");
|
|
21732
|
+
function colourField(options = {}, max = 32) {
|
|
21733
|
+
return import_zod2.z.string().max(max).regex(new RegExp(colourPattern(options)), `color must be ${colourForms(options)}`).describe(colourDescription(options));
|
|
21734
|
+
}
|
|
21621
21735
|
var styleProperties = import_zod2.z.object({
|
|
21622
21736
|
bold: import_zod2.z.boolean().optional(),
|
|
21623
21737
|
italic: import_zod2.z.boolean().optional(),
|
|
@@ -21626,11 +21740,11 @@ var styleProperties = import_zod2.z.object({
|
|
|
21626
21740
|
doubleStrikethrough: import_zod2.z.boolean().optional(),
|
|
21627
21741
|
vertAlign: import_zod2.z.enum(["superscript", "subscript", "baseline"]).optional(),
|
|
21628
21742
|
alignment: import_zod2.z.enum(["left", "center", "right", "both", "justify"]).optional(),
|
|
21629
|
-
color:
|
|
21743
|
+
color: colourField({ allowAuto: true }).optional(),
|
|
21630
21744
|
fontSize: fontSizeOrSzSchema.optional(),
|
|
21631
21745
|
sz: fontSizeOrSzSchema.optional(),
|
|
21632
21746
|
fontFamily: import_zod2.z.string().max(128).optional(),
|
|
21633
|
-
highlight: import_zod2.z.
|
|
21747
|
+
highlight: import_zod2.z.enum(HIGHLIGHT_COLOURS).optional(),
|
|
21634
21748
|
lineSpacing: import_zod2.z.number().optional(),
|
|
21635
21749
|
spacingBefore: twipsOrPoints.optional(),
|
|
21636
21750
|
spacingAfter: twipsOrPoints.optional(),
|
|
@@ -21648,7 +21762,7 @@ var styleProperties = import_zod2.z.object({
|
|
|
21648
21762
|
var insertChartSeries = import_zod2.z.object({
|
|
21649
21763
|
name: import_zod2.z.string().max(500),
|
|
21650
21764
|
values: import_zod2.z.array(import_zod2.z.number()).max(1e3),
|
|
21651
|
-
color: import_zod2.z.string().max(16).optional()
|
|
21765
|
+
color: import_zod2.z.string().max(16).describe("Series colour: 6 hex digits, optionally with #, or one of: red, green, blue, black, white, yellow, orange, gray, grey, purple. Any other value is replaced by the style guide palette colour when there is one.").optional()
|
|
21652
21766
|
}).catchall(import_zod2.z.unknown());
|
|
21653
21767
|
var insertWrapMode = import_zod2.z.enum(["inline", "square", "tight", "through", "topAndBottom", "behindText", "inFrontOfText"]);
|
|
21654
21768
|
var tableCellPadding = import_zod2.z.object({
|
|
@@ -21685,7 +21799,7 @@ var insertOp = import_zod2.z.object({
|
|
|
21685
21799
|
headers: import_zod2.z.array(import_zod2.z.string().max(1e4)).max(100).optional(),
|
|
21686
21800
|
rows: import_zod2.z.array(import_zod2.z.array(import_zod2.z.string().max(1e4)).max(100)).max(1e3).optional(),
|
|
21687
21801
|
cellPadding: import_zod2.z.union([import_zod2.z.number().int().nonnegative(), tableCellPadding]).optional(),
|
|
21688
|
-
color:
|
|
21802
|
+
color: colourField({ allowAuto: true }).optional(),
|
|
21689
21803
|
thickness: import_zod2.z.number().optional(),
|
|
21690
21804
|
lineStyle: import_zod2.z.string().max(32).optional(),
|
|
21691
21805
|
spacingBefore: twipsOrPoints.optional(),
|
|
@@ -21772,11 +21886,11 @@ var styleOp = import_zod2.z.object({
|
|
|
21772
21886
|
doubleStrikethrough: import_zod2.z.boolean().optional(),
|
|
21773
21887
|
vertAlign: import_zod2.z.enum(["superscript", "subscript", "baseline"]).optional(),
|
|
21774
21888
|
alignment: import_zod2.z.enum(["left", "center", "right", "both", "justify"]).optional(),
|
|
21775
|
-
color:
|
|
21889
|
+
color: colourField({ allowAuto: true }).optional(),
|
|
21776
21890
|
fontSize: fontSizeOrSzSchema.optional(),
|
|
21777
21891
|
sz: fontSizeOrSzSchema.optional(),
|
|
21778
21892
|
fontFamily: import_zod2.z.string().max(128).optional(),
|
|
21779
|
-
highlight: import_zod2.z.
|
|
21893
|
+
highlight: import_zod2.z.enum(HIGHLIGHT_COLOURS).optional(),
|
|
21780
21894
|
lineSpacing: import_zod2.z.number().optional(),
|
|
21781
21895
|
spacingBefore: twipsOrPoints.optional(),
|
|
21782
21896
|
spacingAfter: twipsOrPoints.optional(),
|
|
@@ -21853,20 +21967,20 @@ var tableOp = import_zod2.z.object({
|
|
|
21853
21967
|
cells: import_zod2.z.array(import_zod2.z.string().max(1e5)).max(100).describe("addRow: the text of the new cells, one entry per column from the first (also accepted inside params).").optional(),
|
|
21854
21968
|
afterIndex: import_zod2.z.number().int().describe("addRow: insert after this row number; addColumn: after this column number (counting from 1; default the last).").optional(),
|
|
21855
21969
|
index: import_zod2.z.number().int().describe("deleteRow/deleteColumn: the row or column number to remove (counting from 1).").optional(),
|
|
21856
|
-
fill:
|
|
21857
|
-
headerFill:
|
|
21970
|
+
fill: colourField({}, 32).optional(),
|
|
21971
|
+
headerFill: colourField({}, 32).optional(),
|
|
21858
21972
|
zebra: import_zod2.z.boolean().optional(),
|
|
21859
21973
|
// Explicit header/single-cell text color for setShading/setCellShading
|
|
21860
21974
|
// (flat or params-nested; wins over the guide per the precedence invariant).
|
|
21861
|
-
textColor:
|
|
21975
|
+
textColor: colourField({}, 32).optional(),
|
|
21862
21976
|
widths: import_zod2.z.array(import_zod2.z.number()).max(100).optional(),
|
|
21863
21977
|
columnWidths: import_zod2.z.array(import_zod2.z.number()).max(100).optional(),
|
|
21864
21978
|
width: import_zod2.z.number().int().positive().optional(),
|
|
21865
21979
|
height: import_zod2.z.number().int().positive().optional(),
|
|
21866
21980
|
rule: import_zod2.z.enum(["atLeast", "exact", "auto"]).optional(),
|
|
21867
21981
|
border: import_zod2.z.string().max(32).optional(),
|
|
21868
|
-
color:
|
|
21869
|
-
sz:
|
|
21982
|
+
color: colourField({ allowAuto: true }).optional(),
|
|
21983
|
+
sz: borderSizeSchema.optional(),
|
|
21870
21984
|
vAlign: import_zod2.z.string().max(16).optional(),
|
|
21871
21985
|
align: tableAlignmentSchema.optional(),
|
|
21872
21986
|
alignment: tableAlignmentSchema.optional(),
|
|
@@ -21986,7 +22100,7 @@ function chartHoleSizeSchemaRemedy(detail) {
|
|
|
21986
22100
|
var chartSeries = import_zod2.z.object({
|
|
21987
22101
|
name: import_zod2.z.string().max(500),
|
|
21988
22102
|
values: import_zod2.z.array(import_zod2.z.number()).max(1e3),
|
|
21989
|
-
color: import_zod2.z.string().max(16).optional(),
|
|
22103
|
+
color: import_zod2.z.string().max(16).describe("Series colour: 6 hex digits, optionally with #, or one of: red, green, blue, black, white, yellow, orange, gray, grey, purple. Any other value is replaced by the style guide palette colour when there is one.").optional(),
|
|
21990
22104
|
chartType: import_zod2.z.enum(["column", "line"]).optional(),
|
|
21991
22105
|
axis: import_zod2.z.enum(["primary", "secondary"]).optional()
|
|
21992
22106
|
}).catchall(import_zod2.z.unknown());
|
|
@@ -22112,7 +22226,7 @@ var watermarkSizeSchema = import_zod2.z.union([import_zod2.z.number().finite().n
|
|
|
22112
22226
|
var watermarkOp = import_zod2.z.object({
|
|
22113
22227
|
action: import_zod2.z.literal("watermark"),
|
|
22114
22228
|
text: import_zod2.z.string().max(500).optional(),
|
|
22115
|
-
color: import_zod2.z.string().max(16).optional(),
|
|
22229
|
+
color: import_zod2.z.string().max(16).describe("Watermark colour: 6 hex digits, optionally with #, or one of: red, green, blue, black, white, yellow, orange, gray, grey, purple. Any other value falls back to D1D5DB.").optional(),
|
|
22116
22230
|
fontSize: watermarkSizeSchema.optional(),
|
|
22117
22231
|
sz: watermarkSizeSchema.optional(),
|
|
22118
22232
|
opacity: import_zod2.z.number().min(0).max(1).optional(),
|
|
@@ -22122,9 +22236,9 @@ var watermarkOp = import_zod2.z.object({
|
|
|
22122
22236
|
var pageBorderOp = import_zod2.z.object({
|
|
22123
22237
|
action: import_zod2.z.literal("pageBorder"),
|
|
22124
22238
|
style: import_zod2.z.string().max(32).optional(),
|
|
22125
|
-
sz:
|
|
22239
|
+
sz: borderSizeSchema.optional(),
|
|
22126
22240
|
fontSize: fontSizeOrSzSchema.optional(),
|
|
22127
|
-
color:
|
|
22241
|
+
color: colourField({ allowAuto: true }).optional(),
|
|
22128
22242
|
marginOffset: import_zod2.z.enum(["page", "text"]).optional(),
|
|
22129
22243
|
sectionIndex: sectionNumber.optional(),
|
|
22130
22244
|
expectedVersion
|
|
@@ -22462,6 +22576,32 @@ var KEY_WORDS = {
|
|
|
22462
22576
|
};
|
|
22463
22577
|
var MAX_KEYS_LISTED = 60;
|
|
22464
22578
|
var cachedShapes;
|
|
22579
|
+
function subOperationValues(shape) {
|
|
22580
|
+
for (const key of ["operation", "op"]) {
|
|
22581
|
+
let field = shape[key];
|
|
22582
|
+
for (let depth = 0; depth < 4 && field?.def; depth++) {
|
|
22583
|
+
const values = field.def.entries;
|
|
22584
|
+
if (values && typeof values === "object") return Object.keys(values);
|
|
22585
|
+
field = field.def["innerType"] ?? field.def["in"] ?? field.def["out"];
|
|
22586
|
+
}
|
|
22587
|
+
}
|
|
22588
|
+
return [];
|
|
22589
|
+
}
|
|
22590
|
+
var ACTION_VERBS = ["set", "add", "insert", "update", "edit", "create", "apply", "remove", "delete", "change", "make"];
|
|
22591
|
+
function verbPrefixCandidates(given, actions) {
|
|
22592
|
+
const key = squash(given);
|
|
22593
|
+
for (const verb of ACTION_VERBS) {
|
|
22594
|
+
if (!key.startsWith(verb) || key.length - verb.length < 3) continue;
|
|
22595
|
+
const rest = key.slice(verb.length);
|
|
22596
|
+
const target = actions.find((a) => squash(a) === rest);
|
|
22597
|
+
if (!target) continue;
|
|
22598
|
+
const out = [target];
|
|
22599
|
+
const verbAction = actions.find((a) => squash(a) === verb) ?? ACTION_WORDS[verb];
|
|
22600
|
+
if (verbAction && actions.includes(verbAction) && verbAction !== target) out.push(verbAction);
|
|
22601
|
+
return out;
|
|
22602
|
+
}
|
|
22603
|
+
return [];
|
|
22604
|
+
}
|
|
22465
22605
|
function actionShapes() {
|
|
22466
22606
|
if (cachedShapes) return cachedShapes;
|
|
22467
22607
|
const shapes = [];
|
|
@@ -22469,7 +22609,10 @@ function actionShapes() {
|
|
|
22469
22609
|
for (const option of options) {
|
|
22470
22610
|
const literal = option.shape["action"]?.def?.values?.[0];
|
|
22471
22611
|
if (typeof literal !== "string") continue;
|
|
22472
|
-
|
|
22612
|
+
const shape = { action: literal, keys: Object.keys(option.shape).filter((k) => k !== "action") };
|
|
22613
|
+
const sub = subOperationValues(option.shape);
|
|
22614
|
+
if (sub.length > 0) shape.subOperations = sub;
|
|
22615
|
+
shapes.push(shape);
|
|
22473
22616
|
}
|
|
22474
22617
|
cachedShapes = shapes;
|
|
22475
22618
|
return shapes;
|
|
@@ -22514,9 +22657,17 @@ function hintFor(operations, issue) {
|
|
|
22514
22657
|
const given = op["action"];
|
|
22515
22658
|
if (typeof given !== "string") return void 0;
|
|
22516
22659
|
const actions = shapes.map((s) => s.action);
|
|
22517
|
-
|
|
22660
|
+
let near = closestNames(given, actions, ACTION_WORDS);
|
|
22661
|
+
if (near.length === 0) near = verbPrefixCandidates(given, actions);
|
|
22518
22662
|
const parts = [`${label}: "${given}" is not an action.`];
|
|
22519
|
-
if (near.length > 0)
|
|
22663
|
+
if (near.length > 0) {
|
|
22664
|
+
parts.push(`Did you mean ${near.map((n) => `"${n}"`).join(" or ")}?`);
|
|
22665
|
+
const candidate = shapes.find((s) => s.action === near[0]);
|
|
22666
|
+
if (candidate) {
|
|
22667
|
+
parts.push(`Keys of "${candidate.action}": ${candidate.keys.slice(0, MAX_KEYS_LISTED).join(", ")}${candidate.keys.length > MAX_KEYS_LISTED ? ", ..." : ""}.`);
|
|
22668
|
+
if (candidate.subOperations) parts.push(`Operations of "${candidate.action}": ${candidate.subOperations.join(", ")}.`);
|
|
22669
|
+
}
|
|
22670
|
+
}
|
|
22520
22671
|
parts.push(`Actions: ${actions.join(", ")}.`);
|
|
22521
22672
|
return parts.join(" ");
|
|
22522
22673
|
}
|
|
@@ -23342,16 +23493,14 @@ function applyRunProps(doc, run, props) {
|
|
|
23342
23493
|
vaEl.setAttribute("w:val", props.vertAlign);
|
|
23343
23494
|
}
|
|
23344
23495
|
}
|
|
23345
|
-
if (props.color !== void 0 && props.color !== null) {
|
|
23496
|
+
if (props.color !== void 0 && props.color !== null && String(props.color).trim() !== "") {
|
|
23497
|
+
const hex = requireColour("color", props.color, { allowAuto: true });
|
|
23346
23498
|
let colorEl = rPr.getElementsByTagName("w:color")[0];
|
|
23347
23499
|
if (!colorEl) {
|
|
23348
23500
|
colorEl = createWordElement(doc, "w:color");
|
|
23349
23501
|
rPr.appendChild(colorEl);
|
|
23350
23502
|
}
|
|
23351
|
-
|
|
23352
|
-
if (hex) {
|
|
23353
|
-
colorEl.setAttribute("w:val", hex);
|
|
23354
|
-
}
|
|
23503
|
+
colorEl.setAttribute("w:val", hex);
|
|
23355
23504
|
}
|
|
23356
23505
|
const rawSz = props.sz !== void 0 ? props.sz : props.fontSize;
|
|
23357
23506
|
if (rawSz !== void 0 && rawSz !== null) {
|
|
@@ -23867,9 +24016,9 @@ function executeStyle(op, ast) {
|
|
|
23867
24016
|
pBdr.appendChild(el);
|
|
23868
24017
|
}
|
|
23869
24018
|
el.setAttribute("w:val", config.style || "single");
|
|
23870
|
-
el.setAttribute("w:sz", String(config.sz
|
|
24019
|
+
el.setAttribute("w:sz", String(requireBorderSize(`${side} border sz`, config.sz, 12)));
|
|
23871
24020
|
el.setAttribute("w:space", "1");
|
|
23872
|
-
el.setAttribute("w:color",
|
|
24021
|
+
el.setAttribute("w:color", config.color ? requireColour("color", config.color, { allowAuto: true }) : "CBD5E1");
|
|
23873
24022
|
};
|
|
23874
24023
|
if (props.borderBox) {
|
|
23875
24024
|
applyBdr("top", props.borderBox);
|
|
@@ -26868,7 +27017,7 @@ async function executeInsert(op, ast, parser2, guide, paraIdCache) {
|
|
|
26868
27017
|
const pBdr = createWordElement(doc, "w:pBdr");
|
|
26869
27018
|
const bottom = createWordElement(doc, "w:bottom");
|
|
26870
27019
|
const lineStyle = op.lineStyle || "single";
|
|
26871
|
-
const color =
|
|
27020
|
+
const color = op.color ? requireColour("color", op.color, { allowAuto: true }) : "CBD5E1";
|
|
26872
27021
|
const thickness = op.thickness !== void 0 ? String(op.thickness) : "12";
|
|
26873
27022
|
bottom.setAttribute("w:val", lineStyle);
|
|
26874
27023
|
bottom.setAttribute("w:sz", thickness);
|
|
@@ -31616,7 +31765,7 @@ function executeEndnote(op, ast, parser2) {
|
|
|
31616
31765
|
function executeWatermark(op, ast, parser2) {
|
|
31617
31766
|
const rawText = (op.text || "CONFIDENTIAL").trim();
|
|
31618
31767
|
const text2 = rawText.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
31619
|
-
const color =
|
|
31768
|
+
const color = normalizeColour(op.color) ?? "D1D5DB";
|
|
31620
31769
|
const fontSize = op.fontSize || 60;
|
|
31621
31770
|
const rotation = op.rotation !== void 0 ? op.rotation : 315;
|
|
31622
31771
|
const watermarkXml = `<w:p xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
|
@@ -31734,8 +31883,8 @@ function executePageBorder(op, ast, parser2) {
|
|
|
31734
31883
|
const pgBorders = upsertSectPrChild(sectPr, "w:pgBorders");
|
|
31735
31884
|
pgBorders.setAttribute("w:offsetFrom", op.marginOffset || "page");
|
|
31736
31885
|
const style = op.style || "single";
|
|
31737
|
-
const sz = String(op.sz
|
|
31738
|
-
const color =
|
|
31886
|
+
const sz = String(requireBorderSize("pageBorder sz", op.sz, 12));
|
|
31887
|
+
const color = op.color ? requireColour("color", op.color, { allowAuto: true }) : "CBD5E1";
|
|
31739
31888
|
["top", "left", "bottom", "right"].forEach((side) => {
|
|
31740
31889
|
let sideEl = pgBorders.getElementsByTagName(`w:${side}`)[0];
|
|
31741
31890
|
if (!sideEl) {
|
|
@@ -32257,6 +32406,7 @@ async function executeMetadata(op, ast, parser2) {
|
|
|
32257
32406
|
}
|
|
32258
32407
|
|
|
32259
32408
|
// ../engine/src/operation-engine.ts
|
|
32409
|
+
var COMPOSE_ID_IN_MESSAGE = /\b(md\d*_\d{4,})\b/;
|
|
32260
32410
|
var ENGINE_MAX_OPS = 500;
|
|
32261
32411
|
var ENGINE_MAX_BYTES = 200 * 1024 * 1024;
|
|
32262
32412
|
var EngineQuotaError = class extends Error {
|
|
@@ -33056,6 +33206,10 @@ var OperationEngine = class _OperationEngine {
|
|
|
33056
33206
|
}
|
|
33057
33207
|
}
|
|
33058
33208
|
generateHint(op, errorMessage) {
|
|
33209
|
+
const missing = errorMessage.includes("not found") && !errorMessage.includes("not found in node") ? COMPOSE_ID_IN_MESSAGE.exec(errorMessage) : null;
|
|
33210
|
+
if (missing) {
|
|
33211
|
+
return `"${missing[1]}" looks like an id from compose_document. It may be an id of a session that was closed and reopened, a mistyped id, or a node that was deleted. If the document was reopened, use the paragraph id from persistentIds in the compose response, find the text with search_content, or re-read ids with inspect_document. Table ids are renumbered by position (t_001...) after reopen.`;
|
|
33212
|
+
}
|
|
33059
33213
|
if (errorMessage.includes("not found") && !errorMessage.includes("not found in node")) {
|
|
33060
33214
|
return `Check if the target node ID is correct. Node ids are session-scoped and go stale after reopen/edit; re-read ids via inspect_document; locate current text with search_content, or append without an id via anchor:end.`;
|
|
33061
33215
|
}
|
|
@@ -34679,12 +34833,12 @@ $$` : `[Equation]
|
|
|
34679
34833
|
// ../engine/src/toon.ts
|
|
34680
34834
|
var TOON_DEFAULT_FIDELITY = "standard";
|
|
34681
34835
|
function isToonFidelity(value) {
|
|
34682
|
-
return value === "compact" || value === "standard" || value === "full";
|
|
34836
|
+
return value === "outline" || value === "compact" || value === "standard" || value === "full";
|
|
34683
34837
|
}
|
|
34684
34838
|
function assertToonFidelity(value) {
|
|
34685
34839
|
if (!isToonFidelity(value)) {
|
|
34686
34840
|
throw new Error(
|
|
34687
|
-
`Unknown TOON fidelity '${String(value)}'. Supported: compact|standard|full. Remedy: call toon({ fidelity: 'standard' }) for the default enriched outline, 'compact' for the minimal outline, or 'full' with a sectionId for rPr/pPr detail.`
|
|
34841
|
+
`Unknown TOON fidelity '${String(value)}'. Supported: outline|compact|standard|full. Remedy: call toon({ fidelity: 'standard' }) for the default enriched outline, 'compact' for the minimal table, 'outline' for headings only, or 'full' with a sectionId for rPr/pPr detail.`
|
|
34688
34842
|
);
|
|
34689
34843
|
}
|
|
34690
34844
|
}
|
|
@@ -35196,6 +35350,15 @@ function resolveSectionScope(sections, sectionId) {
|
|
|
35196
35350
|
if (sectionId.length >= MIN_NODE_ID_PREFIX_LENGTH) return pick((id) => id.startsWith(sectionId));
|
|
35197
35351
|
return [];
|
|
35198
35352
|
}
|
|
35353
|
+
var OUTLINE_HEADERS = ["ID", "L", "Style", "Text", "Section"];
|
|
35354
|
+
var OUTLINE_DEFAULT_TEXT_LENGTH = 80;
|
|
35355
|
+
function escapeOutlineCell(value) {
|
|
35356
|
+
return value.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
35357
|
+
}
|
|
35358
|
+
function escapeOutlinePathPart(value) {
|
|
35359
|
+
return escapeOutlineCell(value).replace(/>/g, "\\>");
|
|
35360
|
+
}
|
|
35361
|
+
var OUTLINE_PATH_PART_LENGTH = 60;
|
|
35199
35362
|
var ToonGenerator = class _ToonGenerator {
|
|
35200
35363
|
cache = /* @__PURE__ */ new Map();
|
|
35201
35364
|
checksumCache = /* @__PURE__ */ new Map();
|
|
@@ -35313,7 +35476,10 @@ var ToonGenerator = class _ToonGenerator {
|
|
|
35313
35476
|
}
|
|
35314
35477
|
const sectionPaths = this.buildSectionPaths(ast.sections);
|
|
35315
35478
|
const maxLen = options?.maxTextLength;
|
|
35316
|
-
|
|
35479
|
+
if (fidelity === "outline") {
|
|
35480
|
+
rows.push(...this.buildOutlineRows(ast, sectionsToProcess, maxLen ?? OUTLINE_DEFAULT_TEXT_LENGTH));
|
|
35481
|
+
}
|
|
35482
|
+
for (const section of fidelity === "outline" ? [] : sectionsToProcess) {
|
|
35317
35483
|
const sectionPath = sectionPaths.get(section.id) || "Root";
|
|
35318
35484
|
const sectionHeader = section.heading?.text || "";
|
|
35319
35485
|
for (const node of section.nodes) {
|
|
@@ -35353,7 +35519,7 @@ var ToonGenerator = class _ToonGenerator {
|
|
|
35353
35519
|
rows.push(row);
|
|
35354
35520
|
}
|
|
35355
35521
|
}
|
|
35356
|
-
const headers = fidelity === "full" ? ["ID", "V", "Path", "Header", "Style", "Text", "Flags", "Detail"] : ["ID", "V", "Path", "Header", "Style", "Text", "Flags"];
|
|
35522
|
+
const headers = fidelity === "outline" ? [...OUTLINE_HEADERS] : fidelity === "full" ? ["ID", "V", "Path", "Header", "Style", "Text", "Flags", "Detail"] : ["ID", "V", "Path", "Header", "Style", "Text", "Flags"];
|
|
35357
35523
|
const table = {
|
|
35358
35524
|
headers,
|
|
35359
35525
|
rows,
|
|
@@ -35412,6 +35578,20 @@ var ToonGenerator = class _ToonGenerator {
|
|
|
35412
35578
|
if (fidelity !== "compact") {
|
|
35413
35579
|
combined += `${fingerprintFmt(n)};`;
|
|
35414
35580
|
}
|
|
35581
|
+
if (fidelity === "outline") {
|
|
35582
|
+
combined += `os${n.type === "paragraph" ? ownParagraphStyleId(n.xml) ?? "-" : "-"};`;
|
|
35583
|
+
}
|
|
35584
|
+
}
|
|
35585
|
+
}
|
|
35586
|
+
if (fidelity === "outline") {
|
|
35587
|
+
combined += "oh:";
|
|
35588
|
+
for (const sec of ast.sections) {
|
|
35589
|
+
for (const n of sec.nodes) {
|
|
35590
|
+
const h = headingOfNode(n);
|
|
35591
|
+
if (!h) continue;
|
|
35592
|
+
const t = (n.metadata.text ?? "").trim();
|
|
35593
|
+
combined += `${n.id}:${h.level}:${h.style}:${t.length}:${t.slice(0, OUTLINE_PATH_PART_LENGTH + 4)};`;
|
|
35594
|
+
}
|
|
35415
35595
|
}
|
|
35416
35596
|
}
|
|
35417
35597
|
try {
|
|
@@ -35443,6 +35623,42 @@ var ToonGenerator = class _ToonGenerator {
|
|
|
35443
35623
|
}
|
|
35444
35624
|
return combined;
|
|
35445
35625
|
}
|
|
35626
|
+
/**
|
|
35627
|
+
* The `outline` tier: one row per heading, in document order. A heading is a top-level body paragraph whose own
|
|
35628
|
+
* `w:pStyle` is a heading style (`headingLevelOfStyleId`), the rule the structural summary uses. Section is the
|
|
35629
|
+
* chain of headings above it, nested by level, and is built over the whole document so a `sectionId` scope still
|
|
35630
|
+
* shows the real parents. `scope` limits which headings are listed.
|
|
35631
|
+
*/
|
|
35632
|
+
buildOutlineRows(ast, scope, maxLen) {
|
|
35633
|
+
const inScope = /* @__PURE__ */ new Set();
|
|
35634
|
+
for (const sec of scope) for (const n of sec.nodes) inScope.add(n.id);
|
|
35635
|
+
const cut = (text2, max) => max > 0 && text2.length > max ? `${text2.substring(0, max)}...` : text2;
|
|
35636
|
+
const stack = [];
|
|
35637
|
+
const rows = [];
|
|
35638
|
+
for (const sec of ast.sections) {
|
|
35639
|
+
for (const node of sec.nodes) {
|
|
35640
|
+
const heading = headingOfNode(node);
|
|
35641
|
+
if (!heading) continue;
|
|
35642
|
+
const { style, level } = heading;
|
|
35643
|
+
while (stack.length > 0 && stack[stack.length - 1].level >= level) stack.pop();
|
|
35644
|
+
const text2 = node.metadata.text.trim() || this.generatePlaceholderText(node);
|
|
35645
|
+
if (inScope.has(node.id)) {
|
|
35646
|
+
rows.push({
|
|
35647
|
+
id: node.id,
|
|
35648
|
+
version: node.version,
|
|
35649
|
+
path: stack.map((h) => escapeOutlinePathPart(cut(h.text, OUTLINE_PATH_PART_LENGTH))).join(" > "),
|
|
35650
|
+
header: "",
|
|
35651
|
+
style,
|
|
35652
|
+
text: cut(text2, maxLen),
|
|
35653
|
+
flags: "",
|
|
35654
|
+
level
|
|
35655
|
+
});
|
|
35656
|
+
}
|
|
35657
|
+
stack.push({ level, text: text2 });
|
|
35658
|
+
}
|
|
35659
|
+
}
|
|
35660
|
+
return rows;
|
|
35661
|
+
}
|
|
35446
35662
|
buildSectionPaths(sections) {
|
|
35447
35663
|
const paths = /* @__PURE__ */ new Map();
|
|
35448
35664
|
const hierarchy = [];
|
|
@@ -35579,7 +35795,58 @@ var ToonGenerator = class _ToonGenerator {
|
|
|
35579
35795
|
if (node.metadata.listLevel !== void 0) flags.push(`[L${node.metadata.listLevel + 1}]`);
|
|
35580
35796
|
return flags.join(" ");
|
|
35581
35797
|
}
|
|
35798
|
+
/**
|
|
35799
|
+
* One rendered row, wrapped to `rawWidth` when it is a usable width. Text is the cell at `textPos`; only it may
|
|
35800
|
+
* wrap: the prefix (all columns before Text, trailing separator included) and the suffix (the columns after it,
|
|
35801
|
+
* leading separator included) stay whole. The suffix rides on the LAST line so folding continuations back recovers
|
|
35802
|
+
* the default line byte-identically; a row that fits emits exactly the default line.
|
|
35803
|
+
*/
|
|
35804
|
+
renderRowLine(rowParts, textPos, sep5, textVal, rawWidth) {
|
|
35805
|
+
const width = typeof rawWidth === "number" && Number.isFinite(rawWidth) && rawWidth >= 1 ? Math.floor(rawWidth) : void 0;
|
|
35806
|
+
if (width === void 0) return `${rowParts.join(sep5)}
|
|
35807
|
+
`;
|
|
35808
|
+
const prefix = `${rowParts.slice(0, textPos).join(sep5)}${sep5}`;
|
|
35809
|
+
const tail = rowParts.slice(textPos + 1);
|
|
35810
|
+
const suffix = tail.length > 0 ? `${sep5}${tail.join(sep5)}` : "";
|
|
35811
|
+
const prefixLen = displayLen(prefix);
|
|
35812
|
+
const budget = width - prefixLen - displayLen(suffix);
|
|
35813
|
+
if (budget <= 0) return `${rowParts.join(sep5)}
|
|
35814
|
+
`;
|
|
35815
|
+
const segments = wrapTextCell(textVal, budget);
|
|
35816
|
+
if (segments.length <= 1) return `${rowParts.join(sep5)}
|
|
35817
|
+
`;
|
|
35818
|
+
const indent = " ".repeat(prefixLen);
|
|
35819
|
+
let out = `${prefix}${segments[0]}
|
|
35820
|
+
`;
|
|
35821
|
+
for (let k = 1; k < segments.length - 1; k++) out += `${indent}${segments[k]}
|
|
35822
|
+
`;
|
|
35823
|
+
out += `${indent}${segments[segments.length - 1]}${suffix}
|
|
35824
|
+
`;
|
|
35825
|
+
return out;
|
|
35826
|
+
}
|
|
35827
|
+
renderOutlineText(table, options) {
|
|
35828
|
+
const compact = options?.compact ?? false;
|
|
35829
|
+
const sep5 = compact ? "|" : " | ";
|
|
35830
|
+
let output = `> fidelity: ${table.fidelity || "lossy"} tier: outline
|
|
35831
|
+
`;
|
|
35832
|
+
if (options?.includeHeaderRow !== false) {
|
|
35833
|
+
output += `${OUTLINE_HEADERS.join(sep5)}
|
|
35834
|
+
`;
|
|
35835
|
+
if (!compact) output += `${OUTLINE_HEADERS.map(() => "---").join(" | ")}
|
|
35836
|
+
`;
|
|
35837
|
+
}
|
|
35838
|
+
for (const row of table.rows) {
|
|
35839
|
+
let text2 = row.text;
|
|
35840
|
+
if (options?.maxTextLength && options.maxTextLength > 0 && text2.length > options.maxTextLength) {
|
|
35841
|
+
text2 = `${text2.substring(0, options.maxTextLength)}...`;
|
|
35842
|
+
}
|
|
35843
|
+
const cells = [row.id, String(row.level ?? ""), escapeOutlineCell(row.style), escapeOutlineCell(text2), row.path];
|
|
35844
|
+
output += this.renderRowLine(cells, 3, sep5, cells[3], options?.width);
|
|
35845
|
+
}
|
|
35846
|
+
return output;
|
|
35847
|
+
}
|
|
35582
35848
|
renderToonText(table, options) {
|
|
35849
|
+
if (table.tier === "outline") return this.renderOutlineText(table, options);
|
|
35583
35850
|
const omits = new Set(options?.omitColumns ?? []);
|
|
35584
35851
|
const compact = options?.compact ?? false;
|
|
35585
35852
|
const tier = table.tier ?? "compact";
|
|
@@ -35627,40 +35894,8 @@ var ToonGenerator = class _ToonGenerator {
|
|
|
35627
35894
|
rowParts.push(row.detail ?? "");
|
|
35628
35895
|
}
|
|
35629
35896
|
}
|
|
35630
|
-
const sep5 = compact ? "|" : " | ";
|
|
35631
|
-
const rawWidth = options?.width;
|
|
35632
|
-
const width = typeof rawWidth === "number" && Number.isFinite(rawWidth) && rawWidth >= 1 ? Math.floor(rawWidth) : void 0;
|
|
35633
|
-
if (width === void 0) {
|
|
35634
|
-
output += `${rowParts.join(sep5)}
|
|
35635
|
-
`;
|
|
35636
|
-
continue;
|
|
35637
|
-
}
|
|
35638
35897
|
const textPos = 2 + (omits.has("path") ? 0 : 1) + (omits.has("header") ? 0 : 1) + (omits.has("style") ? 0 : 1);
|
|
35639
|
-
|
|
35640
|
-
const tail = rowParts.slice(textPos + 1);
|
|
35641
|
-
const suffix = tail.length > 0 ? `${sep5}${tail.join(sep5)}` : "";
|
|
35642
|
-
const prefixLen = displayLen(prefix);
|
|
35643
|
-
const budget = width - prefixLen - displayLen(suffix);
|
|
35644
|
-
if (budget <= 0) {
|
|
35645
|
-
output += `${rowParts.join(sep5)}
|
|
35646
|
-
`;
|
|
35647
|
-
continue;
|
|
35648
|
-
}
|
|
35649
|
-
const segments = wrapTextCell(textVal, budget);
|
|
35650
|
-
if (segments.length <= 1) {
|
|
35651
|
-
output += `${rowParts.join(sep5)}
|
|
35652
|
-
`;
|
|
35653
|
-
continue;
|
|
35654
|
-
}
|
|
35655
|
-
const indent = " ".repeat(prefixLen);
|
|
35656
|
-
output += `${prefix}${segments[0]}
|
|
35657
|
-
`;
|
|
35658
|
-
for (let k = 1; k < segments.length - 1; k++) {
|
|
35659
|
-
output += `${indent}${segments[k]}
|
|
35660
|
-
`;
|
|
35661
|
-
}
|
|
35662
|
-
output += `${indent}${segments[segments.length - 1]}${suffix}
|
|
35663
|
-
`;
|
|
35898
|
+
output += this.renderRowLine(rowParts, textPos, compact ? "|" : " | ", textVal, options?.width);
|
|
35664
35899
|
}
|
|
35665
35900
|
return output;
|
|
35666
35901
|
}
|
|
@@ -38202,9 +38437,9 @@ function escapeHtml(str) {
|
|
|
38202
38437
|
|
|
38203
38438
|
// ../engine/src/export/pdf.ts
|
|
38204
38439
|
var import_child_process = require("child_process");
|
|
38205
|
-
var
|
|
38440
|
+
var fs10 = __toESM(require("fs"));
|
|
38206
38441
|
var fsp4 = __toESM(require("fs/promises"));
|
|
38207
|
-
var
|
|
38442
|
+
var path9 = __toESM(require("path"));
|
|
38208
38443
|
var import_url = require("url");
|
|
38209
38444
|
|
|
38210
38445
|
// ../engine/src/export/preflight.ts
|
|
@@ -38545,6 +38780,136 @@ function runPreflightChecks(docx) {
|
|
|
38545
38780
|
return { blockers, warnings };
|
|
38546
38781
|
}
|
|
38547
38782
|
|
|
38783
|
+
// ../engine/src/visual/backend.ts
|
|
38784
|
+
var fs9 = __toESM(require("fs"));
|
|
38785
|
+
var path8 = __toESM(require("path"));
|
|
38786
|
+
var VISUAL_DEPENDENCY_REMEDY = "PDF render backend missing. Install LibreOffice and poppler-utils (pdftoppm): Linux: sudo apt install libreoffice poppler-utils; macOS: brew install --cask libreoffice && brew install poppler; Windows: winget install TheDocumentFoundation.LibreOffice, then install Poppler for Windows (for example scoop install poppler, or choco install poppler) and make sure pdftoppm.exe is on PATH. Alternatively inspect the XML/DOM without images (telemetry-only mode).";
|
|
38787
|
+
function createDependencyError(missing) {
|
|
38788
|
+
const which = missing.length > 0 ? missing.join(", ") : "soffice, pdftoppm";
|
|
38789
|
+
return Object.assign(new Error(`E_DEPENDENCY: headless visual backend unavailable (missing: ${which})`), {
|
|
38790
|
+
code: "E_DEPENDENCY",
|
|
38791
|
+
remedy: VISUAL_DEPENDENCY_REMEDY
|
|
38792
|
+
});
|
|
38793
|
+
}
|
|
38794
|
+
function windowsExtensions(pathExt) {
|
|
38795
|
+
const requested = (pathExt ?? ".EXE;.COM").split(";").map((e) => e.trim().toLowerCase()).filter((e) => e === ".exe" || e === ".com");
|
|
38796
|
+
const ordered = [".exe", ".com"].filter((e) => requested.includes(e));
|
|
38797
|
+
return ordered.length > 0 ? ordered : [".exe", ".com"];
|
|
38798
|
+
}
|
|
38799
|
+
function isExecutableFile(candidate) {
|
|
38800
|
+
try {
|
|
38801
|
+
fs9.accessSync(candidate, fs9.constants.X_OK);
|
|
38802
|
+
return fs9.statSync(candidate).isFile();
|
|
38803
|
+
} catch {
|
|
38804
|
+
return false;
|
|
38805
|
+
}
|
|
38806
|
+
}
|
|
38807
|
+
function candidateNames(name2, platform, pathExt) {
|
|
38808
|
+
if (platform !== "win32") return [name2];
|
|
38809
|
+
const lower = name2.toLowerCase();
|
|
38810
|
+
if (lower.endsWith(".exe") || lower.endsWith(".com")) return [name2];
|
|
38811
|
+
return windowsExtensions(pathExt).map((ext) => name2 + ext);
|
|
38812
|
+
}
|
|
38813
|
+
function installFolders(name2, platform = process.platform, env = process.env) {
|
|
38814
|
+
const bare = name2.toLowerCase().replace(/\.(exe|com)$/, "");
|
|
38815
|
+
const isOffice = bare === "soffice" || bare === "libreoffice";
|
|
38816
|
+
const isPoppler = bare === "pdftoppm";
|
|
38817
|
+
const dirs = [];
|
|
38818
|
+
if (platform === "win32") {
|
|
38819
|
+
const programFiles = env.ProgramFiles ?? "C:\\Program Files";
|
|
38820
|
+
const programFilesX86 = env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)";
|
|
38821
|
+
const localAppData = env.LOCALAPPDATA;
|
|
38822
|
+
const userProfile = env.USERPROFILE;
|
|
38823
|
+
if (isOffice) {
|
|
38824
|
+
dirs.push(path8.join(programFiles, "LibreOffice", "program"));
|
|
38825
|
+
dirs.push(path8.join(programFilesX86, "LibreOffice", "program"));
|
|
38826
|
+
if (localAppData) dirs.push(path8.join(localAppData, "Programs", "LibreOffice", "program"));
|
|
38827
|
+
}
|
|
38828
|
+
if (isPoppler) {
|
|
38829
|
+
if (userProfile) dirs.push(path8.join(userProfile, "scoop", "shims"));
|
|
38830
|
+
dirs.push(path8.join(env.ProgramData ?? "C:\\ProgramData", "chocolatey", "bin"));
|
|
38831
|
+
if (localAppData) dirs.push(path8.join(localAppData, "Microsoft", "WinGet", "Links"));
|
|
38832
|
+
}
|
|
38833
|
+
} else if (platform === "darwin") {
|
|
38834
|
+
const home = env.HOME;
|
|
38835
|
+
if (isOffice) {
|
|
38836
|
+
dirs.push("/Applications/LibreOffice.app/Contents/MacOS");
|
|
38837
|
+
if (home) dirs.push(path8.join(home, "Applications", "LibreOffice.app", "Contents", "MacOS"));
|
|
38838
|
+
}
|
|
38839
|
+
dirs.push("/opt/homebrew/bin", "/usr/local/bin", "/opt/local/bin");
|
|
38840
|
+
}
|
|
38841
|
+
return dirs;
|
|
38842
|
+
}
|
|
38843
|
+
function findOnPath(name2, pathEnv, options = {}) {
|
|
38844
|
+
try {
|
|
38845
|
+
if (!name2 || name2.trim() === "") return null;
|
|
38846
|
+
const trimmed = name2.trim();
|
|
38847
|
+
const platform = options.platform ?? process.platform;
|
|
38848
|
+
const env = options.env ?? process.env;
|
|
38849
|
+
const pathExt = options.pathExt ?? (platform === "win32" ? env.PATHEXT : void 0);
|
|
38850
|
+
const names = candidateNames(trimmed, platform, pathExt);
|
|
38851
|
+
if (trimmed.includes("/") || trimmed.includes("\\")) {
|
|
38852
|
+
for (const n of names) {
|
|
38853
|
+
const abs = path8.resolve(n);
|
|
38854
|
+
if (isExecutableFile(abs)) return abs;
|
|
38855
|
+
}
|
|
38856
|
+
return null;
|
|
38857
|
+
}
|
|
38858
|
+
const pathValue = pathEnv !== void 0 ? pathEnv : env.PATH ?? env.Path ?? "";
|
|
38859
|
+
const dirs = pathValue.split(path8.delimiter).map((d) => d.trim().replace(/^"(.*)"$/, "$1")).filter((d) => d.length > 0);
|
|
38860
|
+
for (const dir of dirs) {
|
|
38861
|
+
for (const n of names) {
|
|
38862
|
+
const candidate = path8.resolve(dir, n);
|
|
38863
|
+
if (isExecutableFile(candidate)) return candidate;
|
|
38864
|
+
}
|
|
38865
|
+
}
|
|
38866
|
+
const useFallback = options.fallbackDirs ?? (pathEnv === void 0 && env.ASTRA_VISUAL_NO_FALLBACK_DIRS !== "1");
|
|
38867
|
+
if (useFallback) {
|
|
38868
|
+
for (const dir of options.installDirs ?? installFolders(trimmed, platform, env)) {
|
|
38869
|
+
for (const n of names) {
|
|
38870
|
+
const candidate = path8.resolve(dir, n);
|
|
38871
|
+
if (isExecutableFile(candidate)) return candidate;
|
|
38872
|
+
}
|
|
38873
|
+
}
|
|
38874
|
+
}
|
|
38875
|
+
return null;
|
|
38876
|
+
} catch {
|
|
38877
|
+
return null;
|
|
38878
|
+
}
|
|
38879
|
+
}
|
|
38880
|
+
function resolveSoffice(override, pathEnv) {
|
|
38881
|
+
if (override && override.trim()) {
|
|
38882
|
+
const direct = findOnPath(override.trim(), pathEnv);
|
|
38883
|
+
if (direct) return direct;
|
|
38884
|
+
return null;
|
|
38885
|
+
}
|
|
38886
|
+
return findOnPath("soffice", pathEnv) ?? findOnPath("libreoffice", pathEnv);
|
|
38887
|
+
}
|
|
38888
|
+
function hasPdfBackend(override) {
|
|
38889
|
+
const pathEnv = override?.pathEnv;
|
|
38890
|
+
const sofficePath = resolveSoffice(override?.sofficeBin, pathEnv);
|
|
38891
|
+
let pdftoppmPath = null;
|
|
38892
|
+
if (override?.pdftoppmBin && override.pdftoppmBin.trim()) {
|
|
38893
|
+
pdftoppmPath = findOnPath(override.pdftoppmBin.trim(), pathEnv);
|
|
38894
|
+
} else {
|
|
38895
|
+
pdftoppmPath = findOnPath("pdftoppm", pathEnv);
|
|
38896
|
+
}
|
|
38897
|
+
const missing = [];
|
|
38898
|
+
if (!sofficePath) missing.push("soffice");
|
|
38899
|
+
if (!pdftoppmPath) missing.push("pdftoppm");
|
|
38900
|
+
if (missing.length === 0) {
|
|
38901
|
+
return { available: true, backend: "libreoffice", sofficePath, pdftoppmPath, missing: [] };
|
|
38902
|
+
}
|
|
38903
|
+
return { available: false, backend: "none", missing };
|
|
38904
|
+
}
|
|
38905
|
+
function assertPdfBackend(override) {
|
|
38906
|
+
const status = hasPdfBackend(override);
|
|
38907
|
+
if (!status.available) {
|
|
38908
|
+
throw createDependencyError(status.missing);
|
|
38909
|
+
}
|
|
38910
|
+
return { sofficePath: status.sofficePath, pdftoppmPath: status.pdftoppmPath };
|
|
38911
|
+
}
|
|
38912
|
+
|
|
38548
38913
|
// ../engine/src/export/pdf.ts
|
|
38549
38914
|
var DependencyError = class extends Error {
|
|
38550
38915
|
code = "E_DEPENDENCY";
|
|
@@ -38590,19 +38955,15 @@ function getPdfBackendBinary() {
|
|
|
38590
38955
|
if (cachedBackendBinary !== void 0) {
|
|
38591
38956
|
return cachedBackendBinary;
|
|
38592
38957
|
}
|
|
38593
|
-
const
|
|
38594
|
-
|
|
38595
|
-
|
|
38596
|
-
|
|
38597
|
-
cachedBackendBinary = bin;
|
|
38598
|
-
return bin;
|
|
38599
|
-
} catch {
|
|
38600
|
-
}
|
|
38958
|
+
const found = findOnPath("soffice") ?? findOnPath("libreoffice");
|
|
38959
|
+
if (found) {
|
|
38960
|
+
cachedBackendBinary = found;
|
|
38961
|
+
return found;
|
|
38601
38962
|
}
|
|
38602
38963
|
cachedBackendBinary = null;
|
|
38603
38964
|
return null;
|
|
38604
38965
|
}
|
|
38605
|
-
function
|
|
38966
|
+
function hasPdfBackend2() {
|
|
38606
38967
|
return getPdfBackendBinary() !== null;
|
|
38607
38968
|
}
|
|
38608
38969
|
async function convertDocxToPdf(docxBuffer, outPdfPath, options) {
|
|
@@ -38613,7 +38974,8 @@ async function convertDocxToPdf(docxBuffer, outPdfPath, options) {
|
|
|
38613
38974
|
Debian/Ubuntu: sudo apt install libreoffice
|
|
38614
38975
|
Fedora/RHEL: sudo dnf install libreoffice
|
|
38615
38976
|
macOS: brew install --cask libreoffice
|
|
38616
|
-
Windows: winget install TheDocumentFoundation.LibreOffice
|
|
38977
|
+
Windows: winget install TheDocumentFoundation.LibreOffice
|
|
38978
|
+
ASTra also looks in the standard install folders (for example C:\\Program Files\\LibreOffice\\program and /Applications/LibreOffice.app/Contents/MacOS).`
|
|
38617
38979
|
);
|
|
38618
38980
|
}
|
|
38619
38981
|
const preflight = runPreflightChecks(docxBuffer);
|
|
@@ -38675,7 +39037,7 @@ async function removeStagedDir(dir) {
|
|
|
38675
39037
|
}
|
|
38676
39038
|
async function convertWithFilter(docxBuffer, outPdfPath, filterSpec, options, binary, stagedForensics) {
|
|
38677
39039
|
const tmpDir = secureMkdtemp("astra-pdf-");
|
|
38678
|
-
const tmpDocxPath =
|
|
39040
|
+
const tmpDocxPath = path9.join(tmpDir, `doc_${Date.now()}.docx`);
|
|
38679
39041
|
let succeeded = false;
|
|
38680
39042
|
try {
|
|
38681
39043
|
await fsp4.writeFile(tmpDocxPath, docxBuffer);
|
|
@@ -38687,7 +39049,7 @@ async function convertWithFilter(docxBuffer, outPdfPath, filterSpec, options, bi
|
|
|
38687
39049
|
await new Promise((resolve10, reject) => {
|
|
38688
39050
|
const child = (0, import_child_process.spawn)(
|
|
38689
39051
|
binary,
|
|
38690
|
-
[`-env:UserInstallation=${(0, import_url.pathToFileURL)(
|
|
39052
|
+
[`-env:UserInstallation=${(0, import_url.pathToFileURL)(path9.join(tmpDir, "lo_profile")).href}`, "--headless", "--convert-to", filterSpec, tmpDocxPath, "--outdir", tmpDir],
|
|
38691
39053
|
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
38692
39054
|
);
|
|
38693
39055
|
let stderr = "";
|
|
@@ -38723,19 +39085,19 @@ async function convertWithFilter(docxBuffer, outPdfPath, filterSpec, options, bi
|
|
|
38723
39085
|
if (!converted && lastError) {
|
|
38724
39086
|
throw lastError;
|
|
38725
39087
|
}
|
|
38726
|
-
const expectedPdfName =
|
|
38727
|
-
const generatedPdfPath =
|
|
38728
|
-
if (!
|
|
39088
|
+
const expectedPdfName = path9.basename(tmpDocxPath).replace(/\.docx$/i, ".pdf");
|
|
39089
|
+
const generatedPdfPath = path9.join(tmpDir, expectedPdfName);
|
|
39090
|
+
if (!fs10.existsSync(generatedPdfPath)) {
|
|
38729
39091
|
throw new Error(`PDF conversion failed: output file ${generatedPdfPath} was not created`);
|
|
38730
39092
|
}
|
|
38731
|
-
const targetDir =
|
|
38732
|
-
assertWriteTargetNoSymlink(
|
|
39093
|
+
const targetDir = path9.dirname(path9.resolve(outPdfPath));
|
|
39094
|
+
assertWriteTargetNoSymlink(path9.resolve(outPdfPath));
|
|
38733
39095
|
await fsp4.mkdir(targetDir, { recursive: true });
|
|
38734
|
-
assertWriteTargetNoSymlink(
|
|
38735
|
-
await writeFileNoFollow(
|
|
38736
|
-
|
|
39096
|
+
assertWriteTargetNoSymlink(path9.resolve(outPdfPath));
|
|
39097
|
+
await writeFileNoFollow(path9.resolve(outPdfPath), await fsp4.readFile(generatedPdfPath));
|
|
39098
|
+
fs10.chmodSync(path9.resolve(outPdfPath), 420);
|
|
38737
39099
|
succeeded = true;
|
|
38738
|
-
return
|
|
39100
|
+
return path9.resolve(outPdfPath);
|
|
38739
39101
|
} finally {
|
|
38740
39102
|
if (succeeded) {
|
|
38741
39103
|
try {
|
|
@@ -38751,82 +39113,8 @@ async function convertWithFilter(docxBuffer, outPdfPath, filterSpec, options, bi
|
|
|
38751
39113
|
}
|
|
38752
39114
|
}
|
|
38753
39115
|
|
|
38754
|
-
// ../engine/src/
|
|
38755
|
-
var
|
|
38756
|
-
var path9 = __toESM(require("path"));
|
|
38757
|
-
var VISUAL_DEPENDENCY_REMEDY = "PDF render backend missing. Install LibreOffice and poppler-utils (pdftoppm): sudo apt install libreoffice poppler-utils, or brew install libreoffice poppler. Alternatively inspect the XML/DOM without images (telemetry-only mode).";
|
|
38758
|
-
function createDependencyError(missing) {
|
|
38759
|
-
const which = missing.length > 0 ? missing.join(", ") : "soffice, pdftoppm";
|
|
38760
|
-
return Object.assign(new Error(`E_DEPENDENCY: headless visual backend unavailable (missing: ${which})`), {
|
|
38761
|
-
code: "E_DEPENDENCY",
|
|
38762
|
-
remedy: VISUAL_DEPENDENCY_REMEDY
|
|
38763
|
-
});
|
|
38764
|
-
}
|
|
38765
|
-
function findOnPath(name2, pathEnv) {
|
|
38766
|
-
try {
|
|
38767
|
-
if (!name2 || name2.trim() === "") return null;
|
|
38768
|
-
const trimmed = name2.trim();
|
|
38769
|
-
if (trimmed.includes("/") || trimmed.includes("\\")) {
|
|
38770
|
-
try {
|
|
38771
|
-
const abs = path9.resolve(trimmed);
|
|
38772
|
-
fs10.accessSync(abs, fs10.constants.X_OK);
|
|
38773
|
-
const stat3 = fs10.statSync(abs);
|
|
38774
|
-
if (stat3.isFile()) return abs;
|
|
38775
|
-
return null;
|
|
38776
|
-
} catch {
|
|
38777
|
-
return null;
|
|
38778
|
-
}
|
|
38779
|
-
}
|
|
38780
|
-
const pathValue = pathEnv !== void 0 ? pathEnv : process.env.PATH ?? "";
|
|
38781
|
-
if (!pathValue) return null;
|
|
38782
|
-
const dirs = pathValue.split(path9.delimiter).filter((d) => d.length > 0);
|
|
38783
|
-
for (const dir of dirs) {
|
|
38784
|
-
try {
|
|
38785
|
-
const candidate = path9.join(dir, trimmed);
|
|
38786
|
-
fs10.accessSync(candidate, fs10.constants.X_OK);
|
|
38787
|
-
const stat3 = fs10.statSync(candidate);
|
|
38788
|
-
if (stat3.isFile()) return candidate;
|
|
38789
|
-
} catch {
|
|
38790
|
-
continue;
|
|
38791
|
-
}
|
|
38792
|
-
}
|
|
38793
|
-
return null;
|
|
38794
|
-
} catch {
|
|
38795
|
-
return null;
|
|
38796
|
-
}
|
|
38797
|
-
}
|
|
38798
|
-
function resolveSoffice(override, pathEnv) {
|
|
38799
|
-
if (override && override.trim()) {
|
|
38800
|
-
const direct = findOnPath(override.trim(), pathEnv);
|
|
38801
|
-
if (direct) return direct;
|
|
38802
|
-
return null;
|
|
38803
|
-
}
|
|
38804
|
-
return findOnPath("soffice", pathEnv) ?? findOnPath("libreoffice", pathEnv);
|
|
38805
|
-
}
|
|
38806
|
-
function hasPdfBackend2(override) {
|
|
38807
|
-
const pathEnv = override?.pathEnv;
|
|
38808
|
-
const sofficePath = resolveSoffice(override?.sofficeBin, pathEnv);
|
|
38809
|
-
let pdftoppmPath = null;
|
|
38810
|
-
if (override?.pdftoppmBin && override.pdftoppmBin.trim()) {
|
|
38811
|
-
pdftoppmPath = findOnPath(override.pdftoppmBin.trim(), pathEnv);
|
|
38812
|
-
} else {
|
|
38813
|
-
pdftoppmPath = findOnPath("pdftoppm", pathEnv);
|
|
38814
|
-
}
|
|
38815
|
-
const missing = [];
|
|
38816
|
-
if (!sofficePath) missing.push("soffice");
|
|
38817
|
-
if (!pdftoppmPath) missing.push("pdftoppm");
|
|
38818
|
-
if (missing.length === 0) {
|
|
38819
|
-
return { available: true, backend: "libreoffice", sofficePath, pdftoppmPath, missing: [] };
|
|
38820
|
-
}
|
|
38821
|
-
return { available: false, backend: "none", missing };
|
|
38822
|
-
}
|
|
38823
|
-
function assertPdfBackend(override) {
|
|
38824
|
-
const status = hasPdfBackend2(override);
|
|
38825
|
-
if (!status.available) {
|
|
38826
|
-
throw createDependencyError(status.missing);
|
|
38827
|
-
}
|
|
38828
|
-
return { sofficePath: status.sofficePath, pdftoppmPath: status.pdftoppmPath };
|
|
38829
|
-
}
|
|
39116
|
+
// ../engine/src/export/pdf-source.ts
|
|
39117
|
+
var STRICT_UTF8 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
38830
39118
|
|
|
38831
39119
|
// ../engine/src/visual/renderer.ts
|
|
38832
39120
|
var fs11 = __toESM(require("fs"));
|
|
@@ -39216,8 +39504,18 @@ function computeDocStats(ast) {
|
|
|
39216
39504
|
if (hits) words += hits.length;
|
|
39217
39505
|
}
|
|
39218
39506
|
}
|
|
39219
|
-
const
|
|
39220
|
-
|
|
39507
|
+
const pageTerms = {
|
|
39508
|
+
words: words / 450,
|
|
39509
|
+
tables: (tables + callouts) * 0.45,
|
|
39510
|
+
images: images * 0.5,
|
|
39511
|
+
pageBreaks
|
|
39512
|
+
};
|
|
39513
|
+
const pageBudget = pageTerms.words + pageTerms.tables + pageTerms.images + pageTerms.pageBreaks;
|
|
39514
|
+
return { words, paragraphs, pages: Math.max(1, Math.ceil(pageBudget)), pageTerms, tables, callouts, images };
|
|
39515
|
+
}
|
|
39516
|
+
function roundedPageTerms(terms) {
|
|
39517
|
+
const r = (n) => Math.round(n * 100) / 100;
|
|
39518
|
+
return { words: r(terms.words), tables: r(terms.tables), images: r(terms.images), pageBreaks: r(terms.pageBreaks) };
|
|
39221
39519
|
}
|
|
39222
39520
|
|
|
39223
39521
|
// ../engine/src/visual/telemetry.ts
|
|
@@ -40810,7 +41108,7 @@ function assertTemplateSeed(parser2, template) {
|
|
|
40810
41108
|
}
|
|
40811
41109
|
if (!matched) {
|
|
40812
41110
|
throw new Error(
|
|
40813
|
-
`E_VALIDATION: line 1: open document does not hold the ${template} seed ("${marker}"). Remedy:
|
|
41111
|
+
`E_VALIDATION: line 1: open document does not hold the ${template} seed ("${marker}"). Remedy: to add to this document, call compose_document again without template; to start from the ${template} template, call new_document with template "${template}" first and compose into that.`
|
|
40814
41112
|
);
|
|
40815
41113
|
}
|
|
40816
41114
|
}
|
|
@@ -43103,7 +43401,7 @@ var LEAN6_DESCRIPTIONS = {
|
|
|
43103
43401
|
{ workflow: true }
|
|
43104
43402
|
),
|
|
43105
43403
|
inspect_document: withSharedDocs(
|
|
43106
|
-
'Inspect a session: standard TOON outline (fidelity compact|standard|full, default standard) plus style palette passthrough {availableStyles,brandColors,primaryFont} parsed from word/styles.xml, plus document core properties `metadata` {title,creator,subject,description,keywords,category,lastModifiedBy,revision,created,modified} read from docProps/core.xml (absent when the docx has no core part). Use include:["comments","revisions"] for review records paged by reviewOffset/reviewLimit (max 50; offset/limit page nodes only); commentId or revisionId reads full detail and needs its include. Renders the in-memory session state (includes unsaved execute_batch edits); `dirty:true` means the session differs from the last saved file, `revision` is the in-memory revision. Node ids are verbatim: paragraphs carry 8-hex w14:paraId (e.g. 3F2A9C1B), tables t_001.., sections sec_root, sec_2, sec_3.. by position; use them as-is for targets/anchors. A repeat paragraph sharing one native paraId reports a `<paraId>_dup` session id instead (e.g. 3F2A9C1B_dup): saving refuses until the repeat is deleted and its text reinserted for a fresh id. Tables render one row per table with grid cells as rNcM coordinates (r1c1 value | r1c2 value, rows joined by ;); merged spans are marked (spanN); merge covers are tagged hidden-cover (not directly editable, address the restart cell). Cell edits: the replace, style, namedStyle, clearFormat, hyperlink, bookmark and comment actions accept target "<tableId>:r<row>c<col>" (1-based, as the grid prints it; add ":p<n>" to pick one paragraph of a multi-paragraph cell). Other actions, including table, take the table id. Every row and column number counts from 1 (cell addresses, rowIndex, colIndex, columnIndex, row, col, index, afterIndex, fromRow/toRow/fromCol/toCol), so r3c2 and {rowIndex:3,colIndex:2} are the same cell. Chart nodes render style chart|chart3d with title + categories + series in the text ([Chart column] Title | cats: Q1,Q2 | ARR: 10,25), read from the chart part. Flag legend: [TBL] table [IMG] image/drawing [EQ] equation [CITE] citation [CMT] comment [TRK] tracked [LNK] link [BKMK] bookmark [L#] list level (1 is the top level; list ops count the same way) [+ADDED]/[~MODIFIED] diff; standard tier adds [B] bold [I] italic [C:hex] color [SZ:half-points] size [RS:name] run style [J:just] [N:num] numbering [HDR]/[ZEBRA] table look [CH:type]/[RID:id] chart. Palette lists every style id verbatim with no trim (large templates return the full list). 3-tier workflow for large docs: (1) macro-overview inspect (compact, first window) -> (2) search_content to find a target node id -> (3) local zoom via {aroundNode:"<id>",window:5} or page with {offset,limit}. Windowing opts: offset (min 0, default 0), limit (1-200, default 50), aroundNode + window (1-20, default 5) returning [node-w,node+w], maxTokens (200-8000, cuts on a clean row boundary, always reported via truncated/hasMore). aroundNode and offset/limit are mutually exclusive (use one or the other). Windowed renders preserve the requested tier; the TOON fidelity marker stays lossy by design (TOON is a lossy projection). Docs under ~2000 tokens return the full TOON (unchanged); larger docs default to the first ~50-node window {totalNodes,returnedNodes,offset,limit,hasMore,nextOffset,toonText} with a navigation remedy. Full fidelity requires sectionId; sectionId is a section id or a node id (a node id prefix needs 4 characters or more), and an id that matches nothing is an error that names the available section ids. Example {"sessionId":"sess_abc","fidelity":"standard"} -> TOON text + palette. Use the palette style names verbatim (e.g. Corporate Callout); never invent styles. When the session carries a style guide, the envelope also includes its compact styleGuide summary {name,headerFill,headerText,chartPalette,calloutTheme}. IDs are session-scoped: valid only in the issuing session, must not be stored across sessions, and duplicate client-supplied ids are rejected.'
|
|
43404
|
+
'Inspect a session: standard TOON outline (fidelity outline|compact|standard|full, default standard) plus style palette passthrough fidelity outline lists headings only (ID, level L, Style, Text, Section = the headings above); there rowCount/totalNodes count heading rows, `outline` {headings,nodes,tables,images,words} counts the whole document, and aroundNode on a body node shows the heading above it (`aroundResolvedTo`). {availableStyles,brandColors,primaryFont} parsed from word/styles.xml, plus document core properties `metadata` {title,creator,subject,description,keywords,category,lastModifiedBy,revision,created,modified} read from docProps/core.xml (absent when the docx has no core part). Use include:["comments","revisions"] for review records paged by reviewOffset/reviewLimit (max 50; offset/limit page nodes only); commentId or revisionId reads full detail and needs its include. Renders the in-memory session state (includes unsaved execute_batch edits); `dirty:true` means the session differs from the last saved file, `revision` is the in-memory revision. Node ids are verbatim: paragraphs carry 8-hex w14:paraId (e.g. 3F2A9C1B), tables t_001.., sections sec_root, sec_2, sec_3.. by position; use them as-is for targets/anchors. A repeat paragraph sharing one native paraId reports a `<paraId>_dup` session id instead (e.g. 3F2A9C1B_dup): saving refuses until the repeat is deleted and its text reinserted for a fresh id. Tables render one row per table with grid cells as rNcM coordinates (r1c1 value | r1c2 value, rows joined by ;); merged spans are marked (spanN); merge covers are tagged hidden-cover (not directly editable, address the restart cell). Cell edits: the replace, style, namedStyle, clearFormat, hyperlink, bookmark and comment actions accept target "<tableId>:r<row>c<col>" (1-based, as the grid prints it; add ":p<n>" to pick one paragraph of a multi-paragraph cell). Other actions, including table, take the table id. Every row and column number counts from 1 (cell addresses, rowIndex, colIndex, columnIndex, row, col, index, afterIndex, fromRow/toRow/fromCol/toCol), so r3c2 and {rowIndex:3,colIndex:2} are the same cell. Chart nodes render style chart|chart3d with title + categories + series in the text ([Chart column] Title | cats: Q1,Q2 | ARR: 10,25), read from the chart part. Flag legend: [TBL] table [IMG] image/drawing [EQ] equation [CITE] citation [CMT] comment [TRK] tracked [LNK] link [BKMK] bookmark [L#] list level (1 is the top level; list ops count the same way) [+ADDED]/[~MODIFIED] diff; standard tier adds [B] bold [I] italic [C:hex] color [SZ:half-points] size [RS:name] run style [J:just] [N:num] numbering [HDR]/[ZEBRA] table look [CH:type]/[RID:id] chart. Palette lists every style id verbatim with no trim (large templates return the full list). 3-tier workflow for large docs: (1) macro-overview inspect (compact, first window) -> (2) search_content to find a target node id -> (3) local zoom via {aroundNode:"<id>",window:5} or page with {offset,limit}. Windowing opts: offset (min 0, default 0), limit (1-200, default 50), aroundNode + window (1-20, default 5) returning [node-w,node+w], maxTokens (200-8000, cuts on a clean row boundary, always reported via truncated/hasMore). aroundNode and offset/limit are mutually exclusive (use one or the other). Windowed renders preserve the requested tier; the TOON fidelity marker stays lossy by design (TOON is a lossy projection). Docs under ~2000 tokens return the full TOON (unchanged); larger docs default to the first ~50-node window {totalNodes,returnedNodes,offset,limit,hasMore,nextOffset,toonText} with a navigation remedy. Full fidelity requires sectionId; sectionId is a section id or a node id (a node id prefix needs 4 characters or more), and an id that matches nothing is an error that names the available section ids. Example {"sessionId":"sess_abc","fidelity":"standard"} -> TOON text + palette. Use the palette style names verbatim (e.g. Corporate Callout); never invent styles. When the session carries a style guide, the envelope also includes its compact styleGuide summary {name,headerFill,headerText,chartPalette,calloutTheme}. IDs are session-scoped: valid only in the issuing session, must not be stored across sessions, and duplicate client-supplied ids are rejected.'
|
|
43107
43405
|
),
|
|
43108
43406
|
search_content: withSharedDocs(
|
|
43109
43407
|
'Search live paragraph/table w:t text; returns [{id,preview}] node hits for anchors/targets. Use scope:"comments" or scope:"all" to search comment text; the default scope is body. At most 50 matches come back; truncated:true says more exist. A table hit also lists `cells` (up to 5 addresses such as t_001:r3c1) for the cells that hold the match; pass one as the target of replace/style/namedStyle/clearFormat/hyperlink/bookmark/comment. Chart titles/series are NOT matched by search (they live in the chart part, not w:t); discover them in inspect TOON chart rows, then anchor by the chart node id. Image alt text and footnote/endnote bodies are NOT matched either (search covers body w:t text only); find images via inspect [IMG] rows and footnotes via their anchor ids. Search extension to those parts is a later-program candidate, not this surface. Default is an exact literal substring (case-insensitive); pass {"regex":true} to interpret the query as a regular expression (bounded: pattern <=200 chars, ~500ms evaluation budget, case-insensitive). Caps: first 50 hits, 120-char previews. Literal queries containing regex metacharacters return literal results plus a hint that regex:true exists. Example {"sessionId":"sess_abc","query":"Executive Summary"} -> {"ok":true,"matches":[{"id":"3F2A9C1B","preview":"Executive ..."}]}. Search before editing when the anchor id is unknown.'
|
|
@@ -43144,7 +43442,7 @@ var PARITY_DESCRIPTIONS = {
|
|
|
43144
43442
|
'Preflight read-only OOXML audit for a session: runs the same validator gate save_document enforces, before anything is written. Detects schema violations while they are still cheap to fix. Strictly read-only: zero session mutation (revision and dirty flag untouched), zero disk writes, zero history commits. Returns {ok:true,sessionId,revision,dirty,validation:{valid,errorCount,errors}} where errors carry description + xPath (first 10, same shape as the save-blocked envelope). The verdict is on the document as a save would write it (children put in schema order first); when that save would repair something the result also carries pendingNormalizations (a list of repairs, absent when none), so a valid verdict is not mistaken for "valid exactly as it stands". Use before save_document when the package may be non-conforming; save_document re-gates before writing. Example {"sessionId":"sess_abc"} -> {"ok":true,"validation":{"valid":true,"errorCount":0,"errors":[]}}. '
|
|
43145
43443
|
),
|
|
43146
43444
|
get_stats: withSharedDocs(
|
|
43147
|
-
'Token-cheap document census for a session: words, paragraphs, tables, callouts, images, and an estimated editorial page budget (layout-weighted by words, tables, images, and explicit page breaks) in a single small envelope. Counters are the exact engine counters behind CLI stats, so both surfaces always agree. Physical pagination is finalized when exporting to PDF via LibreOffice. Read-only: never mutates, never bumps the revision. Returns {ok:true,sessionId,revision,dirty,words,paragraphs,tables,callouts,images,pages}. A callout (a "> [!NOTE]" alert or a callout op: a one-cell table with a fill) is counted in callouts and not in tables. Use instead of inspect_document when only counts are needed (a full TOON dump costs thousands of tokens for the same question). Example {"sessionId":"sess_abc"} -> {"ok":true,"words":42,"paragraphs":5}. '
|
|
43445
|
+
'Token-cheap document census for a session: words, paragraphs, tables, callouts, images, and an estimated editorial page budget (layout-weighted by words, tables, images, and explicit page breaks) in a single small envelope. Counters are the exact engine counters behind CLI stats, so both surfaces always agree. Physical pagination is finalized when exporting to PDF via LibreOffice. Read-only: never mutates, never bumps the revision. Returns {ok:true,sessionId,revision,dirty,words,paragraphs,tables,callouts,images,pages,pageTerms}. pages is a rough estimate, max(1, ceil(words/450 + 0.45 per table or callout + 0.5 per image + 1 per page break)); pageTerms gives those four terms in pages, so a caller can see how much of it is counted from tables, callouts, images and page breaks (`pageTerms.tables` covers tables and callouts), which the estimate cannot size. Use visual_inspect for the renderer\'s page count. A callout (a "> [!NOTE]" alert or a callout op: a one-cell table with a fill) is counted in callouts and not in tables. Use instead of inspect_document when only counts are needed (a full TOON dump costs thousands of tokens for the same question). Example {"sessionId":"sess_abc"} -> {"ok":true,"words":42,"paragraphs":5}. '
|
|
43148
43446
|
),
|
|
43149
43447
|
sanitize_document: withSharedDocs(
|
|
43150
43448
|
`Scrub an open session document. By default it removes revision ids, authorship, embedded EXIF and hidden text PII. Tracked changes and comments are kept, with the text of every tracked deletion, unless you ask: acceptAllRevisions:true accepts all tracked changes (deleted text is removed) and stripComments:true removes comments. The CLI presets (bare sanitize, --client-safe, --metadata-only, --redact-pii) turn both on; this tool does not. Macro/active parts are removed on request. It uses the same engine scrub path as CLI sanitize. Body text is kept. Options mirror the engine scrub keys 1:1 (zeroRetention, purgeRSIDs, purgeAuthors, purgeExif, purgePII, acceptAllRevisions, stripComments, replacementAuthor, allowMacros, stripMacros); omitted options take engine defaults. With acceptAllRevisions it returns E_VALIDATION and no file when revision markup remains that cannot be resolved. allowMacros and stripMacros together fail with E_VALIDATION. ${piiCoverageSentence()} Returns {ok:true,sessionId,revision,dirty,removedRSIDs,purgedAuthors,strippedExifBytes,purgedPII,bodyPII,piiCoverage,acceptedRevisions,strippedComments,strippedMacros,macroParts,filesTouched,droppedRogue,...} plus the standard redoCleared signal when a redo chain is dropped. Example {"sessionId":"sess_abc","stripMacros":true} -> scrubbed session at a new revision (save after). `
|
|
@@ -43182,7 +43480,7 @@ var OpenDocumentSchema = import_zod4.z.object({
|
|
|
43182
43480
|
}).strict();
|
|
43183
43481
|
var InspectDocumentSchema = import_zod4.z.object({
|
|
43184
43482
|
sessionId: import_zod4.z.string().min(1).max(128),
|
|
43185
|
-
fidelity: import_zod4.z.enum(["compact", "standard", "full"]).optional(),
|
|
43483
|
+
fidelity: import_zod4.z.enum(["outline", "compact", "standard", "full"]).optional(),
|
|
43186
43484
|
sectionId: import_zod4.z.string().min(1).max(256).optional(),
|
|
43187
43485
|
maxTextLength: import_zod4.z.number().int().min(1).max(1e4).optional(),
|
|
43188
43486
|
// Phase C-2 smart windowing: offset/limit page the node rows; aroundNode +
|
|
@@ -43429,6 +43727,12 @@ function readPendingNormalizations(handle) {
|
|
|
43429
43727
|
return [];
|
|
43430
43728
|
}
|
|
43431
43729
|
}
|
|
43730
|
+
function outlineCountsOf(ast) {
|
|
43731
|
+
const stats = computeDocStats(ast);
|
|
43732
|
+
let nodes = 0;
|
|
43733
|
+
for (const sec of ast.sections) nodes += sec.nodes.length;
|
|
43734
|
+
return { headings: countHeadings(ast), nodes, tables: stats.tables + stats.callouts, images: stats.images, words: stats.words };
|
|
43735
|
+
}
|
|
43432
43736
|
async function handleInspectDocument(store, input) {
|
|
43433
43737
|
const parsed = InspectDocumentSchema.safeParse(input);
|
|
43434
43738
|
if (!parsed.success) {
|
|
@@ -43528,6 +43832,7 @@ async function handleInspectDocument(store, input) {
|
|
|
43528
43832
|
pendingNormalizations: readPendingNormalizations(handle),
|
|
43529
43833
|
...handle.styleGuide !== void 0 ? { styleGuide: summarizeStyleGuide(handle.styleGuide) } : {},
|
|
43530
43834
|
toonText: fullText,
|
|
43835
|
+
...fidelity === "outline" ? { outline: outlineCountsOf(handle.ast) } : {},
|
|
43531
43836
|
rowCount: totalNodes,
|
|
43532
43837
|
totalNodes,
|
|
43533
43838
|
returnedNodes: totalNodes,
|
|
@@ -43541,11 +43846,40 @@ async function handleInspectDocument(store, input) {
|
|
|
43541
43846
|
let start;
|
|
43542
43847
|
let end;
|
|
43543
43848
|
let effectiveLimit = limit;
|
|
43849
|
+
let aroundResolvedTo;
|
|
43850
|
+
let note;
|
|
43544
43851
|
if (aroundNode !== void 0) {
|
|
43545
|
-
|
|
43852
|
+
let idx = table.rows.findIndex((r) => r.id === aroundNode);
|
|
43853
|
+
if (idx < 0 && fidelity === "outline") {
|
|
43854
|
+
const scoped = sectionId ? resolveSectionScope(handle.ast.sections, sectionId) : handle.ast.sections;
|
|
43855
|
+
const order = scoped.flatMap((sec) => sec.nodes.map((n) => n.id));
|
|
43856
|
+
const at = order.indexOf(aroundNode);
|
|
43857
|
+
if (at < 0) {
|
|
43858
|
+
const anywhere = handle.ast.sections.some((sec) => sec.nodes.some((n) => n.id === aroundNode));
|
|
43859
|
+
if (anywhere) {
|
|
43860
|
+
throw Object.assign(
|
|
43861
|
+
new Error(`inspect_document: aroundNode "${aroundNode}" is not in section "${sectionId}".`),
|
|
43862
|
+
{ code: "E_VALIDATION", remedy: "Drop sectionId, or pass a node id that belongs to that section." }
|
|
43863
|
+
);
|
|
43864
|
+
}
|
|
43865
|
+
} else {
|
|
43866
|
+
const headingIds = new Set(table.rows.map((r) => r.id));
|
|
43867
|
+
let above = -1;
|
|
43868
|
+
for (let k = at; k >= 0 && above < 0; k--) if (headingIds.has(order[k])) above = k;
|
|
43869
|
+
if (above >= 0) {
|
|
43870
|
+
idx = table.rows.findIndex((r) => r.id === order[above]);
|
|
43871
|
+
aroundResolvedTo = order[above];
|
|
43872
|
+
note = `Node ${aroundNode} is not a heading; showing the heading above it (${order[above]}).`;
|
|
43873
|
+
} else {
|
|
43874
|
+
aroundResolvedTo = null;
|
|
43875
|
+
note = totalNodes === 0 ? `No headings${sectionId ? " in this scope" : " in the document"}; the outline is empty.` : `Node ${aroundNode} has no heading above it${sectionId ? " in this scope" : ""}; showing the first headings.`;
|
|
43876
|
+
idx = 0;
|
|
43877
|
+
}
|
|
43878
|
+
}
|
|
43879
|
+
}
|
|
43546
43880
|
if (idx < 0) {
|
|
43547
43881
|
throw Object.assign(
|
|
43548
|
-
new Error(`inspect_document: aroundNode "${aroundNode}" not found (${totalNodes} nodes in scope).`),
|
|
43882
|
+
new Error(`inspect_document: aroundNode "${aroundNode}" not found (${totalNodes} ${fidelity === "outline" ? "headings" : "nodes"} in scope).`),
|
|
43549
43883
|
{
|
|
43550
43884
|
code: "E_VALIDATION",
|
|
43551
43885
|
remedy: 'Call search_content to find a valid target node id, then retry inspect_document with {aroundNode:"<id>", window:5}.'
|
|
@@ -43554,6 +43888,10 @@ async function handleInspectDocument(store, input) {
|
|
|
43554
43888
|
}
|
|
43555
43889
|
start = Math.max(0, idx - window);
|
|
43556
43890
|
end = Math.min(totalNodes, idx + window + 1);
|
|
43891
|
+
if (aroundResolvedTo === null) {
|
|
43892
|
+
start = 0;
|
|
43893
|
+
end = Math.min(totalNodes, window * 2 + 1);
|
|
43894
|
+
}
|
|
43557
43895
|
effectiveLimit = end - start;
|
|
43558
43896
|
} else {
|
|
43559
43897
|
if (offset >= totalNodes && totalNodes > 0) {
|
|
@@ -43620,6 +43958,7 @@ async function handleInspectDocument(store, input) {
|
|
|
43620
43958
|
pendingNormalizations: readPendingNormalizations(handle),
|
|
43621
43959
|
...handle.styleGuide !== void 0 ? { styleGuide: summarizeStyleGuide(handle.styleGuide) } : {},
|
|
43622
43960
|
toonText,
|
|
43961
|
+
...fidelity === "outline" ? { outline: outlineCountsOf(handle.ast) } : {},
|
|
43623
43962
|
rowCount: returnedNodes,
|
|
43624
43963
|
totalNodes,
|
|
43625
43964
|
returnedNodes,
|
|
@@ -43629,6 +43968,8 @@ async function handleInspectDocument(store, input) {
|
|
|
43629
43968
|
nextOffset: hasMore ? start + returnedNodes : null,
|
|
43630
43969
|
truncated
|
|
43631
43970
|
};
|
|
43971
|
+
if (aroundResolvedTo !== void 0) out.aroundResolvedTo = aroundResolvedTo;
|
|
43972
|
+
if (note !== void 0) out.note = note;
|
|
43632
43973
|
if (hasMore || truncated) {
|
|
43633
43974
|
out.remedy = `Large document: showing ${returnedNodes}/${totalNodes} TOON nodes${truncated ? ` (cut to fit maxTokens ${maxTokens})` : ""}. Navigate: 1) search_content to find a target node id, 2) inspect_document {aroundNode:"<id>", window:5} to zoom, 3) page with {offset:${hasMore ? start + returnedNodes : start}, limit:${effectiveLimit}} to continue.`;
|
|
43634
43975
|
}
|
|
@@ -43693,7 +44034,7 @@ var ComposeDocumentSchema = import_zod5.z.object({
|
|
|
43693
44034
|
// Side of anchor the first block goes on (default after). Needs anchor.
|
|
43694
44035
|
position: import_zod5.z.enum(["before", "after"]).optional()
|
|
43695
44036
|
}).strict();
|
|
43696
|
-
var COMPOSE_DOCUMENT_DESCRIPTION = 'Compose a Markdown string into the open session document (appends after existing content, or at anchor with position before|after; an unchanged built-in template created in this session has its sample content replaced). A document can be built in several compose calls: the first call of a session mints ids md_0001..., later calls mint md2_0001..., md3_0001..., so ids never collide; pass anchor (a node id, start or end) and position to add a section in the middle without another tool. Runs the engine compose path, then applies the resulting operations through the same execute_batch path (strict schema gate, per-chunk quotas, structural checks, per-chunk atomic rollback, then inline-span run splitting), so every gate a batch meets applies here unchanged. Supported input: #/##/### headings (real Heading1..3 styles), blank-line paragraphs (single newlines join with a space), flat bullet (-, *, +) and ordered (1., 1)) lists with real numbering, GitHub-style tables with a header separator row (header row shaded and repeated), --- dividers, fenced or indented code blocks (monospace, line breaks kept), [^label] footnotes with definitions, inline spans (**bold**, *italic*, `code`, [text](url)) applied as runs with real hyperlink targets, plain `> quote` blocks (written in the Quote paragraph style: blank > lines start a new paragraph, > > nests up to 3 levels, flat lists and fenced code are kept inside, a table, heading or image inside a quote fails), and standalone block images/charts () resolved from allowedDir. Out-of-scope input fails closed with E_VALIDATION plus a 1-based line number and writes nothing: raw HTML (except [!NOTE]-style alert callouts, which compile to callout containers and keep bold, italic, code and links), custom styles, deeper headings, nested lists or tables, task items. Block image/chart refs need allowedDir pointing at the folder holding the files (refs stay jailed inside it; without allowedDir they fail closed); remote URL refs fail closed; inline data-URL block images bypass allowedDir by design (bytes decode in memory through the same image gates). Sources compiling past 500 operations apply as sequential 500-operation chunks; the compose is atomic as a whole: when any chunk fails, every chunk before it is rewound and the document stays as it was. Citation numbers and the References list restart in each compose call. Optional template (report|memo|blank) is accepted on the first compose only and requires the open document to still hold that template seed, otherwise composition fails before any write (for a fresh templated document call new_document first, then compose_document with template), and placement is append after existing content except for an unchanged built-in template created in this session. Reopened documents and external templates keep existing content. Returns {ok:true,opCount,appliedCount,revision,createdNodeIds,results,validation:{valid,errorCount,errors}} where createdNodeIds lists new node ids in document order (use them as anchors without re-inspecting) and validation proves the post-compose package state via a read-only OOXML check (save_document re-gates before writing). A compose that drops a non-empty redo stack signals it (redoCleared:true + redoClearedWarning naming the depth): a fresh edit invalidates redo. Optional styleGuide (bundled guide name or guide .md path, jailed like image refs: explicit allowedDir else the session anchor; remote URLs fail closed) replaces the session guide (not a one-batch override: later batches inherit it until another compose replaces it) and skins this compose (table/header-fill, chart-palette, callout, and code defaults apply where ops omit params; explicit params still win). Guides cannot be detached: open a new session to work without a guide. With no guide, compose uses the built-in Default Corporate look (navy table header, bold white header text, striped rows). styleGuide "legacy" gives the older grey header for this call only and is not stored on the session. An italic-only line directly under the opening # title becomes the Subtitle; a [!NOTE]-style line outside a blockquote stays plain text and comes back in warnings. Later batches inherit it, inspect_document shows its compact summary, save_document reports advisory styleWarnings. Unknown names fail with E_VALIDATION naming the available set. Math: $$ ... $$ or \\[ ... \\] blocks become display equations and \\(...\\) becomes inline math (one line, up to 500 characters; refused inside links and table cells). Display blocks are top level only (inside a list item or alert body they fail closed). Optional dollarMath (default false) treats $...$ as inline math; off by default because prices and shell text can look like math. Workflow: open_document -> compose_document -> save_document. Example {"sessionId":"sess_abc","markdown":"# Plan\\n\\n- a\\n- b\\n"} (add "allowedDir":"<dir>" for image/chart refs, "template":"report|memo|blank" to verify the seed). IDs are session-scoped: valid only in the issuing session, must not be stored across sessions. When dc:title is still a template placeholder it is set from the first Title or Heading 1 (titleSetFrom says so); a title already set is kept. persistentIds maps each created paragraph id (md_0003) to the paragraph id the saved file carries, so a reopened session can be addressed with it (tables are renumbered t_001.. by position on reopen).';
|
|
44037
|
+
var COMPOSE_DOCUMENT_DESCRIPTION = 'Compose a Markdown string into the open session document (appends after existing content, or at anchor with position before|after; an unchanged built-in template created in this session has its sample content replaced). A document can be built in several compose calls: the first call of a session mints ids md_0001..., later calls mint md2_0001..., md3_0001..., so ids never collide; pass anchor (a node id, start or end) and position to add a section in the middle without another tool. Runs the engine compose path, then applies the resulting operations through the same execute_batch path (strict schema gate, per-chunk quotas, structural checks, per-chunk atomic rollback, then inline-span run splitting), so every gate a batch meets applies here unchanged. Supported input: #/##/### headings (real Heading1..3 styles), blank-line paragraphs (single newlines join with a space), flat bullet (-, *, +) and ordered (1., 1)) lists with real numbering, GitHub-style tables with a header separator row (header row shaded and repeated), --- dividers, fenced or indented code blocks (monospace, line breaks kept), [^label] footnotes with definitions, inline spans (**bold**, *italic*, `code`, [text](url)) applied as runs with real hyperlink targets, plain `> quote` blocks (written in the Quote paragraph style: blank > lines start a new paragraph, > > nests up to 3 levels, flat lists and fenced code are kept inside, a table, heading or image inside a quote fails), and standalone block images/charts () resolved from allowedDir. Out-of-scope input fails closed with E_VALIDATION plus a 1-based line number and writes nothing: raw HTML (except [!NOTE]-style alert callouts, which compile to callout containers and keep bold, italic, code and links), custom styles, deeper headings, nested lists or tables, task items. Block image/chart refs need allowedDir pointing at the folder holding the files (refs stay jailed inside it; without allowedDir they fail closed); remote URL refs fail closed; inline data-URL block images bypass allowedDir by design (bytes decode in memory through the same image gates). Sources compiling past 500 operations apply as sequential 500-operation chunks; the compose is atomic as a whole: when any chunk fails, every chunk before it is rewound and the document stays as it was. Citation numbers and the References list restart in each compose call. Optional template (report|memo|blank) is accepted on the first compose only and requires the open document to still hold that template seed, otherwise composition fails before any write (to append to an existing document omit template; for a fresh templated document call new_document first, then compose_document with template), and placement is append after existing content except for an unchanged built-in template created in this session. Reopened documents and external templates keep existing content. Returns {ok:true,opCount,appliedCount,revision,createdNodeIds,results,validation:{valid,errorCount,errors}} where createdNodeIds lists new node ids in document order (use them as anchors without re-inspecting) and validation proves the post-compose package state via a read-only OOXML check (save_document re-gates before writing). A compose that drops a non-empty redo stack signals it (redoCleared:true + redoClearedWarning naming the depth): a fresh edit invalidates redo. Optional styleGuide (bundled guide name or guide .md path, jailed like image refs: explicit allowedDir else the session anchor; remote URLs fail closed) replaces the session guide (not a one-batch override: later batches inherit it until another compose replaces it) and skins this compose (table/header-fill, chart-palette, callout, and code defaults apply where ops omit params; explicit params still win). Guides cannot be detached: open a new session to work without a guide. With no guide, compose uses the built-in Default Corporate look (navy table header, bold white header text, striped rows). styleGuide "legacy" gives the older grey header for this call only and is not stored on the session. An italic-only line directly under the opening # title becomes the Subtitle; a [!NOTE]-style line outside a blockquote stays plain text and comes back in warnings. Later batches inherit it, inspect_document shows its compact summary, save_document reports advisory styleWarnings. Unknown names fail with E_VALIDATION naming the available set. Math: $$ ... $$ or \\[ ... \\] blocks become display equations and \\(...\\) becomes inline math (one line, up to 500 characters; refused inside links and table cells). Display blocks are top level only (inside a list item or alert body they fail closed). Optional dollarMath (default false) treats $...$ as inline math; off by default because prices and shell text can look like math. Workflow: open_document -> compose_document -> save_document. Example {"sessionId":"sess_abc","markdown":"# Plan\\n\\n- a\\n- b\\n"} (add "allowedDir":"<dir>" for image/chart refs, "template":"report|memo|blank" to verify the seed). IDs are session-scoped: valid only in the issuing session, must not be stored across sessions. When dc:title is still a template placeholder it is set from the first Title or Heading 1 (titleSetFrom says so); a title already set is kept. persistentIds maps each created paragraph id (md_0003) to the paragraph id the saved file carries, so a reopened session can be addressed with it (tables are renumbered t_001.. by position on reopen).';
|
|
43697
44038
|
function persistentParagraphIds(ast, createdIds) {
|
|
43698
44039
|
const wanted = new Set(createdIds);
|
|
43699
44040
|
const out = {};
|
|
@@ -45666,7 +46007,7 @@ async function handleExportDocument(store, rawArgs) {
|
|
|
45666
46007
|
}
|
|
45667
46008
|
let validatedPdfPath;
|
|
45668
46009
|
if (format === "pdf") {
|
|
45669
|
-
if (!
|
|
46010
|
+
if (!hasPdfBackend2()) {
|
|
45670
46011
|
return {
|
|
45671
46012
|
ok: false,
|
|
45672
46013
|
code: "E_DEPENDENCY",
|
|
@@ -46392,7 +46733,8 @@ async function handleGetStats(store, input) {
|
|
|
46392
46733
|
tables: stats.tables,
|
|
46393
46734
|
callouts: stats.callouts,
|
|
46394
46735
|
images: stats.images,
|
|
46395
|
-
pages: stats.pages
|
|
46736
|
+
pages: stats.pages,
|
|
46737
|
+
pageTerms: roundedPageTerms(stats.pageTerms)
|
|
46396
46738
|
};
|
|
46397
46739
|
});
|
|
46398
46740
|
} catch (err) {
|
|
@@ -46714,7 +47056,7 @@ var import_zod9 = require("zod");
|
|
|
46714
47056
|
var package_default = {
|
|
46715
47057
|
name: "@attentionform/astra-mcp-server",
|
|
46716
47058
|
mcpName: "com.attentionform/astra-mcp-server",
|
|
46717
|
-
version: "0.5.
|
|
47059
|
+
version: "0.5.1",
|
|
46718
47060
|
description: "Official Model Context Protocol (MCP) server for ASTra document IDE",
|
|
46719
47061
|
main: "dist/index.js",
|
|
46720
47062
|
types: "dist/index.d.ts",
|
|
@@ -46755,8 +47097,8 @@ var package_default = {
|
|
|
46755
47097
|
"adm-zip-gate": "node ../../scripts/check-adm-zip-imports.mjs"
|
|
46756
47098
|
},
|
|
46757
47099
|
dependencies: {
|
|
46758
|
-
"@attentionform/astra-engine": "^0.5.
|
|
46759
|
-
"@attentionform/ooxml-validator": "^0.5.
|
|
47100
|
+
"@attentionform/astra-engine": "^0.5.1",
|
|
47101
|
+
"@attentionform/ooxml-validator": "^0.5.1",
|
|
46760
47102
|
"@modelcontextprotocol/sdk": "^1.32.2",
|
|
46761
47103
|
"@xmldom/xmldom": "0.8.15",
|
|
46762
47104
|
"adm-zip": "0.6.1",
|