@lotics/cli 0.96.4 → 0.96.6
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 +284 -13
- package/package.json +1 -1
package/dist/src/cli.js
CHANGED
|
@@ -70166,6 +70166,16 @@ function runNpm(args, cwd) {
|
|
|
70166
70166
|
});
|
|
70167
70167
|
});
|
|
70168
70168
|
}
|
|
70169
|
+
function readAppWorkspaceId(projectDir) {
|
|
70170
|
+
try {
|
|
70171
|
+
const pkgPath2 = path5.join(projectDir, "package.json");
|
|
70172
|
+
if (!fs4.existsSync(pkgPath2)) return null;
|
|
70173
|
+
const pkg2 = JSON.parse(fs4.readFileSync(pkgPath2, "utf-8"));
|
|
70174
|
+
return pkg2.lotics?.workspace_id ?? null;
|
|
70175
|
+
} catch {
|
|
70176
|
+
return null;
|
|
70177
|
+
}
|
|
70178
|
+
}
|
|
70169
70179
|
function readAppMeta(projectDir) {
|
|
70170
70180
|
const pkgPath2 = path5.join(projectDir, "package.json");
|
|
70171
70181
|
if (!fs4.existsSync(pkgPath2)) {
|
|
@@ -90242,6 +90252,170 @@ async function ensureContentTypeOverride(doc, partName, contentType) {
|
|
|
90242
90252
|
doc.zip.file("[Content_Types].xml", updated);
|
|
90243
90253
|
}
|
|
90244
90254
|
|
|
90255
|
+
// ../ooxml/src/queries.ts
|
|
90256
|
+
function getBodyElementType(el) {
|
|
90257
|
+
const tag = getTagName(el);
|
|
90258
|
+
if (tag === "w:p") return "paragraph";
|
|
90259
|
+
if (tag === "w:tbl") return "table";
|
|
90260
|
+
return "other";
|
|
90261
|
+
}
|
|
90262
|
+
function extractParagraphText(el) {
|
|
90263
|
+
const tag = getTagName(el);
|
|
90264
|
+
if (tag !== "w:p") return "";
|
|
90265
|
+
const parts = [];
|
|
90266
|
+
for (const child of getChildren(el)) {
|
|
90267
|
+
const childTag = getTagName(child);
|
|
90268
|
+
if (childTag === "w:r") {
|
|
90269
|
+
for (const runChild of getChildren(child)) {
|
|
90270
|
+
if (getTagName(runChild) === "w:t") {
|
|
90271
|
+
parts.push(getTextContent(runChild));
|
|
90272
|
+
}
|
|
90273
|
+
}
|
|
90274
|
+
} else if (childTag === "w:hyperlink") {
|
|
90275
|
+
for (const hlChild of getChildren(child)) {
|
|
90276
|
+
if (getTagName(hlChild) === "w:r") {
|
|
90277
|
+
for (const runChild of getChildren(hlChild)) {
|
|
90278
|
+
if (getTagName(runChild) === "w:t") {
|
|
90279
|
+
parts.push(getTextContent(runChild));
|
|
90280
|
+
}
|
|
90281
|
+
}
|
|
90282
|
+
}
|
|
90283
|
+
}
|
|
90284
|
+
}
|
|
90285
|
+
}
|
|
90286
|
+
return parts.join("");
|
|
90287
|
+
}
|
|
90288
|
+
function getRunsFromParagraph(el) {
|
|
90289
|
+
const runs = [];
|
|
90290
|
+
for (const child of getChildren(el)) {
|
|
90291
|
+
if (getTagName(child) !== "w:r") continue;
|
|
90292
|
+
let text = "";
|
|
90293
|
+
let textIdx = -1;
|
|
90294
|
+
const children = getChildren(child);
|
|
90295
|
+
for (let i2 = 0; i2 < children.length; i2++) {
|
|
90296
|
+
if (getTagName(children[i2]) === "w:t") {
|
|
90297
|
+
const tChildren = getChildren(children[i2]);
|
|
90298
|
+
for (const tn of tChildren) {
|
|
90299
|
+
if (typeof tn["#text"] === "string") {
|
|
90300
|
+
text += tn["#text"];
|
|
90301
|
+
}
|
|
90302
|
+
}
|
|
90303
|
+
textIdx = i2;
|
|
90304
|
+
}
|
|
90305
|
+
}
|
|
90306
|
+
if (textIdx >= 0) {
|
|
90307
|
+
runs.push({ element: child, text, textNodeIndex: textIdx });
|
|
90308
|
+
}
|
|
90309
|
+
}
|
|
90310
|
+
return runs;
|
|
90311
|
+
}
|
|
90312
|
+
function setRunText(run, newText) {
|
|
90313
|
+
const children = getChildren(run.element);
|
|
90314
|
+
children[run.textNodeIndex] = {
|
|
90315
|
+
"w:t": [{ "#text": newText }],
|
|
90316
|
+
":@": { "@_xml:space": "preserve" }
|
|
90317
|
+
};
|
|
90318
|
+
run.text = newText;
|
|
90319
|
+
}
|
|
90320
|
+
function computeReplacementRanges(text, query, replacement, matchType, maxReplacements, startCount) {
|
|
90321
|
+
const ranges = [];
|
|
90322
|
+
let count = startCount;
|
|
90323
|
+
if (matchType === "exact") {
|
|
90324
|
+
if (text === query && count < maxReplacements) {
|
|
90325
|
+
ranges.push({ start: 0, end: text.length, replacement });
|
|
90326
|
+
count++;
|
|
90327
|
+
}
|
|
90328
|
+
return { ranges, count };
|
|
90329
|
+
}
|
|
90330
|
+
if (matchType === "regex") {
|
|
90331
|
+
const re2 = new RegExp(query, "gi");
|
|
90332
|
+
let match2;
|
|
90333
|
+
while (count < maxReplacements && (match2 = re2.exec(text)) !== null) {
|
|
90334
|
+
ranges.push({ start: match2.index, end: match2.index + match2[0].length, replacement });
|
|
90335
|
+
count++;
|
|
90336
|
+
if (match2[0].length === 0) re2.lastIndex++;
|
|
90337
|
+
}
|
|
90338
|
+
return { ranges, count };
|
|
90339
|
+
}
|
|
90340
|
+
if (query.length === 0) return { ranges, count };
|
|
90341
|
+
const lower2 = text.toLowerCase();
|
|
90342
|
+
const lowerQuery = query.toLowerCase();
|
|
90343
|
+
let searchFrom = 0;
|
|
90344
|
+
while (count < maxReplacements) {
|
|
90345
|
+
const idx = lower2.indexOf(lowerQuery, searchFrom);
|
|
90346
|
+
if (idx === -1) break;
|
|
90347
|
+
ranges.push({ start: idx, end: idx + query.length, replacement });
|
|
90348
|
+
searchFrom = idx + query.length;
|
|
90349
|
+
count++;
|
|
90350
|
+
}
|
|
90351
|
+
return { ranges, count };
|
|
90352
|
+
}
|
|
90353
|
+
function applyRangeReplacements(runs, ranges) {
|
|
90354
|
+
if (ranges.length === 0) return;
|
|
90355
|
+
const bounds = [];
|
|
90356
|
+
let offset = 0;
|
|
90357
|
+
for (const run of runs) {
|
|
90358
|
+
bounds.push({ run, start: offset, end: offset + run.text.length });
|
|
90359
|
+
offset += run.text.length;
|
|
90360
|
+
}
|
|
90361
|
+
const joined = runs.map((r) => r.text).join("");
|
|
90362
|
+
const sorted = [...ranges].sort((a, b) => a.start - b.start);
|
|
90363
|
+
for (const { run, start: runStart, end: runEnd } of bounds) {
|
|
90364
|
+
let next = "";
|
|
90365
|
+
let cursor = runStart;
|
|
90366
|
+
for (const range2 of sorted) {
|
|
90367
|
+
if (range2.end <= runStart || range2.start >= runEnd) continue;
|
|
90368
|
+
const keepUntil = Math.min(range2.start, runEnd);
|
|
90369
|
+
const from = Math.max(cursor, runStart);
|
|
90370
|
+
if (keepUntil > from) next += joined.slice(from, keepUntil);
|
|
90371
|
+
if (runStart <= range2.start && range2.start < runEnd) next += range2.replacement;
|
|
90372
|
+
cursor = Math.max(cursor, range2.end);
|
|
90373
|
+
}
|
|
90374
|
+
const tail = Math.max(cursor, runStart);
|
|
90375
|
+
if (runEnd > tail) next += joined.slice(tail, runEnd);
|
|
90376
|
+
if (next !== run.text) setRunText(run, next);
|
|
90377
|
+
}
|
|
90378
|
+
}
|
|
90379
|
+
function replaceInParagraph(el, query, replacement, matchType, maxReplacements, replacementsMade) {
|
|
90380
|
+
if (!extractParagraphText(el)) return replacementsMade;
|
|
90381
|
+
const runs = getRunsFromParagraph(el);
|
|
90382
|
+
if (runs.length === 0) return replacementsMade;
|
|
90383
|
+
const text = runs.length === 1 ? runs[0].text : runs.map((r) => r.text).join("");
|
|
90384
|
+
const { ranges, count } = computeReplacementRanges(text, query, replacement, matchType, maxReplacements, replacementsMade);
|
|
90385
|
+
applyRangeReplacements(runs, ranges);
|
|
90386
|
+
return count;
|
|
90387
|
+
}
|
|
90388
|
+
function replaceText(bodyElements, query, replacement, matchType = "contains", maxReplacements = Infinity) {
|
|
90389
|
+
if (matchType === "regex") {
|
|
90390
|
+
try {
|
|
90391
|
+
new RegExp(query);
|
|
90392
|
+
} catch {
|
|
90393
|
+
throw new Error(`Invalid regex pattern: ${query}`);
|
|
90394
|
+
}
|
|
90395
|
+
}
|
|
90396
|
+
let replacementsMade = 0;
|
|
90397
|
+
for (let i2 = 0; i2 < bodyElements.length && replacementsMade < maxReplacements; i2++) {
|
|
90398
|
+
const el = bodyElements[i2];
|
|
90399
|
+
const type = getBodyElementType(el);
|
|
90400
|
+
if (type === "paragraph") {
|
|
90401
|
+
replacementsMade = replaceInParagraph(el, query, replacement, matchType, maxReplacements, replacementsMade);
|
|
90402
|
+
} else if (type === "table") {
|
|
90403
|
+
for (const child of getChildren(el)) {
|
|
90404
|
+
if (getTagName(child) !== "w:tr") continue;
|
|
90405
|
+
for (const cell of getChildren(child)) {
|
|
90406
|
+
if (getTagName(cell) !== "w:tc") continue;
|
|
90407
|
+
for (const p of getChildren(cell)) {
|
|
90408
|
+
if (getTagName(p) === "w:p" && replacementsMade < maxReplacements) {
|
|
90409
|
+
replacementsMade = replaceInParagraph(p, query, replacement, matchType, maxReplacements, replacementsMade);
|
|
90410
|
+
}
|
|
90411
|
+
}
|
|
90412
|
+
}
|
|
90413
|
+
}
|
|
90414
|
+
}
|
|
90415
|
+
}
|
|
90416
|
+
return replacementsMade;
|
|
90417
|
+
}
|
|
90418
|
+
|
|
90245
90419
|
// ../docx/src/parse/parser.ts
|
|
90246
90420
|
var import_jszip2 = __toESM(require_lib4(), 1);
|
|
90247
90421
|
async function parseDocx(buffer) {
|
|
@@ -90768,18 +90942,102 @@ async function docxReplaceText(filePath, rest) {
|
|
|
90768
90942
|
fail2("Usage: lotics docx replace-text <file> '<search>' '<replace>'");
|
|
90769
90943
|
}
|
|
90770
90944
|
const doc = await loadFile2(filePath);
|
|
90771
|
-
const
|
|
90772
|
-
|
|
90945
|
+
const { children, count } = replaceEverywhere(doc.body.children, search, replace2);
|
|
90946
|
+
if (count === 0) {
|
|
90947
|
+
fail2(`No occurrence of ${JSON.stringify(search)} in ${filePath} \u2014 nothing written.`);
|
|
90948
|
+
}
|
|
90949
|
+
await writeDoc(filePath, replaceChildren(doc, children));
|
|
90950
|
+
console.error(`Wrote ${filePath} (${count} replacement${count === 1 ? "" : "s"})`);
|
|
90951
|
+
}
|
|
90952
|
+
function replaceEverywhere(blocks, search, replace2) {
|
|
90953
|
+
let count = 0;
|
|
90954
|
+
const children = blocks.map((b) => {
|
|
90955
|
+
if (b.kind === "paragraph") {
|
|
90956
|
+
const [paragraph, n] = replaceInParagraph2(b, search, replace2);
|
|
90957
|
+
count += n;
|
|
90958
|
+
return paragraph;
|
|
90959
|
+
}
|
|
90960
|
+
if (b.kind === "opaque_block") {
|
|
90961
|
+
const xml = structuredClone(b.xml);
|
|
90962
|
+
const n = replaceText([xml], search, replace2, "contains");
|
|
90963
|
+
if (n === 0) return b;
|
|
90964
|
+
count += n;
|
|
90965
|
+
return { kind: "opaque_block", xml };
|
|
90966
|
+
}
|
|
90967
|
+
return b;
|
|
90968
|
+
});
|
|
90969
|
+
return { children, count };
|
|
90773
90970
|
}
|
|
90774
|
-
function
|
|
90775
|
-
|
|
90971
|
+
function replaceInParagraph2(p, search, replace2) {
|
|
90972
|
+
if (search === "") return [p, 0];
|
|
90973
|
+
const rewritten = /* @__PURE__ */ new Map();
|
|
90974
|
+
let count = 0;
|
|
90975
|
+
for (const segment of collectTextSegments(p)) {
|
|
90976
|
+
const joined = segment.map((s) => s.value).join("");
|
|
90977
|
+
const matches = [];
|
|
90978
|
+
for (let at2 = joined.indexOf(search); at2 !== -1; at2 = joined.indexOf(search, at2 + search.length)) {
|
|
90979
|
+
matches.push({ start: at2, end: at2 + search.length });
|
|
90980
|
+
}
|
|
90981
|
+
if (matches.length === 0) continue;
|
|
90982
|
+
count += matches.length;
|
|
90983
|
+
for (const slot of segment) {
|
|
90984
|
+
let out = "";
|
|
90985
|
+
let cursor = slot.start;
|
|
90986
|
+
for (const m of matches) {
|
|
90987
|
+
if (m.end <= slot.start || m.start >= slot.end) continue;
|
|
90988
|
+
const keepUntil = Math.max(cursor, Math.min(m.start, slot.end));
|
|
90989
|
+
if (keepUntil > cursor) out += joined.slice(cursor, keepUntil);
|
|
90990
|
+
if (m.start >= slot.start && m.start < slot.end) out += replace2;
|
|
90991
|
+
cursor = Math.max(cursor, Math.min(m.end, slot.end));
|
|
90992
|
+
}
|
|
90993
|
+
if (cursor < slot.end) out += joined.slice(cursor, slot.end);
|
|
90994
|
+
rewritten.set(`${slot.inlineIndex}:${slot.childIndex}`, out);
|
|
90995
|
+
}
|
|
90996
|
+
}
|
|
90997
|
+
if (count === 0) return [p, 0];
|
|
90998
|
+
const content = p.content.map((inline, inlineIndex) => {
|
|
90776
90999
|
if (inline.kind !== "run") return inline;
|
|
90777
|
-
const
|
|
90778
|
-
(c
|
|
90779
|
-
|
|
90780
|
-
|
|
91000
|
+
const children = inline.content.map((c, childIndex) => {
|
|
91001
|
+
if (c.kind !== "text") return c;
|
|
91002
|
+
const next = rewritten.get(`${inlineIndex}:${childIndex}`);
|
|
91003
|
+
if (next === void 0 || next === c.value) return c;
|
|
91004
|
+
return { kind: "text", value: next, preserveSpace: next.startsWith(" ") || next.endsWith(" ") };
|
|
91005
|
+
});
|
|
91006
|
+
return { kind: "run", properties: inline.properties, content: children };
|
|
90781
91007
|
});
|
|
90782
|
-
return { kind: "paragraph", properties: p.properties, content };
|
|
91008
|
+
return [{ kind: "paragraph", properties: p.properties, content }, count];
|
|
91009
|
+
}
|
|
91010
|
+
function collectTextSegments(p) {
|
|
91011
|
+
const segments = [];
|
|
91012
|
+
let current = [];
|
|
91013
|
+
let offset = 0;
|
|
91014
|
+
const breakSegment = () => {
|
|
91015
|
+
if (current.length > 0) segments.push(current);
|
|
91016
|
+
current = [];
|
|
91017
|
+
offset = 0;
|
|
91018
|
+
};
|
|
91019
|
+
p.content.forEach((inline, inlineIndex) => {
|
|
91020
|
+
if (inline.kind !== "run") {
|
|
91021
|
+
breakSegment();
|
|
91022
|
+
return;
|
|
91023
|
+
}
|
|
91024
|
+
inline.content.forEach((child, childIndex) => {
|
|
91025
|
+
if (child.kind !== "text") {
|
|
91026
|
+
breakSegment();
|
|
91027
|
+
return;
|
|
91028
|
+
}
|
|
91029
|
+
current.push({
|
|
91030
|
+
inlineIndex,
|
|
91031
|
+
childIndex,
|
|
91032
|
+
value: child.value,
|
|
91033
|
+
start: offset,
|
|
91034
|
+
end: offset + child.value.length
|
|
91035
|
+
});
|
|
91036
|
+
offset += child.value.length;
|
|
91037
|
+
});
|
|
91038
|
+
});
|
|
91039
|
+
breakSegment();
|
|
91040
|
+
return segments.filter((s) => s.length > 0);
|
|
90783
91041
|
}
|
|
90784
91042
|
async function docxBatch(filePath, json2) {
|
|
90785
91043
|
if (!filePath || !json2) fail2("Usage: lotics docx batch <file> '<json-array-of-ops>'");
|
|
@@ -90823,8 +91081,9 @@ function applyBatchOp(doc, op, index) {
|
|
|
90823
91081
|
return replaceChildren(doc, doc.body.children.filter((_, i2) => i2 !== op.at));
|
|
90824
91082
|
}
|
|
90825
91083
|
case "replace-text": {
|
|
90826
|
-
const
|
|
90827
|
-
|
|
91084
|
+
const { children, count } = replaceEverywhere(doc.body.children, op.search, op.replace);
|
|
91085
|
+
if (count === 0) fail2(`[op ${index}] No occurrence of ${JSON.stringify(op.search)} \u2014 batch aborted.`);
|
|
91086
|
+
return replaceChildren(doc, children);
|
|
90828
91087
|
}
|
|
90829
91088
|
default: {
|
|
90830
91089
|
const exhaustive = op;
|
|
@@ -90844,10 +91103,12 @@ Uses Lotics' own OOXML engine; round-trips faithfully with the Lotics editor and
|
|
|
90844
91103
|
lotics docx insert-paragraph <file> '<text>' --at=<i> [--style=NAME]
|
|
90845
91104
|
Insert a paragraph at index <i> (0-based)
|
|
90846
91105
|
lotics docx delete-block <file> --at=<i> Remove the block at index <i>
|
|
90847
|
-
lotics docx replace-text <file> '<search>' '<replace>' Replace text in
|
|
91106
|
+
lotics docx replace-text <file> '<search>' '<replace>' Replace text in paragraphs AND table cells
|
|
91107
|
+
(errors if nothing matched \u2014 never a silent no-op)
|
|
90848
91108
|
lotics docx batch <file> '<json-array-of-ops>' Apply many ops in one parse/serialize cycle
|
|
90849
91109
|
|
|
90850
|
-
Edit ops mutate the file atomically (temp file + rename).
|
|
91110
|
+
Edit ops mutate the file atomically (temp file + rename). Structure inside opaque blocks (tables, custom XML) is
|
|
91111
|
+
preserved verbatim; replace-text rewrites the text within them.`);
|
|
90851
91112
|
}
|
|
90852
91113
|
async function runDocxCommand(subcommand, toolArgs, restArgs) {
|
|
90853
91114
|
switch (subcommand) {
|
|
@@ -91542,6 +91803,14 @@ function announceTarget(ctx, workspaceId) {
|
|
|
91542
91803
|
}
|
|
91543
91804
|
console.error(`lotics \u2192 ${org} / ${workspaceId}`);
|
|
91544
91805
|
}
|
|
91806
|
+
function applyAppManifestWorkspace(ctx, command, subcommand, pathArg, flags) {
|
|
91807
|
+
if (command !== "app") return;
|
|
91808
|
+
if (flags.workspace || process.env.LOTICS_WORKSPACE) return;
|
|
91809
|
+
if (subcommand === "create" || subcommand === "pull") return;
|
|
91810
|
+
const dir = pathArg && !pathArg.startsWith("-") ? pathArg : process.cwd();
|
|
91811
|
+
const workspaceId = readAppWorkspaceId(dir);
|
|
91812
|
+
if (workspaceId) ctx.workspaceId = workspaceId;
|
|
91813
|
+
}
|
|
91545
91814
|
async function resolveWorkspace(client, ctx) {
|
|
91546
91815
|
if (ctx.workspaceId) {
|
|
91547
91816
|
announceTarget(ctx, ctx.workspaceId);
|
|
@@ -91744,6 +92013,7 @@ async function main() {
|
|
|
91744
92013
|
await appCodegen({ projectDir });
|
|
91745
92014
|
return;
|
|
91746
92015
|
}
|
|
92016
|
+
applyAppManifestWorkspace(ctx2, command, subcommand, projectDir, flags);
|
|
91747
92017
|
const viewAsMemberId = flags.viewAs ?? process.env.LOTICS_VIEW_AS;
|
|
91748
92018
|
const client2 = new LoticsClient({ apiKey: ctx2.apiKey, workspaceId: ctx2.workspaceId, viewAsMemberId });
|
|
91749
92019
|
await resolveWorkspace(client2, ctx2);
|
|
@@ -91969,6 +92239,7 @@ Available workspaces:`);
|
|
|
91969
92239
|
if (ctx.orgName) console.error(`Org: ${ctx.orgName}`);
|
|
91970
92240
|
return;
|
|
91971
92241
|
}
|
|
92242
|
+
applyAppManifestWorkspace(ctx, command, subcommand, subcommand === "dev" ? toolArgs : void 0, flags);
|
|
91972
92243
|
await resolveWorkspace(client, ctx);
|
|
91973
92244
|
if (command === "knowledge") {
|
|
91974
92245
|
await runKnowledgeCommand(client, subcommand, toolArgs, flags);
|