@stll/folio-cli 0.5.1 → 0.5.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/bin.js +10 -4
- package/dist/editor-lease.js +3 -2
- package/dist/execute-read.js +5 -4
- package/dist/flags.js +3 -2
- package/dist/journal.js +4 -3
- package/dist/lock.js +4 -3
- package/dist/main-document-part.js +5 -4
- package/dist/mcp-kit/schema.js +2 -1
- package/dist/mcp-kit/surface.js +6 -2
- package/dist/output.js +6 -1
- package/dist/package-parts.js +4 -3
- package/dist/package.js +1 -1
- package/dist/preview-commands.js +2 -1
- package/dist/serve.js +6 -5
- package/package.json +25 -7
package/dist/bin.js
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { runFolioCli } from "./cli.js";
|
|
3
|
+
import { panic } from "better-result";
|
|
4
|
+
import { createTextReader, encodeUtf8 } from "@stll/folio-core/utils/textEncoding";
|
|
3
5
|
//#region src/bin.ts
|
|
4
6
|
const readStdin = async () => {
|
|
7
|
+
const reader = createTextReader({ ignoreBOM: true });
|
|
5
8
|
const chunks = [];
|
|
6
|
-
for await (const chunk of process.stdin)
|
|
7
|
-
|
|
9
|
+
for await (const chunk of process.stdin) {
|
|
10
|
+
if (!(chunk instanceof Uint8Array)) panic("Standard input must provide byte chunks");
|
|
11
|
+
chunks.push(reader.read(chunk));
|
|
12
|
+
}
|
|
13
|
+
return chunks.join("") + reader.finish();
|
|
8
14
|
};
|
|
9
15
|
process.exitCode = await runFolioCli(process.argv.slice(2), {
|
|
10
16
|
stdout: (text) => {
|
|
11
|
-
process.stdout.write(text);
|
|
17
|
+
process.stdout.write(encodeUtf8(text));
|
|
12
18
|
},
|
|
13
19
|
stderr: (text) => {
|
|
14
|
-
process.stderr.write(text);
|
|
20
|
+
process.stderr.write(encodeUtf8(text));
|
|
15
21
|
},
|
|
16
22
|
readStdin,
|
|
17
23
|
isTTY: process.stdout.isTTY,
|
package/dist/editor-lease.js
CHANGED
|
@@ -3,6 +3,7 @@ import { fileVersionOf } from "./file-system.js";
|
|
|
3
3
|
import { DEFAULT_LEASE_OWNER, acquireLease, publicLockHolder, readLease } from "./lock.js";
|
|
4
4
|
import { readSidecarFile, writeNewSidecarFile } from "./sidecar.js";
|
|
5
5
|
import { Result } from "better-result";
|
|
6
|
+
import { decodeText, encodeUtf8 } from "@stll/folio-core/utils/textEncoding";
|
|
6
7
|
import { watch } from "node:fs";
|
|
7
8
|
import { readFile, readdir, rename, rm } from "node:fs/promises";
|
|
8
9
|
import path from "node:path";
|
|
@@ -103,7 +104,7 @@ const pendingFlushRequests = async (documentPath, now = /* @__PURE__ */ new Date
|
|
|
103
104
|
const requestPath = path.join(directory, name);
|
|
104
105
|
const bytes = await readSidecarFile(requestPath);
|
|
105
106
|
if (bytes.isErr()) continue;
|
|
106
|
-
const request = parseRequest(
|
|
107
|
+
const request = parseRequest(decodeText(bytes.value));
|
|
107
108
|
if (request === null) continue;
|
|
108
109
|
if (Date.parse(request.deadline) > now.getTime()) pending.push(request);
|
|
109
110
|
else await rm(requestPath, { force: true });
|
|
@@ -113,7 +114,7 @@ const pendingFlushRequests = async (documentPath, now = /* @__PURE__ */ new Date
|
|
|
113
114
|
const placeRequest = async (documentPath, request) => {
|
|
114
115
|
const target = flushRequestPathFor(documentPath, request.id);
|
|
115
116
|
const temporary = path.join(path.dirname(documentPath), `.${path.basename(documentPath)}.folio-flush.${request.id}.tmp`);
|
|
116
|
-
const written = await writeNewSidecarFile(temporary,
|
|
117
|
+
const written = await writeNewSidecarFile(temporary, encodeUtf8(`${JSON.stringify(request)}\n`));
|
|
117
118
|
if (written.isErr()) return Result.err(written.error);
|
|
118
119
|
if ((await Result.tryPromise(() => rename(temporary, target))).isOk()) return Result.ok();
|
|
119
120
|
await rm(temporary, { force: true });
|
package/dist/execute-read.js
CHANGED
|
@@ -4,6 +4,7 @@ import { MAX_READ_BLOCKS } from "./registry.js";
|
|
|
4
4
|
import { Result, panic } from "better-result";
|
|
5
5
|
import { executeFolioToolCallUntyped } from "@stll/folio-agents/execute";
|
|
6
6
|
import { FOLIO_AGENT_TOOL_NAMES } from "@stll/folio-agents/types";
|
|
7
|
+
import { base64ToBytes, bytesToBase64Url, decodeText, encodeUtf8 } from "@stll/folio-core/utils/textEncoding";
|
|
7
8
|
import { createReviewerBridge } from "@stll/folio-agents/bridges/reviewer";
|
|
8
9
|
import { compareDocxVersions, formatVersionDiffForLLM } from "@stll/folio-agents/compare";
|
|
9
10
|
//#region src/execute-read.ts
|
|
@@ -36,12 +37,12 @@ const labelBlocks = (blocks, sources, cells = /* @__PURE__ */ new Map()) => bloc
|
|
|
36
37
|
...cell !== void 0 && { tableCell: cell }
|
|
37
38
|
};
|
|
38
39
|
});
|
|
39
|
-
const encodeReadCursor = ({ fileVersion, afterBlockId }) =>
|
|
40
|
+
const encodeReadCursor = ({ fileVersion, afterBlockId }) => bytesToBase64Url(encodeUtf8(JSON.stringify({
|
|
40
41
|
v: fileVersion,
|
|
41
42
|
a: afterBlockId
|
|
42
|
-
})
|
|
43
|
+
})));
|
|
43
44
|
const decodeReadCursor = (cursor) => {
|
|
44
|
-
const parsed = Result.try(() => JSON.parse(
|
|
45
|
+
const parsed = Result.try(() => JSON.parse(decodeText(base64ToBytes(cursor), { ignoreBOM: true })));
|
|
45
46
|
if (parsed.isErr() || !isRecord(parsed.value)) return null;
|
|
46
47
|
const { v, a } = parsed.value;
|
|
47
48
|
return typeof v === "string" && typeof a === "string" ? {
|
|
@@ -54,7 +55,7 @@ const invalidInput = (message, hint) => cliError({
|
|
|
54
55
|
message,
|
|
55
56
|
hint
|
|
56
57
|
});
|
|
57
|
-
const byteLength = (value) =>
|
|
58
|
+
const byteLength = (value) => encodeUtf8(JSON.stringify(value) ?? "").byteLength;
|
|
58
59
|
/** Room kept in a paged response for its envelope fields and `nextCursor`. */
|
|
59
60
|
const PAGE_ENVELOPE_RESERVE_BYTES = 512;
|
|
60
61
|
/**
|
package/dist/flags.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FOLIO_CLI_ERROR_CODES, cliError } from "./errors.js";
|
|
2
2
|
import { Result, panic } from "better-result";
|
|
3
|
+
import { decodeText, encodeUtf8 } from "@stll/folio-core/utils/textEncoding";
|
|
3
4
|
import { readFile } from "node:fs/promises";
|
|
4
5
|
//#region src/flags.ts
|
|
5
6
|
/**
|
|
@@ -82,13 +83,13 @@ const readInputSource = async ({ source, readStdin }) => {
|
|
|
82
83
|
else if (source.startsWith("@")) {
|
|
83
84
|
const inputPath = source.slice(1);
|
|
84
85
|
const read = await Result.tryPromise({
|
|
85
|
-
try: () => readFile(inputPath,
|
|
86
|
+
try: () => readFile(inputPath).then((bytes) => decodeText(bytes, { ignoreBOM: true })),
|
|
86
87
|
catch: () => usage(`Cannot read --input file ${inputPath}.`)
|
|
87
88
|
});
|
|
88
89
|
if (read.isErr()) return Result.err(read.error);
|
|
89
90
|
text = read.value;
|
|
90
91
|
}
|
|
91
|
-
if (
|
|
92
|
+
if (encodeUtf8(text).byteLength > 16777216) return Result.err(usage(`--input is over the ${MAX_INPUT_BYTES}-byte limit.`));
|
|
92
93
|
const parsed = Result.try(() => JSON.parse(text));
|
|
93
94
|
if (parsed.isErr()) return Result.err(usage("--input is not valid JSON.", "Pass inline JSON, @file.json, or - for stdin."));
|
|
94
95
|
const value = parsed.value;
|
package/dist/journal.js
CHANGED
|
@@ -3,6 +3,7 @@ import { errnoCode, fileVersionOf, sameFile } from "./file-system.js";
|
|
|
3
3
|
import { checkPackageIntegrity } from "./package-parts.js";
|
|
4
4
|
import { SIDECAR_DIRECTORY, ensureSidecarParent, inspectPath, openSidecarFile, readSidecarFile, syncDirectory } from "./sidecar.js";
|
|
5
5
|
import { Result } from "better-result";
|
|
6
|
+
import { decodeText, encodeUtf8 } from "@stll/folio-core/utils/textEncoding";
|
|
6
7
|
import { constants } from "node:fs";
|
|
7
8
|
import { readdir, rename, rm } from "node:fs/promises";
|
|
8
9
|
import path from "node:path";
|
|
@@ -42,7 +43,7 @@ const appendJournal = async (journalPath, entry) => {
|
|
|
42
43
|
const { handle } = opened.value;
|
|
43
44
|
const written = await Result.tryPromise(async () => {
|
|
44
45
|
try {
|
|
45
|
-
await handle.writeFile(`${JSON.stringify(entry)}\n`);
|
|
46
|
+
await handle.writeFile(encodeUtf8(`${JSON.stringify(entry)}\n`));
|
|
46
47
|
await handle.sync();
|
|
47
48
|
} finally {
|
|
48
49
|
await handle.close();
|
|
@@ -90,7 +91,7 @@ const findCommit = async (journalPath, txId) => {
|
|
|
90
91
|
const bytes = await readSidecarFile(journalPath);
|
|
91
92
|
if (bytes.isErr()) return Result.err(bytes.error);
|
|
92
93
|
let found;
|
|
93
|
-
for (const line of
|
|
94
|
+
for (const line of decodeText(bytes.value).split("\n")) {
|
|
94
95
|
if (!line.includes(txId)) continue;
|
|
95
96
|
if (isDiscardRecord(line, txId)) return Result.ok(void 0);
|
|
96
97
|
const commit = parseCommit(line);
|
|
@@ -121,7 +122,7 @@ const latestCommitFor = async (documentPath, journalPath) => {
|
|
|
121
122
|
if (parent.isErr() || parent.value.type !== "directory") return null;
|
|
122
123
|
const bytes = await readSidecarFile(journal);
|
|
123
124
|
if (bytes.isErr()) return null;
|
|
124
|
-
const lines =
|
|
125
|
+
const lines = decodeText(bytes.value).split("\n");
|
|
125
126
|
const discarded = /* @__PURE__ */ new Set();
|
|
126
127
|
for (let index = lines.length - 1; index >= 0; index--) {
|
|
127
128
|
const parsed = Result.try(() => JSON.parse(lines[index] ?? ""));
|
package/dist/lock.js
CHANGED
|
@@ -2,6 +2,7 @@ import { FOLIO_CLI_ERROR_CODES, cliError } from "./errors.js";
|
|
|
2
2
|
import { errnoCode } from "./file-system.js";
|
|
3
3
|
import { inspectPath, readSidecarFile, writeNewSidecarFile } from "./sidecar.js";
|
|
4
4
|
import { Result } from "better-result";
|
|
5
|
+
import { decodeText, encodeUtf8 } from "@stll/folio-core/utils/textEncoding";
|
|
5
6
|
import { link, rename, rm } from "node:fs/promises";
|
|
6
7
|
import path from "node:path";
|
|
7
8
|
import { randomUUID } from "node:crypto";
|
|
@@ -90,7 +91,7 @@ const readLease = async (documentPath) => {
|
|
|
90
91
|
if (entry.value.type === "missing") return { type: "free" };
|
|
91
92
|
const modifiedMs = entry.value.type === "file" ? entry.value.modifiedMs : Date.now();
|
|
92
93
|
const bytes = await readSidecarFile(lockPath);
|
|
93
|
-
const holder = bytes.isOk() ? parseHolder(
|
|
94
|
+
const holder = bytes.isOk() ? parseHolder(decodeText(bytes.value)) : null;
|
|
94
95
|
return holder === null ? {
|
|
95
96
|
type: "unreadable",
|
|
96
97
|
modifiedMs
|
|
@@ -132,7 +133,7 @@ const placeLock = async (lockPath, contents, mode) => {
|
|
|
132
133
|
await rm(temporary, { force: true });
|
|
133
134
|
}
|
|
134
135
|
};
|
|
135
|
-
const encodeHolder = (holder) =>
|
|
136
|
+
const encodeHolder = (holder) => encodeUtf8(`${JSON.stringify(holder)}\n`);
|
|
136
137
|
/** How long a swap lock may stand before another process breaks it (its holder crashed). */
|
|
137
138
|
const SWAP_STALE_MS = 10 * 1e3;
|
|
138
139
|
/** How long to wait for another process's swap before refusing. */
|
|
@@ -152,7 +153,7 @@ const lockSwapPathFor = (documentPath) => `${lockPathFor(documentPath)}.swap`;
|
|
|
152
153
|
*/
|
|
153
154
|
const underSwapLock = async (documentPath, change) => {
|
|
154
155
|
const swapPath = lockSwapPathFor(documentPath);
|
|
155
|
-
const marker =
|
|
156
|
+
const marker = encodeUtf8(`${process.pid}\n`);
|
|
156
157
|
const deadline = Date.now() + SWAP_WAIT_MS;
|
|
157
158
|
for (;;) {
|
|
158
159
|
const taken = await placeLock(swapPath, marker, "create");
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FOLIO_CLI_ERROR_CODES, cliError } from "./errors.js";
|
|
2
2
|
import { Result } from "better-result";
|
|
3
|
+
import { createTextReader } from "@stll/folio-core/utils/textEncoding";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { XMLParser, XMLValidator } from "fast-xml-parser";
|
|
5
6
|
import JSZip from "jszip";
|
|
@@ -121,23 +122,23 @@ const childrenNamed = (element, namespace, localName) => element.children.filter
|
|
|
121
122
|
const partKey = (name) => name.toLowerCase();
|
|
122
123
|
/** Inflate at most `limit` bytes of an entry; `truncated` when it holds more. */
|
|
123
124
|
const inflatePrefix = (entry, limit) => new Promise((resolve, reject) => {
|
|
124
|
-
const decoder =
|
|
125
|
+
const decoder = createTextReader();
|
|
125
126
|
let text = "";
|
|
126
127
|
let size = 0;
|
|
127
128
|
const stream = entry.internalStream("uint8array");
|
|
128
129
|
stream.on("data", (chunk) => {
|
|
129
130
|
const room = limit - size;
|
|
130
131
|
size += chunk.length;
|
|
131
|
-
text += decoder.
|
|
132
|
+
text += decoder.read(chunk.subarray(0, Math.max(0, room)));
|
|
132
133
|
if (size > limit) {
|
|
133
134
|
stream.pause();
|
|
134
135
|
resolve({
|
|
135
|
-
text: text + decoder.
|
|
136
|
+
text: text + decoder.finishPrefix(),
|
|
136
137
|
truncated: true
|
|
137
138
|
});
|
|
138
139
|
}
|
|
139
140
|
}).on("error", reject).on("end", () => resolve({
|
|
140
|
-
text: text + decoder.
|
|
141
|
+
text: text + decoder.finish(),
|
|
141
142
|
truncated: false
|
|
142
143
|
})).resume();
|
|
143
144
|
});
|
package/dist/mcp-kit/schema.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { encodeUtf8 } from "@stll/folio-core/utils/textEncoding";
|
|
1
2
|
//#region src/mcp-kit/schema.ts
|
|
2
3
|
const ANNOTATION_KEYWORDS = /* @__PURE__ */ new Set([
|
|
3
4
|
"$comment",
|
|
@@ -92,6 +93,6 @@ const hoistRepeatedSchemas = (schema, { minBytes = 200 } = {}) => {
|
|
|
92
93
|
} : schema;
|
|
93
94
|
};
|
|
94
95
|
/** The UTF-8 size of what `tools/list` sends for these tools. */
|
|
95
|
-
const advertisedBytes = (tools) =>
|
|
96
|
+
const advertisedBytes = (tools) => encodeUtf8(JSON.stringify(tools)).length;
|
|
96
97
|
//#endregion
|
|
97
98
|
export { advertisedBytes, compactSchema, hoistRepeatedSchemas };
|
package/dist/mcp-kit/surface.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { KIT_ERROR_CODES, closestNames, didYouMean, failure, success, toCallResult, validationError } from "./envelope.js";
|
|
2
2
|
import { readToolInput } from "./input.js";
|
|
3
3
|
import { compactSchema } from "./schema.js";
|
|
4
|
+
import { Result } from "better-result";
|
|
5
|
+
import { base64ToBytes, bytesToBase64Url, decodeText, encodeUtf8 } from "@stll/folio-core/utils/textEncoding";
|
|
4
6
|
//#region src/mcp-kit/surface.ts
|
|
5
7
|
/**
|
|
6
8
|
* A tool surface with lazy discovery. The frequent tools are listed directly
|
|
@@ -102,9 +104,11 @@ const compareIds = (a, b) => {
|
|
|
102
104
|
if (a === b) return 0;
|
|
103
105
|
return a < b ? -1 : 1;
|
|
104
106
|
};
|
|
105
|
-
const encodeCursor = (id) =>
|
|
107
|
+
const encodeCursor = (id) => bytesToBase64Url(encodeUtf8(id));
|
|
106
108
|
const decodeCursor = (cursor) => {
|
|
107
|
-
const
|
|
109
|
+
const parsed = Result.try(() => decodeText(base64ToBytes(cursor), { ignoreBOM: true }));
|
|
110
|
+
if (parsed.isErr()) return null;
|
|
111
|
+
const decoded = parsed.value;
|
|
108
112
|
return decoded.length > 0 && encodeCursor(decoded) === cursor ? decoded : null;
|
|
109
113
|
};
|
|
110
114
|
const hasJsonType = (value, type) => {
|
package/dist/output.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sliceText } from "@stll/folio-core/utils/sliceText";
|
|
1
2
|
//#region src/output.ts
|
|
2
3
|
const OUTPUT_FORMATS = ["json", "text"];
|
|
3
4
|
const isOutputFormat = (value) => OUTPUT_FORMATS.some((format) => format === value);
|
|
@@ -43,7 +44,11 @@ const renderResult = (tool, result) => {
|
|
|
43
44
|
if (tool === "compare_documents" && isRecord(result) && typeof result["text"] === "string") return [result["text"]];
|
|
44
45
|
return null;
|
|
45
46
|
};
|
|
46
|
-
const shortVersion = (value) =>
|
|
47
|
+
const shortVersion = (value) => sliceText({
|
|
48
|
+
text: text(value),
|
|
49
|
+
units: "codeUnits",
|
|
50
|
+
end: 12
|
|
51
|
+
});
|
|
47
52
|
/** One line per committed or replayed transaction. */
|
|
48
53
|
const renderReceipt = (receipt) => {
|
|
49
54
|
const parts = Array.isArray(receipt["changedParts"]) ? receipt["changedParts"].length : 0;
|
package/dist/package-parts.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FOLIO_CLI_ERROR_CODES, cliError } from "./errors.js";
|
|
2
2
|
import { checkWordprocessingPackage } from "./main-document-part.js";
|
|
3
3
|
import { Result } from "better-result";
|
|
4
|
+
import { decodeText } from "@stll/folio-core/utils/textEncoding";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { FolioDocxReviewer } from "@stll/folio-core/server";
|
|
6
7
|
import { XMLParser, XMLValidator } from "fast-xml-parser";
|
|
@@ -73,7 +74,7 @@ const nextRevisionIdSeed = async (bytes) => {
|
|
|
73
74
|
let highest = 0;
|
|
74
75
|
for (const [part, entry] of partEntries(zip.value)) {
|
|
75
76
|
if (!part.endsWith(".xml")) continue;
|
|
76
|
-
const xml = await entry.async("
|
|
77
|
+
const xml = await entry.async("uint8array").then((partBytes) => decodeText(partBytes, { ignoreBOM: true }));
|
|
77
78
|
for (const prefix of prefixesBoundTo(xml, WORDPROCESSINGML_NAMESPACES)) {
|
|
78
79
|
const pattern = new RegExp(`\\s${escapeRegExp(prefix)}:id\\s*=\\s*"(\\d+)"`, "gu");
|
|
79
80
|
for (const [, id] of xml.matchAll(pattern)) highest = Math.max(highest, Number(id));
|
|
@@ -123,7 +124,7 @@ const integrityError = (message) => cliError({
|
|
|
123
124
|
const checkChangedPart = async ({ zip, parts, part }) => {
|
|
124
125
|
const entry = parts.get(part);
|
|
125
126
|
if (entry === void 0 || !(part.endsWith(".xml") || part.endsWith(".rels"))) return null;
|
|
126
|
-
const xml = await entry.async("
|
|
127
|
+
const xml = await entry.async("uint8array").then((partBytes) => decodeText(partBytes, { ignoreBOM: true }));
|
|
127
128
|
if (XMLValidator.validate(xml) !== true) return `${part} is not well-formed XML.`;
|
|
128
129
|
if (part.endsWith(".rels")) {
|
|
129
130
|
const lowerCaseParts = new Set([...parts.keys()].map((name) => name.toLowerCase()));
|
|
@@ -136,7 +137,7 @@ const checkChangedPart = async ({ zip, parts, part }) => {
|
|
|
136
137
|
}
|
|
137
138
|
const prefixes = prefixesBoundTo(xml, RELATIONSHIP_NAMESPACES);
|
|
138
139
|
if (prefixes.size === 0) return null;
|
|
139
|
-
const relsXml = await zip.file(relationshipsPartFor(part))?.async("
|
|
140
|
+
const relsXml = await zip.file(relationshipsPartFor(part))?.async("uint8array").then((partBytes) => decodeText(partBytes, { ignoreBOM: true }));
|
|
140
141
|
const ids = new Set(parseRelationships(relsXml ?? "").map(({ id }) => id));
|
|
141
142
|
for (const prefix of prefixes) {
|
|
142
143
|
const pattern = new RegExp(`\\s${escapeRegExp(prefix)}:[A-Za-z]+\\s*=\\s*"([^"]*)"`, "gu");
|
package/dist/package.js
CHANGED
package/dist/preview-commands.js
CHANGED
|
@@ -6,6 +6,7 @@ import { resolveTransactionDate } from "./provenance.js";
|
|
|
6
6
|
import { buildDisplayList, checkPages, displayListHtml, renderPdf, renderPng } from "./render.js";
|
|
7
7
|
import { startPreviewServer } from "./serve.js";
|
|
8
8
|
import { Result } from "better-result";
|
|
9
|
+
import { encodeUtf8 } from "@stll/folio-core/utils/textEncoding";
|
|
9
10
|
import { parseArgs } from "node:util";
|
|
10
11
|
import path from "node:path";
|
|
11
12
|
//#region src/preview-commands.ts
|
|
@@ -140,7 +141,7 @@ const render = async ({ values, positionals }) => {
|
|
|
140
141
|
const list = await buildDisplayList(file.value);
|
|
141
142
|
const selected = list.isOk() ? checkPages(list.value, pages) : list;
|
|
142
143
|
output = selected.isOk() ? Result.ok({
|
|
143
|
-
bytes:
|
|
144
|
+
bytes: encodeUtf8(displayListHtml(selected.value, path.basename(file.value.path))),
|
|
144
145
|
pageCount: selected.value.pages.length
|
|
145
146
|
}) : Result.err(selected.error);
|
|
146
147
|
break;
|
package/dist/serve.js
CHANGED
|
@@ -4,6 +4,7 @@ import { latestCommitFor } from "./journal.js";
|
|
|
4
4
|
import { buildDisplayList, displayListHtml } from "./render.js";
|
|
5
5
|
import { SIDECAR_DIRECTORY } from "./sidecar.js";
|
|
6
6
|
import { Result } from "better-result";
|
|
7
|
+
import { bytesToBase64, bytesToBase64Url, encodeUtf8 } from "@stll/folio-core/utils/textEncoding";
|
|
7
8
|
import { watch } from "node:fs";
|
|
8
9
|
import { stat } from "node:fs/promises";
|
|
9
10
|
import path from "node:path";
|
|
@@ -85,7 +86,7 @@ const startPreviewServer = async ({ documentPath, port }) => {
|
|
|
85
86
|
if (initial.isErr()) return Result.err(initial.error);
|
|
86
87
|
const realPath = initial.value.path;
|
|
87
88
|
const title = path.basename(realPath);
|
|
88
|
-
const prefix = `/${randomBytes(24)
|
|
89
|
+
const prefix = `/${bytesToBase64Url(randomBytes(24))}/`;
|
|
89
90
|
let stamp = null;
|
|
90
91
|
let rendered = null;
|
|
91
92
|
let rendering = null;
|
|
@@ -129,7 +130,7 @@ const startPreviewServer = async ({ documentPath, port }) => {
|
|
|
129
130
|
rendered = result;
|
|
130
131
|
stamp = next;
|
|
131
132
|
if (changed) {
|
|
132
|
-
const message = `data: ${JSON.stringify({ fileVersion: result.fileVersion })}\n\n
|
|
133
|
+
const message = encodeUtf8(`data: ${JSON.stringify({ fileVersion: result.fileVersion })}\n\n`);
|
|
133
134
|
for (const listener of listeners) listener.write(message);
|
|
134
135
|
}
|
|
135
136
|
return result;
|
|
@@ -144,7 +145,7 @@ const startPreviewServer = async ({ documentPath, port }) => {
|
|
|
144
145
|
...SECURITY_HEADERS,
|
|
145
146
|
...headers
|
|
146
147
|
});
|
|
147
|
-
response.end(body);
|
|
148
|
+
response.end(encodeUtf8(body));
|
|
148
149
|
};
|
|
149
150
|
const handle = async (request, response) => {
|
|
150
151
|
if (!allowedHosts().has(request.headers.host ?? "")) {
|
|
@@ -165,7 +166,7 @@ const startPreviewServer = async ({ documentPath, port }) => {
|
|
|
165
166
|
}
|
|
166
167
|
const route = url.pathname.slice(prefix.length);
|
|
167
168
|
if (route === "") {
|
|
168
|
-
const nonce = randomBytes(16)
|
|
169
|
+
const nonce = bytesToBase64(randomBytes(16));
|
|
169
170
|
const page = await current();
|
|
170
171
|
respond(response, 200, shellHtml(title, nonce, page.type === "page" ? page.pageWidthPx : null), {
|
|
171
172
|
"Content-Type": "text/html; charset=utf-8",
|
|
@@ -203,7 +204,7 @@ const startPreviewServer = async ({ documentPath, port }) => {
|
|
|
203
204
|
listeners.add(response);
|
|
204
205
|
request.on("close", () => listeners.delete(response));
|
|
205
206
|
const page = await current();
|
|
206
|
-
response.write(`data: ${JSON.stringify({ fileVersion: page.fileVersion })}\n\n`);
|
|
207
|
+
response.write(encodeUtf8(`data: ${JSON.stringify({ fileVersion: page.fileVersion })}\n\n`));
|
|
207
208
|
return;
|
|
208
209
|
}
|
|
209
210
|
respond(response, 404, "not found", { "Content-Type": "text/plain" });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stll/folio-cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Command line and stdio MCP server for reading, reviewing, and redlining .docx files with folio: tracked-change edits, comments, and comparisons written atomically with version preconditions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -39,15 +39,18 @@
|
|
|
39
39
|
"exports": {
|
|
40
40
|
".": {
|
|
41
41
|
"types": "./dist/cli.d.ts",
|
|
42
|
-
"import": "./dist/cli.js"
|
|
42
|
+
"import": "./dist/cli.js",
|
|
43
|
+
"default": "./dist/cli.js"
|
|
43
44
|
},
|
|
44
45
|
"./editor-lease": {
|
|
45
46
|
"types": "./dist/editor-lease.d.ts",
|
|
46
|
-
"import": "./dist/editor-lease.js"
|
|
47
|
+
"import": "./dist/editor-lease.js",
|
|
48
|
+
"default": "./dist/editor-lease.js"
|
|
47
49
|
},
|
|
48
50
|
"./save": {
|
|
49
51
|
"types": "./dist/save.d.ts",
|
|
50
|
-
"import": "./dist/save.js"
|
|
52
|
+
"import": "./dist/save.js",
|
|
53
|
+
"default": "./dist/save.js"
|
|
51
54
|
}
|
|
52
55
|
},
|
|
53
56
|
"publishConfig": {
|
|
@@ -71,8 +74,9 @@
|
|
|
71
74
|
"@fontsource/tinos": "^5.2.7",
|
|
72
75
|
"@modelcontextprotocol/server": "2.0.0",
|
|
73
76
|
"@stll/agent-input": "0.1.0",
|
|
74
|
-
"@stll/folio-agents": "^0.16.
|
|
75
|
-
"@stll/folio-core": "^0.
|
|
77
|
+
"@stll/folio-agents": "^0.16.2",
|
|
78
|
+
"@stll/folio-core": "^0.61.0",
|
|
79
|
+
"@stll/utf8-boundaries": "0.1.0",
|
|
76
80
|
"better-result": "3.0.1",
|
|
77
81
|
"fast-xml-parser": "^5.10.1",
|
|
78
82
|
"jszip": "3.10.2"
|
|
@@ -82,7 +86,21 @@
|
|
|
82
86
|
"playwright-core": "1.61.1"
|
|
83
87
|
},
|
|
84
88
|
"peerDependencies": {
|
|
85
|
-
"playwright-core": ">=1.50.0"
|
|
89
|
+
"playwright-core": ">=1.50.0",
|
|
90
|
+
"prosemirror-commands": "^1.7.1",
|
|
91
|
+
"prosemirror-dropcursor": "^1.8.2",
|
|
92
|
+
"prosemirror-gapcursor": "^1.4.1",
|
|
93
|
+
"prosemirror-history": "^1.5.0",
|
|
94
|
+
"prosemirror-inputrules": "^1.5.0",
|
|
95
|
+
"prosemirror-keymap": "^1.2.3",
|
|
96
|
+
"prosemirror-model": "^1.25.9",
|
|
97
|
+
"prosemirror-state": "^1.4.4",
|
|
98
|
+
"prosemirror-tables": "^1.8.5",
|
|
99
|
+
"prosemirror-transform": "^1.12.0",
|
|
100
|
+
"prosemirror-view": "^1.42.6",
|
|
101
|
+
"y-prosemirror": "^1.3.7",
|
|
102
|
+
"y-protocols": "^1.0.6",
|
|
103
|
+
"yjs": "^13.6.31"
|
|
86
104
|
},
|
|
87
105
|
"peerDependenciesMeta": {
|
|
88
106
|
"playwright-core": {
|