token-goat 2.9.11 → 2.9.13
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/README.md +7 -1
- package/dist/{token-goat-chunk-FDURVZQD.mjs → token-goat-chunk-2WC4ZUXN.mjs} +1 -1
- package/dist/{token-goat-chunk-MRZ555B3.mjs → token-goat-chunk-3ESRORNM.mjs} +188 -97
- package/dist/token-goat-chunk-4NXUKV7D.mjs +56 -0
- package/dist/{token-goat-chunk-Y5VKNLVD.mjs → token-goat-chunk-6B44WLIF.mjs} +5 -5
- package/dist/{token-goat-chunk-2ZUPQYLO.mjs → token-goat-chunk-B3CTCQTH.mjs} +1 -1
- package/dist/{token-goat-chunk-V53Z47ZH.mjs → token-goat-chunk-FQCNJV4V.mjs} +20 -6
- package/dist/{token-goat-chunk-3ZALKJ23.mjs → token-goat-chunk-FZU7GMUS.mjs} +49 -24
- package/dist/{token-goat-chunk-AAEYU2U2.mjs → token-goat-chunk-JOXLE672.mjs} +344 -5699
- package/dist/{token-goat-chunk-HDL77BN3.mjs → token-goat-chunk-JVNPCQB7.mjs} +10 -7
- package/dist/{token-goat-chunk-LHLQFGWQ.mjs → token-goat-chunk-OMNQUUIT.mjs} +9 -27
- package/dist/{token-goat-chunk-PJPFOOGM.mjs → token-goat-chunk-P2PU4CR5.mjs} +8 -6
- package/dist/{token-goat-chunk-YCKCFYTM.mjs → token-goat-chunk-QKXBGBQR.mjs} +110 -21
- package/dist/{token-goat-chunk-MKITQ5RY.mjs → token-goat-chunk-QWSUZWFP.mjs} +44 -33
- package/dist/{token-goat-chunk-4NZUXC4F.mjs → token-goat-chunk-RDITECDL.mjs} +7 -5
- package/dist/{token-goat-chunk-LKOYKSID.mjs → token-goat-chunk-U7X6LQD2.mjs} +271 -69
- package/dist/token-goat-chunk-UM47DRD3.mjs +242 -0
- package/dist/token-goat-chunk-UMXJN7DI.mjs +5521 -0
- package/dist/token-goat-chunk-YOA4N6WA.mjs +308 -0
- package/dist/{token-goat-chunk-SY7WTZMW.mjs → token-goat-chunk-ZZI3IDQZ.mjs} +1016 -995
- package/dist/token-goat-hook.mjs +8 -6
- package/dist/token-goat.core.mjs +10 -7
- package/docs/cli.md +11 -8
- package/docs/security.md +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { createRequire as __cjsRequire } from 'node:module';
|
|
2
|
+
const require = __cjsRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_OCR_LANG,
|
|
5
|
+
isSupportedOcrLang,
|
|
6
|
+
loadConfig,
|
|
7
|
+
resolveOcrLangs
|
|
8
|
+
} from "./token-goat-chunk-UMXJN7DI.mjs";
|
|
9
|
+
import {
|
|
10
|
+
ensureDirSync,
|
|
11
|
+
fenceUntrustedOcrText,
|
|
12
|
+
tokenGoatHome
|
|
13
|
+
} from "./token-goat-chunk-QKXBGBQR.mjs";
|
|
14
|
+
import {
|
|
15
|
+
init_define_import_meta_env
|
|
16
|
+
} from "./token-goat-chunk-A37V4PBF.mjs";
|
|
17
|
+
|
|
18
|
+
// src/image_ocr.ts
|
|
19
|
+
init_define_import_meta_env();
|
|
20
|
+
import { spawn } from "node:child_process";
|
|
21
|
+
import { createHash } from "node:crypto";
|
|
22
|
+
import * as fs from "node:fs";
|
|
23
|
+
import { createRequire } from "node:module";
|
|
24
|
+
import * as path from "node:path";
|
|
25
|
+
|
|
26
|
+
// src/ocr_hashes.ts
|
|
27
|
+
init_define_import_meta_env();
|
|
28
|
+
function ocrLangPath(lang) {
|
|
29
|
+
return `https://cdn.jsdelivr.net/npm/@tesseract.js-data/${lang}@1.0.0/4.0.0_best_int`;
|
|
30
|
+
}
|
|
31
|
+
var OCR_LANG_HASHES = {
|
|
32
|
+
eng: "5dc5d8d640a212c9d6184921ba103b186f50e0fed9ee716c53e6b312b400d747",
|
|
33
|
+
fra: "bf83833fa957ff0076f6aa93f69e3bdf7b014dea829ea0c0d6be6b48a3ceef6d",
|
|
34
|
+
spa: "0062377729b81cc268b1822f09eb1c08c09f3f7f1c6b422540b51555a7eeea70",
|
|
35
|
+
deu: "a1b72cc25753eac167edfef5af4448a8dc34973503a2265c34c84520f896eb03",
|
|
36
|
+
ita: "36ec897f5f1f489b257801881286167a79b99a16040c6fbc7e3e30f03821b10b",
|
|
37
|
+
por: "42fab1f017aedab69b92bdecc01bbb11166cd3b177575612ee860f8e2825ece0",
|
|
38
|
+
nld: "363c360db9838838ff7ed3d8b885b33acc0d61d37165708303cf8a81e50164f3",
|
|
39
|
+
pol: "02b89cad819f1374631b4a3c92bdac79c214150f97a3686df78de6c8c30782db",
|
|
40
|
+
rus: "eb9be824435f6bb0f993925acb85fd842c8418d6db7613c818e749e619a1ad6d",
|
|
41
|
+
tur: "f0127d0f3745f9c65e2ae7ec6b23198fbe5aa186a61b35661426f5e1ef9dedd7",
|
|
42
|
+
swe: "a4c33cbd23d988c84b5f9d5d5b2417fc16da061e3dc7b16acb904a4566088b14",
|
|
43
|
+
ara: "e7d6494e2ef249ee97ad151eb01e0e6ae3aaf429256442ad6af534862a2a8c0f",
|
|
44
|
+
chi_sim: "9784f7c917c546424b690fcde708ce1f604a4393d08bb51ddab146d7d7c794e6",
|
|
45
|
+
chi_tra: "6abfb87cce5db0d09624f16eedd8a0b24173718856121f721b6d1214193d4dab",
|
|
46
|
+
jpn: "1a0175291ea145d4a66be681d1084496f10af938aacab247c5d40b31359a604e",
|
|
47
|
+
kor: "ec1749377d49ac38fb3d3cd05dd5e2a53359d329f359762bc638a81132109992"
|
|
48
|
+
};
|
|
49
|
+
var SUPPORTED_OCR_LANGS = Object.fromEntries(
|
|
50
|
+
Object.entries(OCR_LANG_HASHES).map(([lang, sha256]) => [lang, { langPath: ocrLangPath(lang), sha256 }])
|
|
51
|
+
);
|
|
52
|
+
function getOcrLangSpec(lang) {
|
|
53
|
+
const tokens = lang.split("+");
|
|
54
|
+
const primary = tokens.find((t) => t !== DEFAULT_OCR_LANG && isSupportedOcrLang(t)) ?? tokens[0] ?? DEFAULT_OCR_LANG;
|
|
55
|
+
const active = isSupportedOcrLang(primary) ? primary : DEFAULT_OCR_LANG;
|
|
56
|
+
const sha256 = OCR_LANG_HASHES[active];
|
|
57
|
+
return {
|
|
58
|
+
langPath: ocrLangPath(active),
|
|
59
|
+
sha256
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/image_ocr.ts
|
|
64
|
+
var _ocrTimeoutMs = 12e3;
|
|
65
|
+
function setOcrTimeoutForTesting(ms) {
|
|
66
|
+
_ocrTimeoutMs = ms ?? 12e3;
|
|
67
|
+
}
|
|
68
|
+
function getActiveOcrLangs(lang) {
|
|
69
|
+
if (typeof lang === "string" && lang.trim()) {
|
|
70
|
+
return resolveOcrLangs(lang);
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const configured = loadConfig().image_shrink?.ocr_lang;
|
|
74
|
+
if (typeof configured === "string" && configured.trim()) {
|
|
75
|
+
return resolveOcrLangs(configured);
|
|
76
|
+
}
|
|
77
|
+
} catch {
|
|
78
|
+
}
|
|
79
|
+
return [DEFAULT_OCR_LANG];
|
|
80
|
+
}
|
|
81
|
+
function resolveOcrLang(lang) {
|
|
82
|
+
return getActiveOcrLangs(lang).join("+");
|
|
83
|
+
}
|
|
84
|
+
var OCR_LANG_FILE = `${DEFAULT_OCR_LANG}.traineddata`;
|
|
85
|
+
var OCR_LANG_PATH = getOcrLangSpec("eng").langPath;
|
|
86
|
+
var OCR_LANG_SHA256 = getOcrLangSpec("eng").sha256;
|
|
87
|
+
function verifyOcrLangCache(lang) {
|
|
88
|
+
const activeLangs = getActiveOcrLangs(lang);
|
|
89
|
+
let anyAbsent = false;
|
|
90
|
+
for (const l of activeLangs) {
|
|
91
|
+
const spec = getOcrLangSpec(l);
|
|
92
|
+
const file = path.join(ocrCacheDir(), `${l}.traineddata`);
|
|
93
|
+
try {
|
|
94
|
+
if (!fs.existsSync(file)) {
|
|
95
|
+
anyAbsent = true;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const bytes = fs.readFileSync(file);
|
|
99
|
+
if (createHash("sha256").update(bytes).digest("hex") !== spec.sha256) {
|
|
100
|
+
return "mismatch";
|
|
101
|
+
}
|
|
102
|
+
} catch {
|
|
103
|
+
return "unreadable";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return anyAbsent ? "absent" : "ok";
|
|
107
|
+
}
|
|
108
|
+
function quarantineOcrLangCache(lang) {
|
|
109
|
+
const activeLangs = getActiveOcrLangs(lang);
|
|
110
|
+
for (const l of activeLangs) {
|
|
111
|
+
const spec = getOcrLangSpec(l);
|
|
112
|
+
const file = path.join(ocrCacheDir(), `${l}.traineddata`);
|
|
113
|
+
try {
|
|
114
|
+
if (fs.existsSync(file)) {
|
|
115
|
+
const bytes = fs.readFileSync(file);
|
|
116
|
+
if (createHash("sha256").update(bytes).digest("hex") !== spec.sha256) {
|
|
117
|
+
fs.rmSync(file, { force: true });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} catch {
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
var _ocrIntegrityFailed = false;
|
|
125
|
+
function ocrIntegrityFailed() {
|
|
126
|
+
return _ocrIntegrityFailed;
|
|
127
|
+
}
|
|
128
|
+
function ocrBlockedOffline(lang) {
|
|
129
|
+
if (!loadConfig().network.offline) return false;
|
|
130
|
+
return verifyOcrLangCache(lang) === "absent";
|
|
131
|
+
}
|
|
132
|
+
function ocrCacheDir() {
|
|
133
|
+
return path.join(tokenGoatHome(), "ocr-cache");
|
|
134
|
+
}
|
|
135
|
+
function ensureOcrCacheDir() {
|
|
136
|
+
try {
|
|
137
|
+
ensureDirSync(ocrCacheDir());
|
|
138
|
+
} catch {
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
var _require = createRequire(import.meta.url);
|
|
142
|
+
var _tesseractEntryPath;
|
|
143
|
+
function resolveTesseractEntry() {
|
|
144
|
+
if (_tesseractEntryPath !== void 0) return _tesseractEntryPath;
|
|
145
|
+
try {
|
|
146
|
+
_tesseractEntryPath = _require.resolve("tesseract.js");
|
|
147
|
+
} catch {
|
|
148
|
+
_tesseractEntryPath = null;
|
|
149
|
+
}
|
|
150
|
+
return _tesseractEntryPath;
|
|
151
|
+
}
|
|
152
|
+
function isOcrEngineAvailable() {
|
|
153
|
+
return !_ocrUnavailableThisProcess && resolveTesseractEntry() !== null;
|
|
154
|
+
}
|
|
155
|
+
function setTesseractEntryForTesting(entryPath) {
|
|
156
|
+
_tesseractEntryPath = entryPath;
|
|
157
|
+
}
|
|
158
|
+
var _ocrUnavailableThisProcess = false;
|
|
159
|
+
function resetOcrStateForTesting() {
|
|
160
|
+
_ocrUnavailableThisProcess = false;
|
|
161
|
+
_ocrIntegrityFailed = false;
|
|
162
|
+
_tesseractEntryPath = void 0;
|
|
163
|
+
_ocrTimeoutMs = 12e3;
|
|
164
|
+
}
|
|
165
|
+
function buildChildScript(entryPath, cacheDir, lang) {
|
|
166
|
+
const activeLangs = getActiveOcrLangs(lang);
|
|
167
|
+
const activeLangString = activeLangs.join("+");
|
|
168
|
+
const spec = getOcrLangSpec(lang ?? activeLangString);
|
|
169
|
+
return [
|
|
170
|
+
`const { createWorker } = require(${JSON.stringify(entryPath)});`,
|
|
171
|
+
"const chunks = [];",
|
|
172
|
+
"process.stdin.on('data', (c) => chunks.push(c));",
|
|
173
|
+
"process.stdin.on('end', async () => {",
|
|
174
|
+
" try {",
|
|
175
|
+
" const buf = Buffer.concat(chunks);",
|
|
176
|
+
` const worker = await createWorker(${JSON.stringify(activeLangString)}, 1, { cachePath: ${JSON.stringify(cacheDir)}, langPath: ${JSON.stringify(spec.langPath)}, errorHandler: () => {} });`,
|
|
177
|
+
" const { data } = await worker.recognize(buf);",
|
|
178
|
+
" process.stdout.write(JSON.stringify({ text: data.text || '', confidence: data.confidence || 0 }));",
|
|
179
|
+
" await worker.terminate();",
|
|
180
|
+
" process.exit(0);",
|
|
181
|
+
" } catch (e) {",
|
|
182
|
+
" process.exit(1);",
|
|
183
|
+
" }",
|
|
184
|
+
"});"
|
|
185
|
+
].join("\n");
|
|
186
|
+
}
|
|
187
|
+
async function ocrImage(input, lang) {
|
|
188
|
+
if (_ocrUnavailableThisProcess) return null;
|
|
189
|
+
const activeLangs = getActiveOcrLangs(lang);
|
|
190
|
+
const entryPath = resolveTesseractEntry();
|
|
191
|
+
if (entryPath === null) return null;
|
|
192
|
+
if (ocrBlockedOffline(lang)) return null;
|
|
193
|
+
if (_ocrIntegrityFailed) return null;
|
|
194
|
+
if (verifyOcrLangCache(lang) === "mismatch") {
|
|
195
|
+
_ocrIntegrityFailed = true;
|
|
196
|
+
quarantineOcrLangCache(lang);
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
for (const l of activeLangs) {
|
|
200
|
+
if (l !== DEFAULT_OCR_LANG) {
|
|
201
|
+
const file = path.join(ocrCacheDir(), `${l}.traineddata`);
|
|
202
|
+
if (!fs.existsSync(file)) {
|
|
203
|
+
if (loadConfig().network.offline) return null;
|
|
204
|
+
try {
|
|
205
|
+
const res = await fetch(getOcrLangSpec(l).langPath + `/${l}.traineddata.gz`);
|
|
206
|
+
if (!res.ok) return null;
|
|
207
|
+
const gz = Buffer.from(await res.arrayBuffer());
|
|
208
|
+
const unzipped = (await import("node:zlib")).gunzipSync(gz);
|
|
209
|
+
if (createHash("sha256").update(unzipped).digest("hex") !== getOcrLangSpec(l).sha256) {
|
|
210
|
+
_ocrIntegrityFailed = true;
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
ensureOcrCacheDir();
|
|
214
|
+
fs.writeFileSync(file, unzipped);
|
|
215
|
+
} catch {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return new Promise((resolve) => {
|
|
222
|
+
let settled = false;
|
|
223
|
+
let child;
|
|
224
|
+
try {
|
|
225
|
+
ensureOcrCacheDir();
|
|
226
|
+
child = spawn(process.execPath, ["-e", buildChildScript(entryPath, ocrCacheDir(), lang)], {
|
|
227
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
228
|
+
});
|
|
229
|
+
} catch {
|
|
230
|
+
_ocrUnavailableThisProcess = true;
|
|
231
|
+
resolve(null);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const chunks = [];
|
|
235
|
+
const finish = (result, subprocessBroken) => {
|
|
236
|
+
if (settled) return;
|
|
237
|
+
settled = true;
|
|
238
|
+
clearTimeout(timer);
|
|
239
|
+
if (subprocessBroken) _ocrUnavailableThisProcess = true;
|
|
240
|
+
try {
|
|
241
|
+
child.kill();
|
|
242
|
+
} catch {
|
|
243
|
+
}
|
|
244
|
+
resolve(result);
|
|
245
|
+
};
|
|
246
|
+
const timer = setTimeout(() => finish(null, true), _ocrTimeoutMs);
|
|
247
|
+
child.stdout?.on("data", (c) => chunks.push(c));
|
|
248
|
+
child.on("error", () => finish(null, true));
|
|
249
|
+
child.on("close", (code) => {
|
|
250
|
+
if (code !== 0) {
|
|
251
|
+
finish(null, false);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (verifyOcrLangCache(lang) === "mismatch") {
|
|
255
|
+
_ocrIntegrityFailed = true;
|
|
256
|
+
quarantineOcrLangCache(lang);
|
|
257
|
+
finish(null, false);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
const raw = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
262
|
+
const parsed = raw;
|
|
263
|
+
const text = typeof parsed.text === "string" ? parsed.text.trim() : "";
|
|
264
|
+
const confidence = typeof parsed.confidence === "number" ? parsed.confidence : 0;
|
|
265
|
+
finish({ text, confidence }, false);
|
|
266
|
+
} catch {
|
|
267
|
+
finish(null, false);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
try {
|
|
271
|
+
child.stdin?.end(input);
|
|
272
|
+
} catch {
|
|
273
|
+
finish(null, false);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
function isTextHeavy(result, minConfidence, minChars = 40) {
|
|
278
|
+
return result.confidence >= minConfidence && result.text.length >= minChars;
|
|
279
|
+
}
|
|
280
|
+
function formatOcrSummary(result, subject, originalBytes) {
|
|
281
|
+
const kb = Math.round(originalBytes / 1024);
|
|
282
|
+
const summary = `token-goat OCR'd ${subject} instead of shrinking it: text-heavy image detected (${Math.round(result.confidence)}% confidence), extracted ${result.text.length} chars of text from ${kb}kb of pixels.`;
|
|
283
|
+
return `${summary}
|
|
284
|
+
|
|
285
|
+
${fenceUntrustedOcrText(result.text)}`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export {
|
|
289
|
+
SUPPORTED_OCR_LANGS,
|
|
290
|
+
getOcrLangSpec,
|
|
291
|
+
setOcrTimeoutForTesting,
|
|
292
|
+
getActiveOcrLangs,
|
|
293
|
+
resolveOcrLang,
|
|
294
|
+
OCR_LANG_FILE,
|
|
295
|
+
OCR_LANG_PATH,
|
|
296
|
+
OCR_LANG_SHA256,
|
|
297
|
+
verifyOcrLangCache,
|
|
298
|
+
quarantineOcrLangCache,
|
|
299
|
+
ocrIntegrityFailed,
|
|
300
|
+
ocrBlockedOffline,
|
|
301
|
+
isOcrEngineAvailable,
|
|
302
|
+
setTesseractEntryForTesting,
|
|
303
|
+
resetOcrStateForTesting,
|
|
304
|
+
buildChildScript,
|
|
305
|
+
ocrImage,
|
|
306
|
+
isTextHeavy,
|
|
307
|
+
formatOcrSummary
|
|
308
|
+
};
|