@lotics/cli 0.152.0 → 0.152.2
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/src/cli.js +254 -8
- package/dist/src/client.js +26 -2
- package/docs/knowledge_docs.md +11 -5
- package/package.json +1 -1
package/dist/src/cli.js
CHANGED
|
@@ -44248,6 +44248,17 @@ function sessionId(now = Date.now(), env = process.env) {
|
|
|
44248
44248
|
}
|
|
44249
44249
|
|
|
44250
44250
|
// src/client.ts
|
|
44251
|
+
function parseContentDispositionFilename(disposition) {
|
|
44252
|
+
const extended = disposition.match(/filename\*=\s*([^']*)'[^']*'([^;\n]+)/i);
|
|
44253
|
+
if (extended) {
|
|
44254
|
+
try {
|
|
44255
|
+
return decodeURIComponent(extended[2].trim());
|
|
44256
|
+
} catch {
|
|
44257
|
+
}
|
|
44258
|
+
}
|
|
44259
|
+
const plain = disposition.match(/filename=\s*"?([^";\n]+)"?/i);
|
|
44260
|
+
return plain ? plain[1].trim() : null;
|
|
44261
|
+
}
|
|
44251
44262
|
function findAvailableFilename(dir, filename, reserved) {
|
|
44252
44263
|
const isTaken = (name2) => {
|
|
44253
44264
|
const full = path2.join(dir, name2);
|
|
@@ -45115,8 +45126,7 @@ var LoticsClient = class {
|
|
|
45115
45126
|
const response = await fetch(url2);
|
|
45116
45127
|
if (!response.ok) await this.throwResponseError(response);
|
|
45117
45128
|
const disposition = response.headers.get("content-disposition") ?? "";
|
|
45118
|
-
const
|
|
45119
|
-
const originalFilename = match2?.[1] ?? fileId;
|
|
45129
|
+
const originalFilename = parseContentDispositionFilename(disposition) ?? fileId;
|
|
45120
45130
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
45121
45131
|
const dir = outputDir ? path2.resolve(outputDir) : process.cwd();
|
|
45122
45132
|
await fs2.promises.mkdir(dir, { recursive: true });
|
|
@@ -97544,6 +97554,9 @@ function readChpx(grpprl) {
|
|
|
97544
97554
|
case Sprm.CIstd:
|
|
97545
97555
|
chp.charStyleIstd = s.operand.u16(0);
|
|
97546
97556
|
break;
|
|
97557
|
+
case Sprm.CPicLocation:
|
|
97558
|
+
chp.picLocation = s.operand.u32(0);
|
|
97559
|
+
break;
|
|
97547
97560
|
default:
|
|
97548
97561
|
break;
|
|
97549
97562
|
}
|
|
@@ -98494,9 +98507,93 @@ function applyCellPadding(operand, current) {
|
|
|
98494
98507
|
return margins;
|
|
98495
98508
|
}
|
|
98496
98509
|
|
|
98510
|
+
// ../ooxml/src/doc/pictures.ts
|
|
98511
|
+
var EMU_PER_TWIP = 635;
|
|
98512
|
+
var PICF_MIN_HEADER = 68;
|
|
98513
|
+
var PICMID_OFFSET = 28;
|
|
98514
|
+
var MM_SHAPE = 100;
|
|
98515
|
+
var BLIP_TYPES = {
|
|
98516
|
+
61466: { mediaType: "image/x-emf", skipByInstance: { 980: 50, 981: 66 }, extractable: false },
|
|
98517
|
+
61467: { mediaType: "image/x-wmf", skipByInstance: { 534: 50, 535: 66 }, extractable: false },
|
|
98518
|
+
61468: { mediaType: "image/pict", skipByInstance: { 1346: 50, 1347: 66 }, extractable: false },
|
|
98519
|
+
61469: { mediaType: "image/jpeg", skipByInstance: { 1130: 17, 1131: 33 }, extractable: true },
|
|
98520
|
+
61470: { mediaType: "image/png", skipByInstance: { 1760: 17, 1761: 33 }, extractable: true },
|
|
98521
|
+
61471: { mediaType: "image/bmp", skipByInstance: { 1960: 17, 1961: 33 }, extractable: false },
|
|
98522
|
+
61481: { mediaType: "image/tiff", skipByInstance: { 1764: 17, 1765: 33 }, extractable: true },
|
|
98523
|
+
61482: { mediaType: "image/jpeg", skipByInstance: { 1130: 17, 1131: 33 }, extractable: true }
|
|
98524
|
+
};
|
|
98525
|
+
function pictureExtension(mediaType) {
|
|
98526
|
+
switch (mediaType) {
|
|
98527
|
+
case "image/png":
|
|
98528
|
+
return "png";
|
|
98529
|
+
case "image/jpeg":
|
|
98530
|
+
return "jpeg";
|
|
98531
|
+
case "image/tiff":
|
|
98532
|
+
return "tiff";
|
|
98533
|
+
default:
|
|
98534
|
+
throw new Error(`No part extension for media type "${mediaType}"; it should not have been extracted`);
|
|
98535
|
+
}
|
|
98536
|
+
}
|
|
98537
|
+
function readInlinePicture(data2, fc) {
|
|
98538
|
+
if (fc < 0 || fc + PICF_MIN_HEADER > data2.length) return null;
|
|
98539
|
+
const lcb = data2.u32(fc);
|
|
98540
|
+
const cbHeader = data2.u16(fc + 4);
|
|
98541
|
+
const mm = data2.u16(fc + 6);
|
|
98542
|
+
if (cbHeader < PICF_MIN_HEADER || lcb <= cbHeader) return null;
|
|
98543
|
+
if (fc + lcb > data2.length) return null;
|
|
98544
|
+
const picmid = fc + PICMID_OFFSET;
|
|
98545
|
+
const dxaGoal = data2.u16(picmid);
|
|
98546
|
+
const dyaGoal = data2.u16(picmid + 2);
|
|
98547
|
+
const mx = data2.u16(picmid + 4) || 1e3;
|
|
98548
|
+
const my = data2.u16(picmid + 6) || 1e3;
|
|
98549
|
+
if (mm < MM_SHAPE) return null;
|
|
98550
|
+
const found = findBlip(data2.bytes.subarray(fc + cbHeader, fc + lcb));
|
|
98551
|
+
if (!found) return null;
|
|
98552
|
+
const widthEmu = Math.round(dxaGoal * EMU_PER_TWIP * mx / 1e3);
|
|
98553
|
+
const heightEmu = Math.round(dyaGoal * EMU_PER_TWIP * my / 1e3);
|
|
98554
|
+
if (widthEmu <= 0 || heightEmu <= 0) return null;
|
|
98555
|
+
return { bytes: found.bytes, mediaType: found.mediaType, widthEmu, heightEmu };
|
|
98556
|
+
}
|
|
98557
|
+
var BSE_RECORD = 61447;
|
|
98558
|
+
var BSE_HEADER = 36;
|
|
98559
|
+
var BSE_CB_NAME = 33;
|
|
98560
|
+
function findBlip(payload) {
|
|
98561
|
+
const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);
|
|
98562
|
+
let offset = 0;
|
|
98563
|
+
while (offset + 8 <= payload.length) {
|
|
98564
|
+
const verInstance = view.getUint16(offset, true);
|
|
98565
|
+
const recType = view.getUint16(offset + 2, true);
|
|
98566
|
+
const length2 = view.getUint32(offset + 4, true);
|
|
98567
|
+
const body = offset + 8;
|
|
98568
|
+
const spec = BLIP_TYPES[recType];
|
|
98569
|
+
if (spec) {
|
|
98570
|
+
if (!spec.extractable) return null;
|
|
98571
|
+
if (length2 > payload.length - body) return null;
|
|
98572
|
+
const instance = verInstance >> 4;
|
|
98573
|
+
const skip = spec.skipByInstance[instance];
|
|
98574
|
+
if (skip === void 0 || skip > length2) return null;
|
|
98575
|
+
return { bytes: payload.subarray(body + skip, body + length2), mediaType: spec.mediaType };
|
|
98576
|
+
}
|
|
98577
|
+
if (recType === BSE_RECORD) {
|
|
98578
|
+
if (body + BSE_HEADER > payload.length) return null;
|
|
98579
|
+
offset = body + BSE_HEADER + view.getUint8(body + BSE_CB_NAME);
|
|
98580
|
+
continue;
|
|
98581
|
+
}
|
|
98582
|
+
if ((verInstance & 15) === 15) {
|
|
98583
|
+
offset = body;
|
|
98584
|
+
continue;
|
|
98585
|
+
}
|
|
98586
|
+
if (length2 > payload.length - body) return null;
|
|
98587
|
+
offset = body + length2;
|
|
98588
|
+
}
|
|
98589
|
+
return null;
|
|
98590
|
+
}
|
|
98591
|
+
|
|
98497
98592
|
// ../ooxml/src/doc/parse.ts
|
|
98498
98593
|
var FIB_MAGIC2 = 42476;
|
|
98499
98594
|
var CH = {
|
|
98595
|
+
picture: 1,
|
|
98596
|
+
// inline picture anchor; its CHPX locates the bytes in Data
|
|
98500
98597
|
refMark: 2,
|
|
98501
98598
|
// footnote/endnote auto-number, inside a note subdocument
|
|
98502
98599
|
cellMark: 7,
|
|
@@ -98522,6 +98619,8 @@ function parseDocToModel(bytes) {
|
|
|
98522
98619
|
if (!tableBytes) throw new DocParseError("Not a legacy .doc: missing Table stream");
|
|
98523
98620
|
const table = new Reader(tableBytes);
|
|
98524
98621
|
const diagnostics = [];
|
|
98622
|
+
const dataBytes = findStream(streams, "Data");
|
|
98623
|
+
const pictures = new PictureCollector();
|
|
98525
98624
|
const clx = readerFor(table, fib.fcLcb(FcLcbIndex.Clx));
|
|
98526
98625
|
if (!clx) throw new DocParseError("Not a legacy .doc: empty Clx");
|
|
98527
98626
|
const pieces = PieceTable.parse(wd, clx);
|
|
@@ -98546,6 +98645,8 @@ function parseDocToModel(bytes) {
|
|
|
98546
98645
|
stsh,
|
|
98547
98646
|
fonts,
|
|
98548
98647
|
footnoteRefs: new Map(footnotes.refs.map((r) => [r.cp, r.index])),
|
|
98648
|
+
data: dataBytes ? new Reader(dataBytes) : null,
|
|
98649
|
+
pictures,
|
|
98549
98650
|
diagnostics
|
|
98550
98651
|
};
|
|
98551
98652
|
const bodyParas = parseParagraphs(ctx, 0, fib.ccpText, numbering);
|
|
@@ -98568,9 +98669,32 @@ function parseDocToModel(bytes) {
|
|
|
98568
98669
|
section: sectionResult.section,
|
|
98569
98670
|
settings,
|
|
98570
98671
|
bookmarks,
|
|
98672
|
+
pictures: pictures.list,
|
|
98571
98673
|
diagnostics
|
|
98572
98674
|
};
|
|
98573
98675
|
}
|
|
98676
|
+
var PictureCollector = class {
|
|
98677
|
+
list = [];
|
|
98678
|
+
indexByKey = /* @__PURE__ */ new Map();
|
|
98679
|
+
/** Index of this picture in `list`, adding it on first sight. */
|
|
98680
|
+
intern(picture) {
|
|
98681
|
+
const key = `${picture.mediaType}:${picture.bytes.length}:${fnv1a(picture.bytes)}`;
|
|
98682
|
+
const existing = this.indexByKey.get(key);
|
|
98683
|
+
if (existing !== void 0) return existing;
|
|
98684
|
+
const index = this.list.length;
|
|
98685
|
+
this.list.push({ bytes: picture.bytes, mediaType: picture.mediaType });
|
|
98686
|
+
this.indexByKey.set(key, index);
|
|
98687
|
+
return index;
|
|
98688
|
+
}
|
|
98689
|
+
};
|
|
98690
|
+
function fnv1a(bytes) {
|
|
98691
|
+
let hash2 = 2166136261;
|
|
98692
|
+
for (let i2 = 0; i2 < bytes.length; i2++) {
|
|
98693
|
+
hash2 ^= bytes[i2];
|
|
98694
|
+
hash2 = Math.imul(hash2, 16777619) >>> 0;
|
|
98695
|
+
}
|
|
98696
|
+
return hash2.toString(16);
|
|
98697
|
+
}
|
|
98574
98698
|
function parseParagraphs(ctx, startCp, endCp, numbering) {
|
|
98575
98699
|
const paras = [];
|
|
98576
98700
|
let paraStart = startCp;
|
|
@@ -98658,6 +98782,20 @@ function parseParagraphs(ctx, startCp, endCp, numbering) {
|
|
|
98658
98782
|
text += " ";
|
|
98659
98783
|
continue;
|
|
98660
98784
|
}
|
|
98785
|
+
if (code === CH.picture) {
|
|
98786
|
+
const picture = readPictureAt(ctx, currentRaw, cp);
|
|
98787
|
+
if (picture !== null) {
|
|
98788
|
+
const index = ctx.pictures.intern(picture);
|
|
98789
|
+
pushPart(cp, currentRaw, (chp) => ({
|
|
98790
|
+
kind: "picture",
|
|
98791
|
+
pictureIndex: index,
|
|
98792
|
+
widthEmu: picture.widthEmu,
|
|
98793
|
+
heightEmu: picture.heightEmu,
|
|
98794
|
+
chp
|
|
98795
|
+
}));
|
|
98796
|
+
}
|
|
98797
|
+
continue;
|
|
98798
|
+
}
|
|
98661
98799
|
if (code < 32) {
|
|
98662
98800
|
noteControlChar(ctx, cp, code);
|
|
98663
98801
|
continue;
|
|
@@ -98698,9 +98836,22 @@ function finalizeParagraph(ctx, parts, startCp, markCp, isCellMark, numbering) {
|
|
|
98698
98836
|
runCps
|
|
98699
98837
|
};
|
|
98700
98838
|
}
|
|
98839
|
+
function readPictureAt(ctx, raw, cp) {
|
|
98840
|
+
const note = (detail) => {
|
|
98841
|
+
ctx.diagnostics.push({ kind: "image", location: { cp }, detail });
|
|
98842
|
+
return null;
|
|
98843
|
+
};
|
|
98844
|
+
if (raw.picLocation === void 0) return note("Picture anchor carries no Data-stream location; dropped");
|
|
98845
|
+
if (!ctx.data) return note("Picture anchor found but the document has no Data stream; dropped");
|
|
98846
|
+
const picture = readInlinePicture(ctx.data, raw.picLocation);
|
|
98847
|
+
if (!picture) {
|
|
98848
|
+
return note(`Picture at Data+${raw.picLocation} is not a readable inline image; dropped`);
|
|
98849
|
+
}
|
|
98850
|
+
return picture;
|
|
98851
|
+
}
|
|
98701
98852
|
function noteControlChar(ctx, cp, code) {
|
|
98702
|
-
if (code ===
|
|
98703
|
-
ctx.diagnostics.push({ kind: "
|
|
98853
|
+
if (code === 8) {
|
|
98854
|
+
ctx.diagnostics.push({ kind: "drawingObject", location: { cp }, detail: "Floating drawing object dropped" });
|
|
98704
98855
|
} else if (code === 5) {
|
|
98705
98856
|
ctx.diagnostics.push({ kind: "annotation", location: { cp }, detail: "Annotation reference dropped" });
|
|
98706
98857
|
}
|
|
@@ -100972,8 +101123,66 @@ function textRunContent(text) {
|
|
|
100972
101123
|
}
|
|
100973
101124
|
return out.length > 0 ? out : [textElement("")];
|
|
100974
101125
|
}
|
|
100975
|
-
function
|
|
101126
|
+
function pictureContent(run, ctx) {
|
|
101127
|
+
const relId = ctx.pictureRelIds[run.pictureIndex];
|
|
101128
|
+
if (relId === void 0) {
|
|
101129
|
+
throw new Error(
|
|
101130
|
+
`Picture ${run.pictureIndex} has no relationship; the emitter must register every picture before building runs`
|
|
101131
|
+
);
|
|
101132
|
+
}
|
|
101133
|
+
const name2 = `Picture ${run.pictureIndex + 1}`;
|
|
101134
|
+
const extent = { "@_cx": num2(run.widthEmu), "@_cy": num2(run.heightEmu) };
|
|
101135
|
+
const blipChildren = [
|
|
101136
|
+
leaf("a:blip", { "@_r:embed": relId }),
|
|
101137
|
+
{ "a:stretch": [{ "a:fillRect": [] }] }
|
|
101138
|
+
];
|
|
101139
|
+
return {
|
|
101140
|
+
"w:drawing": [
|
|
101141
|
+
{
|
|
101142
|
+
"wp:inline": [
|
|
101143
|
+
leaf("wp:extent", extent),
|
|
101144
|
+
leaf("wp:docPr", { "@_id": num2(run.pictureIndex + 1), "@_name": name2 }),
|
|
101145
|
+
{
|
|
101146
|
+
"a:graphic": [
|
|
101147
|
+
{
|
|
101148
|
+
"a:graphicData": [
|
|
101149
|
+
{
|
|
101150
|
+
"pic:pic": [
|
|
101151
|
+
{
|
|
101152
|
+
"pic:nvPicPr": [
|
|
101153
|
+
leaf("pic:cNvPr", { "@_id": num2(run.pictureIndex + 1), "@_name": name2 }),
|
|
101154
|
+
{ "pic:cNvPicPr": [] }
|
|
101155
|
+
]
|
|
101156
|
+
},
|
|
101157
|
+
{ "pic:blipFill": blipChildren },
|
|
101158
|
+
{
|
|
101159
|
+
"pic:spPr": [
|
|
101160
|
+
{
|
|
101161
|
+
"a:xfrm": [
|
|
101162
|
+
leaf("a:off", { "@_x": "0", "@_y": "0" }),
|
|
101163
|
+
leaf("a:ext", extent)
|
|
101164
|
+
]
|
|
101165
|
+
},
|
|
101166
|
+
{ "a:prstGeom": [{ "a:avLst": [] }], ":@": { "@_prst": "rect" } }
|
|
101167
|
+
]
|
|
101168
|
+
}
|
|
101169
|
+
]
|
|
101170
|
+
}
|
|
101171
|
+
],
|
|
101172
|
+
":@": { "@_uri": "http://schemas.openxmlformats.org/drawingml/2006/picture" }
|
|
101173
|
+
}
|
|
101174
|
+
]
|
|
101175
|
+
}
|
|
101176
|
+
],
|
|
101177
|
+
":@": { "@_distT": "0", "@_distB": "0", "@_distL": "0", "@_distR": "0" }
|
|
101178
|
+
}
|
|
101179
|
+
]
|
|
101180
|
+
};
|
|
101181
|
+
}
|
|
101182
|
+
function specialRunContent(run, ctx) {
|
|
100976
101183
|
switch (run.kind) {
|
|
101184
|
+
case "picture":
|
|
101185
|
+
return pictureContent(run, ctx);
|
|
100977
101186
|
case "break":
|
|
100978
101187
|
return run.breakType === "textWrapping" ? { "w:br": [] } : leaf("w:br", { "@_w:type": run.breakType });
|
|
100979
101188
|
case "footnoteReference":
|
|
@@ -100991,7 +101200,7 @@ function buildRunElement(run, ctx) {
|
|
|
100991
101200
|
const rPr = rPrElement(run.chp, ctx);
|
|
100992
101201
|
if (rPr) children.push(rPr);
|
|
100993
101202
|
if (run.kind === "text") children.push(...textRunContent(run.text));
|
|
100994
|
-
else children.push(specialRunContent(run));
|
|
101203
|
+
else children.push(specialRunContent(run, ctx));
|
|
100995
101204
|
return { "w:r": children };
|
|
100996
101205
|
}
|
|
100997
101206
|
function borderChild(tag, border2) {
|
|
@@ -101292,7 +101501,7 @@ function overrideElement(override) {
|
|
|
101292
101501
|
const loChildren = [];
|
|
101293
101502
|
if (lo.startOverride !== void 0) loChildren.push(leaf3("w:startOverride", { "@_w:val": String(lo.startOverride) }));
|
|
101294
101503
|
if (lo.level) {
|
|
101295
|
-
loChildren.push(levelElement(lo.level, { styleIdByIstd: /* @__PURE__ */ new Map() }));
|
|
101504
|
+
loChildren.push(levelElement(lo.level, { styleIdByIstd: /* @__PURE__ */ new Map(), pictureRelIds: [] }));
|
|
101296
101505
|
}
|
|
101297
101506
|
children.push({ "w:lvlOverride": loChildren, ":@": { "@_w:ilvl": String(lo.ilvl) } });
|
|
101298
101507
|
}
|
|
@@ -101472,14 +101681,28 @@ function buildFontTableXml(fontNames) {
|
|
|
101472
101681
|
const root = { "w:fonts": fonts, ":@": { "@_xmlns:w": W_NS4 } };
|
|
101473
101682
|
return buildXml([root]);
|
|
101474
101683
|
}
|
|
101684
|
+
async function registerPictures(doc, pictures) {
|
|
101685
|
+
if (pictures.length === 0) return [];
|
|
101686
|
+
ensureDrawingNamespaces(doc);
|
|
101687
|
+
const relIds = [];
|
|
101688
|
+
for (let i2 = 0; i2 < pictures.length; i2++) {
|
|
101689
|
+
const extension = pictureExtension(pictures[i2].mediaType);
|
|
101690
|
+
const name2 = `image${i2 + 1}.${extension}`;
|
|
101691
|
+
doc.zip.file(`word/media/${name2}`, pictures[i2].bytes);
|
|
101692
|
+
await ensureContentType(doc, extension, pictures[i2].mediaType);
|
|
101693
|
+
relIds.push(await addRelationship(doc, `${REL}image`, `media/${name2}`));
|
|
101694
|
+
}
|
|
101695
|
+
return relIds;
|
|
101696
|
+
}
|
|
101475
101697
|
async function docModelToDocx(model) {
|
|
101476
101698
|
const styleIdByIstd = /* @__PURE__ */ new Map();
|
|
101477
101699
|
for (const style of model.styles) styleIdByIstd.set(style.istd, style.styleId);
|
|
101478
101700
|
if (!styleIdByIstd.has(0)) styleIdByIstd.set(0, "Normal");
|
|
101479
|
-
const ctx = { styleIdByIstd };
|
|
101480
101701
|
const walked = walkModel(model);
|
|
101481
101702
|
const hasFootnotes = model.footnotes.length > 0;
|
|
101482
101703
|
const doc = createBlankDocx();
|
|
101704
|
+
const pictureRelIds = await registerPictures(doc, model.pictures);
|
|
101705
|
+
const ctx = { styleIdByIstd, pictureRelIds };
|
|
101483
101706
|
doc.bodyElements = [...buildBody(model, ctx), buildSectPr(model.section)];
|
|
101484
101707
|
doc.zip.file("word/styles.xml", buildStylesXml2(model, ctx, walked.referencedStyleIstds));
|
|
101485
101708
|
doc.zip.file("word/settings.xml", buildSettingsXml(model, hasFootnotes));
|
|
@@ -101720,6 +101943,18 @@ async function addRelationship(doc, type, target, targetMode) {
|
|
|
101720
101943
|
doc.zip.file("word/_rels/document.xml.rels", updated);
|
|
101721
101944
|
return rId;
|
|
101722
101945
|
}
|
|
101946
|
+
async function ensureContentType(doc, extension, contentType) {
|
|
101947
|
+
const contentTypesFile = doc.zip.file("[Content_Types].xml");
|
|
101948
|
+
if (!contentTypesFile) return;
|
|
101949
|
+
const ct = await contentTypesFile.async("string");
|
|
101950
|
+
if (ct.includes(`Extension="${extension}"`)) return;
|
|
101951
|
+
const updated = ct.replace(
|
|
101952
|
+
"</Types>",
|
|
101953
|
+
` <Default Extension="${extension}" ContentType="${contentType}"/>
|
|
101954
|
+
</Types>`
|
|
101955
|
+
);
|
|
101956
|
+
doc.zip.file("[Content_Types].xml", updated);
|
|
101957
|
+
}
|
|
101723
101958
|
async function ensureContentTypeOverride(doc, partName, contentType) {
|
|
101724
101959
|
const contentTypesFile = doc.zip.file("[Content_Types].xml");
|
|
101725
101960
|
if (!contentTypesFile) return;
|
|
@@ -101732,6 +101967,17 @@ async function ensureContentTypeOverride(doc, partName, contentType) {
|
|
|
101732
101967
|
);
|
|
101733
101968
|
doc.zip.file("[Content_Types].xml", updated);
|
|
101734
101969
|
}
|
|
101970
|
+
function ensureDrawingNamespaces(doc) {
|
|
101971
|
+
if (!doc.documentAttrs["@_xmlns:a"]) {
|
|
101972
|
+
doc.documentAttrs["@_xmlns:a"] = "http://schemas.openxmlformats.org/drawingml/2006/main";
|
|
101973
|
+
}
|
|
101974
|
+
if (!doc.documentAttrs["@_xmlns:pic"]) {
|
|
101975
|
+
doc.documentAttrs["@_xmlns:pic"] = "http://schemas.openxmlformats.org/drawingml/2006/picture";
|
|
101976
|
+
}
|
|
101977
|
+
if (!doc.documentAttrs["@_xmlns:wp"]) {
|
|
101978
|
+
doc.documentAttrs["@_xmlns:wp"] = "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing";
|
|
101979
|
+
}
|
|
101980
|
+
}
|
|
101735
101981
|
|
|
101736
101982
|
// ../docx/src/parse/parser.ts
|
|
101737
101983
|
var import_jszip3 = __toESM(require_lib4(), 1);
|
package/dist/src/client.js
CHANGED
|
@@ -2,6 +2,31 @@ import { transportErrorMessage } from "@lotics/shared/transport_error";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { getInvocation } from "./invocation.js";
|
|
5
|
+
/**
|
|
6
|
+
* The filename a `Content-Disposition` names, preferring RFC 5987 `filename*`.
|
|
7
|
+
*
|
|
8
|
+
* The server always sends BOTH: `filename*=UTF-8''…` carrying the real name,
|
|
9
|
+
* and a plain `filename=` whose non-ASCII characters are replaced with `_`
|
|
10
|
+
* because HTTP header values are Latin-1. Reading only the plain one turned
|
|
11
|
+
* every accented name into underscores on the way to disk — `GIẤY YÊU CẦU.doc`
|
|
12
|
+
* saved as `GI_Y Y_U C_U.doc` — even though the correct bytes were in the same
|
|
13
|
+
* header. The `*` form wins whenever it parses; the plain form is the fallback
|
|
14
|
+
* it was always meant to be.
|
|
15
|
+
*/
|
|
16
|
+
function parseContentDispositionFilename(disposition) {
|
|
17
|
+
const extended = disposition.match(/filename\*=\s*([^']*)'[^']*'([^;\n]+)/i);
|
|
18
|
+
if (extended) {
|
|
19
|
+
try {
|
|
20
|
+
return decodeURIComponent(extended[2].trim());
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// A malformed percent-escape means this parameter is unusable, not that
|
|
24
|
+
// the whole header is — fall through to the ASCII form below.
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const plain = disposition.match(/filename=\s*"?([^";\n]+)"?/i);
|
|
28
|
+
return plain ? plain[1].trim() : null;
|
|
29
|
+
}
|
|
5
30
|
function findAvailableFilename(dir, filename, reserved) {
|
|
6
31
|
// `reserved` tracks absolute paths claimed by in-flight downloads in the same
|
|
7
32
|
// batch — required for parallel callers because the file may not be on disk
|
|
@@ -824,8 +849,7 @@ export class LoticsClient {
|
|
|
824
849
|
if (!response.ok)
|
|
825
850
|
await this.throwResponseError(response);
|
|
826
851
|
const disposition = response.headers.get("content-disposition") ?? "";
|
|
827
|
-
const
|
|
828
|
-
const originalFilename = match?.[1] ?? fileId;
|
|
852
|
+
const originalFilename = parseContentDispositionFilename(disposition) ?? fileId;
|
|
829
853
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
830
854
|
const dir = outputDir ? path.resolve(outputDir) : process.cwd();
|
|
831
855
|
// The bytes are already fetched by this point, so a missing output directory
|
package/docs/knowledge_docs.md
CHANGED
|
@@ -30,18 +30,24 @@ lotics knowledge create --name "Shipping tariffs" \
|
|
|
30
30
|
|
|
31
31
|
This reads the body from your filesystem and creates the doc through `create_knowledge`
|
|
32
32
|
(prints the new id). A raw `lotics run create_knowledge @doc.json` works too — read a large
|
|
33
|
-
`content` from a file or stdin.
|
|
33
|
+
`content` from a file or stdin. `create_knowledge` takes five fields, three of them required:
|
|
34
34
|
|
|
35
|
-
- `name` — what it is.
|
|
35
|
+
- `name` — what it is. Just the name: **grouping belongs in `tags`, not in a prefix baked into
|
|
36
|
+
every title.**
|
|
36
37
|
- `description` — what the doc is **and what to grep it for**: its vocabulary and the synonyms
|
|
37
38
|
an ambiguous query would use. Surfaced in the tree before any body is read, and truncated at
|
|
38
39
|
200 characters. See *Write for grep* below.
|
|
39
40
|
- `content` — Markdown.
|
|
41
|
+
- `tags` *(optional)* — the labels someone would filter by, e.g. `["Hồ sơ NOXH", "Khoản 13"]`.
|
|
42
|
+
Give **every** one that is true of the doc, not just the main one — a corpus is narrowed by
|
|
43
|
+
intersecting tags, so a doc carrying one label can only ever be found down one path. On
|
|
44
|
+
`update_knowledge` the array **replaces** what is stored, so resend the labels it should keep.
|
|
45
|
+
- `files` *(optional)* — file ids of the source `.docx`/`.pdf`/`.xlsx` the doc was built from,
|
|
46
|
+
stored alongside it so a reader can open the original. The body stays the searchable text.
|
|
40
47
|
|
|
41
48
|
A new doc is created **owned by you, active in your own agent context, and private** — no one
|
|
42
|
-
else can see it yet.
|
|
43
|
-
|
|
44
|
-
tool.)
|
|
49
|
+
else can see it yet. Changing a doc's default-active state is done through the web app / REST
|
|
50
|
+
surface, not this tool.
|
|
45
51
|
|
|
46
52
|
## Access vs. activation — both are required
|
|
47
53
|
|