zero-vision 0.1.0
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/LICENSE +202 -0
- package/NOTICE +5 -0
- package/README.md +163 -0
- package/dist/cdp/attach.js +121 -0
- package/dist/cdp/attach.js.map +1 -0
- package/dist/cdp/client.js +53 -0
- package/dist/cdp/client.js.map +1 -0
- package/dist/cdp/extract.js +197 -0
- package/dist/cdp/extract.js.map +1 -0
- package/dist/cli.js +223 -0
- package/dist/cli.js.map +1 -0
- package/dist/engines/apple-fm.js +103 -0
- package/dist/engines/apple-fm.js.map +1 -0
- package/dist/engines/apple-vision.js +89 -0
- package/dist/engines/apple-vision.js.map +1 -0
- package/dist/engines/cloud/index.js +41 -0
- package/dist/engines/cloud/index.js.map +1 -0
- package/dist/engines/index.js +71 -0
- package/dist/engines/index.js.map +1 -0
- package/dist/engines/local-vlm.js +46 -0
- package/dist/engines/local-vlm.js.map +1 -0
- package/dist/engines/tesseract.js +221 -0
- package/dist/engines/tesseract.js.map +1 -0
- package/dist/mcp.js +225 -0
- package/dist/mcp.js.map +1 -0
- package/dist/native.js +66 -0
- package/dist/native.js.map +1 -0
- package/dist/snap.js +30 -0
- package/dist/snap.js.map +1 -0
- package/dist/test/argv.test.js +22 -0
- package/dist/test/argv.test.js.map +1 -0
- package/dist/test/ax.test.js +28 -0
- package/dist/test/ax.test.js.map +1 -0
- package/dist/test/engines.test.js +81 -0
- package/dist/test/engines.test.js.map +1 -0
- package/dist/test/fetch.test.js +46 -0
- package/dist/test/fetch.test.js.map +1 -0
- package/dist/test/ocr.test.js +34 -0
- package/dist/test/ocr.test.js.map +1 -0
- package/dist/test/parity.test.js +33 -0
- package/dist/test/parity.test.js.map +1 -0
- package/dist/test/port.test.js +55 -0
- package/dist/test/port.test.js.map +1 -0
- package/dist/test/snap.test.js +13 -0
- package/dist/test/snap.test.js.map +1 -0
- package/dist/test/tesseract.test.js +17 -0
- package/dist/test/tesseract.test.js.map +1 -0
- package/docs/spec.md +549 -0
- package/package.json +40 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apple-vision.js","sourceRoot":"","sources":["../../src/engines/apple-vision.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACrF,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAG3C,SAAS,SAAS,CAAC,MAAc;IAC/B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IACtE,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAqB,CAAC;QACjD,OAAO,EAAE,GAAG,GAAG,EAAE,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,YAAY,EAAE,CAAC;IAC5E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,cAAc;YACtB,IAAI,EAAE,YAAY;YAClB,IAAI,EAAE,EAAE;YACR,EAAE,EAAE,CAAC;YACL,KAAK,EAAE,6BAA6B,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE;SACzD,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAW;IACjC,EAAE,EAAE,cAAc;IAClB,YAAY,EAAE,CAAC,YAAY,CAAC;IAC5B,KAAK,CAAC,QAAQ,CAAC,KAAoB;QACjC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,YAAY,CAAC;QACxC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,UAAU,CAAC;QACxC,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC;QACtC,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC;QAErD,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YAC/B,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,WAAW,CAAC,CAAC,eAAe,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;YACzG,OAAO,SAAS,CAAC,MAAM,IAAI,MAAM,CAAC,CAAC;QACrC,CAAC;QAED,IAAI,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;QACtB,IAAI,GAAuB,CAAC;QAC5B,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YAChB,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;gBAC1C,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAC;YACpG,CAAC;YACD,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;YAC1C,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;YAC3B,aAAa,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QACnC,CAAC;QACD,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC;QAC9F,CAAC;QACD,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,IAAI,CAAC;gBACH,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;gBAC1B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;oBAC7B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC;gBAC9F,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC;YAC9F,CAAC;QACH,CAAC;QACD,IAAI,CAAC;YACH,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC3B,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,EAAE,IAAI,IAAI,OAAO,CAAC;gBAC1C,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,QAAQ,IAAI,CAAC,CAAC,CAAC;gBACpD,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC;gBACvD,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,WAAW,CAC1C;oBACE,WAAW;oBACX,SAAS;oBACT,IAAI;oBACJ,QAAQ;oBACR,IAAI;oBACJ,YAAY;oBACZ,QAAQ;oBACR,cAAc;oBACd,SAAS;oBACT,SAAS;oBACT,KAAK;oBACL,GAAG,QAAQ;oBACX,QAAQ;iBACT,EACD,EAAE,SAAS,EAAE,OAAO,EAAE,CACvB,CAAC;gBACF,OAAO,SAAS,CAAC,MAAM,IAAI,MAAM,CAAC,CAAC;YACrC,CAAC;YACD,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,WAAW,CAAC,CAAC,WAAW,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;YACtH,OAAO,SAAS,CAAC,MAAM,IAAI,MAAM,CAAC,CAAC;QACrC,CAAC;gBAAS,CAAC;YACT,IAAI,GAAG;gBAAE,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { loadConfig } from "../index.js";
|
|
2
|
+
export const cloudVlm = {
|
|
3
|
+
id: "cloud-vlm",
|
|
4
|
+
capabilities: ["transcribe", "describe"],
|
|
5
|
+
async perceive(input) {
|
|
6
|
+
const task = input.task ?? "describe";
|
|
7
|
+
const cfg = loadConfig().engines?.["cloud-vlm"] ?? {};
|
|
8
|
+
const keyEnv = cfg.apiKeyEnv ?? "GEMINI_API_KEY";
|
|
9
|
+
if (!process.env[keyEnv]) {
|
|
10
|
+
return {
|
|
11
|
+
ok: false,
|
|
12
|
+
engine: "cloud-vlm",
|
|
13
|
+
task,
|
|
14
|
+
text: "",
|
|
15
|
+
ms: 0,
|
|
16
|
+
error: `cloud-vlm refused: ${keyEnv} is unset`,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
if (input.bytes && input.bytes.length > 10 * 1024 * 1024) {
|
|
20
|
+
return {
|
|
21
|
+
ok: false,
|
|
22
|
+
engine: "cloud-vlm",
|
|
23
|
+
task,
|
|
24
|
+
text: "",
|
|
25
|
+
ms: 0,
|
|
26
|
+
error: "cloud-vlm refuses inputs over 10 MB",
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
process.stderr.write(`engine=cloud-vlm model=${cfg.model ?? "unset"} bytes=${input.bytes?.length ?? "path"}\n`);
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
engine: "cloud-vlm",
|
|
33
|
+
task,
|
|
34
|
+
text: "",
|
|
35
|
+
ms: 0,
|
|
36
|
+
model: cfg.model,
|
|
37
|
+
error: "cloud-vlm is opt-in and not a silent fallback. Provider HTTP is not called in v1 until a provider SDK is an optional peerDependency. Key is present; wire the POST in a follow-up.",
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/engines/cloud/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,CAAC,MAAM,QAAQ,GAAW;IAC9B,EAAE,EAAE,WAAW;IACf,YAAY,EAAE,CAAC,YAAY,EAAE,UAAU,CAAC;IACxC,KAAK,CAAC,QAAQ,CAAC,KAAoB;QACjC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,UAAU,CAAC;QACtC,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC,OAAO,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;QACtD,MAAM,MAAM,GAAG,GAAG,CAAC,SAAS,IAAI,gBAAgB,CAAC;QACjD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACzB,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,WAAW;gBACnB,IAAI;gBACJ,IAAI,EAAE,EAAE;gBACR,EAAE,EAAE,CAAC;gBACL,KAAK,EAAE,sBAAsB,MAAM,WAAW;aAC/C,CAAC;QACJ,CAAC;QACD,IAAI,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;YACzD,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,WAAW;gBACnB,IAAI;gBACJ,IAAI,EAAE,EAAE;gBACR,EAAE,EAAE,CAAC;gBACL,KAAK,EAAE,qCAAqC;aAC7C,CAAC;QACJ,CAAC;QACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,0BAA0B,GAAG,CAAC,KAAK,IAAI,OAAO,UAAU,KAAK,CAAC,KAAK,EAAE,MAAM,IAAI,MAAM,IAAI,CAC1F,CAAC;QACF,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,WAAW;YACnB,IAAI;YACJ,IAAI,EAAE,EAAE;YACR,EAAE,EAAE,CAAC;YACL,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,KAAK,EACH,oLAAoL;SACvL,CAAC;IACJ,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
const ENGINES = ["apple-vision", "apple-fm", "local-vlm", "cloud-vlm", "tesseract"];
|
|
5
|
+
export function isEngineId(s) {
|
|
6
|
+
return ENGINES.includes(s);
|
|
7
|
+
}
|
|
8
|
+
export function loadConfig() {
|
|
9
|
+
const path = join(homedir(), ".config", "zero-vision", "config.json");
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function resolveEngine(cli) {
|
|
18
|
+
if (cli && isEngineId(cli))
|
|
19
|
+
return cli;
|
|
20
|
+
if (cli)
|
|
21
|
+
throw new Error(`unknown engine: ${cli}`);
|
|
22
|
+
const env = process.env.ZEROVISION_ENGINE;
|
|
23
|
+
if (env && isEngineId(env))
|
|
24
|
+
return env;
|
|
25
|
+
const cfg = loadConfig().engine;
|
|
26
|
+
if (cfg && isEngineId(cfg))
|
|
27
|
+
return cfg;
|
|
28
|
+
// Linux has no Apple frameworks: tesseract is the local default there.
|
|
29
|
+
// Same on Intel Macs, where the arm64 native binary cannot run.
|
|
30
|
+
if (process.platform === "linux")
|
|
31
|
+
return "tesseract";
|
|
32
|
+
if (process.platform === "darwin" && process.arch === "x64")
|
|
33
|
+
return "tesseract";
|
|
34
|
+
return "apple-vision";
|
|
35
|
+
}
|
|
36
|
+
export async function perceive(engine, input) {
|
|
37
|
+
const task = input.task ?? "transcribe";
|
|
38
|
+
if (engine === "apple-vision" && task === "describe") {
|
|
39
|
+
return {
|
|
40
|
+
ok: false,
|
|
41
|
+
engine,
|
|
42
|
+
task,
|
|
43
|
+
text: "",
|
|
44
|
+
ms: 0,
|
|
45
|
+
error: "engine apple-vision cannot describe; use apple-fm, local-vlm, or cloud-vlm",
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
switch (engine) {
|
|
49
|
+
case "apple-vision": {
|
|
50
|
+
const { appleVision } = await import("./apple-vision.js");
|
|
51
|
+
return appleVision.perceive(input);
|
|
52
|
+
}
|
|
53
|
+
case "apple-fm": {
|
|
54
|
+
const { appleFm } = await import("./apple-fm.js");
|
|
55
|
+
return appleFm.perceive(input);
|
|
56
|
+
}
|
|
57
|
+
case "local-vlm": {
|
|
58
|
+
const { localVlm } = await import("./local-vlm.js");
|
|
59
|
+
return localVlm.perceive(input);
|
|
60
|
+
}
|
|
61
|
+
case "cloud-vlm": {
|
|
62
|
+
const { cloudVlm } = await import("./cloud/index.js");
|
|
63
|
+
return cloudVlm.perceive(input);
|
|
64
|
+
}
|
|
65
|
+
case "tesseract": {
|
|
66
|
+
const { tesseract } = await import("./tesseract.js");
|
|
67
|
+
return tesseract.perceive(input);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/engines/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { loadConfig } from "./index.js";
|
|
5
|
+
const DEFAULT_MODEL = join(homedir(), ".cache/huggingface/hub/models--mlx-community--Qwen3-VL-8B-Instruct-4bit");
|
|
6
|
+
export const localVlm = {
|
|
7
|
+
id: "local-vlm",
|
|
8
|
+
capabilities: ["transcribe", "describe"],
|
|
9
|
+
async perceive(input) {
|
|
10
|
+
const task = input.task ?? "describe";
|
|
11
|
+
const cfg = loadConfig().engines?.["local-vlm"]?.modelPath ?? DEFAULT_MODEL;
|
|
12
|
+
const modelPath = cfg.startsWith("~") ? join(homedir(), cfg.slice(1)) : cfg;
|
|
13
|
+
if (!existsSync(modelPath)) {
|
|
14
|
+
return {
|
|
15
|
+
ok: false,
|
|
16
|
+
engine: "local-vlm",
|
|
17
|
+
task,
|
|
18
|
+
text: "",
|
|
19
|
+
ms: 0,
|
|
20
|
+
error: `local-vlm weights missing at ${modelPath}`,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
if (input.kind === "video") {
|
|
24
|
+
return {
|
|
25
|
+
ok: false,
|
|
26
|
+
engine: "local-vlm",
|
|
27
|
+
task,
|
|
28
|
+
text: "",
|
|
29
|
+
ms: 0,
|
|
30
|
+
error: "local-vlm does not take video in v1; extract a frame first",
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
// Runner is opt-in and unflown. Fail closed rather than shelling a 5 GB model
|
|
34
|
+
// from a test or an accidental default.
|
|
35
|
+
return {
|
|
36
|
+
ok: false,
|
|
37
|
+
engine: "local-vlm",
|
|
38
|
+
task,
|
|
39
|
+
text: "",
|
|
40
|
+
ms: 0,
|
|
41
|
+
model: "Qwen3-VL-8B-Instruct-4bit",
|
|
42
|
+
error: "local-vlm is wired for presence-check only in v1. Weights are on disk. Set ZEROVISION_LIVE_VLM=1 in a later release to spawn the MLX runner. Use apple-vision to transcribe.",
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=local-vlm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-vlm.js","sourceRoot":"","sources":["../../src/engines/local-vlm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,MAAM,aAAa,GAAG,IAAI,CACxB,OAAO,EAAE,EACT,yEAAyE,CAC1E,CAAC;AAEF,MAAM,CAAC,MAAM,QAAQ,GAAW;IAC9B,EAAE,EAAE,WAAW;IACf,YAAY,EAAE,CAAC,YAAY,EAAE,UAAU,CAAC;IACxC,KAAK,CAAC,QAAQ,CAAC,KAAoB;QACjC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,UAAU,CAAC;QACtC,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC,OAAO,EAAE,CAAC,WAAW,CAAC,EAAE,SAAS,IAAI,aAAa,CAAC;QAC5E,MAAM,SAAS,GAAG,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;QAC5E,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC3B,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,WAAW;gBACnB,IAAI;gBACJ,IAAI,EAAE,EAAE;gBACR,EAAE,EAAE,CAAC;gBACL,KAAK,EAAE,gCAAgC,SAAS,EAAE;aACnD,CAAC;QACJ,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC3B,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,WAAW;gBACnB,IAAI;gBACJ,IAAI,EAAE,EAAE;gBACR,EAAE,EAAE,CAAC;gBACL,KAAK,EAAE,4DAA4D;aACpE,CAAC;QACJ,CAAC;QACD,8EAA8E;QAC9E,wCAAwC;QACxC,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,WAAW;YACnB,IAAI;YACJ,IAAI,EAAE,EAAE;YACR,EAAE,EAAE,CAAC;YACL,KAAK,EAAE,2BAA2B;YAClC,KAAK,EACH,8KAA8K;SACjL,CAAC;IACJ,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
// BCP-47 (or bare) -> tesseract traineddata code. Small map, "eng" fallback.
|
|
8
|
+
const LANG_MAP = {
|
|
9
|
+
en: "eng",
|
|
10
|
+
ja: "jpn",
|
|
11
|
+
zh: "chi_sim",
|
|
12
|
+
"zh-Hans": "chi_sim",
|
|
13
|
+
"zh-Hant": "chi_tra",
|
|
14
|
+
ko: "kor",
|
|
15
|
+
fr: "fra",
|
|
16
|
+
de: "deu",
|
|
17
|
+
es: "spa",
|
|
18
|
+
it: "ita",
|
|
19
|
+
pt: "por",
|
|
20
|
+
ru: "rus",
|
|
21
|
+
};
|
|
22
|
+
function toTessLang(langs) {
|
|
23
|
+
const mapped = langs.map((l) => LANG_MAP[l] ?? LANG_MAP[l.split("-")[0]] ?? "eng");
|
|
24
|
+
return [...new Set(mapped)].join("+");
|
|
25
|
+
}
|
|
26
|
+
async function which(bin) {
|
|
27
|
+
try {
|
|
28
|
+
const { stdout } = await execFileAsync(process.platform === "win32" ? "where" : "which", [bin]);
|
|
29
|
+
return stdout.trim().split("\n")[0] || null;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function fail(task, error) {
|
|
36
|
+
return { ok: false, engine: "tesseract", task: task ?? "transcribe", text: "", ms: 0, error };
|
|
37
|
+
}
|
|
38
|
+
async function ocrSystemTesseract(imagePath, tessLang) {
|
|
39
|
+
// tsv: level word conf left top width height text (header line first)
|
|
40
|
+
const { stdout } = await execFileAsync("tesseract", [imagePath, "stdout", "-l", tessLang, "--psm", "3", "tsv"], {
|
|
41
|
+
timeout: 120_000,
|
|
42
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
43
|
+
});
|
|
44
|
+
const lines = stdout.trim().split("\n").slice(1);
|
|
45
|
+
const blocks = [];
|
|
46
|
+
const words = [];
|
|
47
|
+
for (const line of lines) {
|
|
48
|
+
const cols = line.split("\t");
|
|
49
|
+
if (cols.length < 12 || cols[0] !== "5")
|
|
50
|
+
continue; // word level only
|
|
51
|
+
const conf = Number(cols[10]);
|
|
52
|
+
const text = (cols[11] ?? "").trim();
|
|
53
|
+
if (!text)
|
|
54
|
+
continue;
|
|
55
|
+
words.push(text);
|
|
56
|
+
blocks.push({
|
|
57
|
+
text,
|
|
58
|
+
confidence: Number.isFinite(conf) ? conf / 100 : undefined,
|
|
59
|
+
bbox: { x: Number(cols[6]), y: Number(cols[7]), w: Number(cols[8]), h: Number(cols[9]) },
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return { text: words.join(" "), blocks };
|
|
63
|
+
}
|
|
64
|
+
async function ocrWasm(image, tessLang) {
|
|
65
|
+
const { createWorker } = await import("tesseract.js");
|
|
66
|
+
const worker = await createWorker(tessLang.split("+")[0] || "eng");
|
|
67
|
+
try {
|
|
68
|
+
const { data } = await worker.recognize(image);
|
|
69
|
+
// v6 Page shape: blocks[] -> paragraphs[] -> lines[] -> words[]
|
|
70
|
+
const out = [];
|
|
71
|
+
for (const b of data.blocks ?? []) {
|
|
72
|
+
for (const p of b.paragraphs ?? []) {
|
|
73
|
+
for (const l of p.lines ?? []) {
|
|
74
|
+
for (const w of l.words ?? []) {
|
|
75
|
+
const t = (w.text ?? "").trim();
|
|
76
|
+
if (!t)
|
|
77
|
+
continue;
|
|
78
|
+
out.push({
|
|
79
|
+
text: t,
|
|
80
|
+
confidence: typeof w.confidence === "number" ? w.confidence / 100 : undefined,
|
|
81
|
+
bbox: w.bbox ? { x: w.bbox.x0, y: w.bbox.y0, w: w.bbox.x1 - w.bbox.x0, h: w.bbox.y1 - w.bbox.y0 } : undefined,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { text: (data.text ?? "").trim().replace(/\s+/g, " "), blocks: out };
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
await worker.terminate();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function maybeDownsample(srcPath, level, tmp) {
|
|
94
|
+
if (level !== "fast")
|
|
95
|
+
return srcPath;
|
|
96
|
+
if (!(await which("ffmpeg")))
|
|
97
|
+
return srcPath;
|
|
98
|
+
const out = join(tmp, "fast.png");
|
|
99
|
+
await execFileAsync("ffmpeg", ["-y", "-v", "error", "-i", srcPath, "-vf", "scale='min(1600,iw)':-2", out], {
|
|
100
|
+
timeout: 60_000,
|
|
101
|
+
});
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
export const tesseract = {
|
|
105
|
+
id: "tesseract",
|
|
106
|
+
capabilities: ["transcribe"],
|
|
107
|
+
async perceive(input) {
|
|
108
|
+
const t0 = Date.now();
|
|
109
|
+
const done = (text, blocks) => ({
|
|
110
|
+
ok: true,
|
|
111
|
+
engine: "tesseract",
|
|
112
|
+
task: "transcribe",
|
|
113
|
+
text,
|
|
114
|
+
blocks,
|
|
115
|
+
ms: Date.now() - t0,
|
|
116
|
+
});
|
|
117
|
+
if ((input.task ?? "transcribe") === "describe") {
|
|
118
|
+
return {
|
|
119
|
+
ok: false,
|
|
120
|
+
engine: "tesseract",
|
|
121
|
+
task: "describe",
|
|
122
|
+
text: "",
|
|
123
|
+
ms: 0,
|
|
124
|
+
error: "engine tesseract cannot describe; use apple-fm, local-vlm, or cloud-vlm",
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const tessLang = toTessLang(input.lang ?? ["en-US"]);
|
|
128
|
+
const level = input.level ?? "accurate";
|
|
129
|
+
const tmp = mkdtempSync(join(tmpdir(), "zrv-tess-"));
|
|
130
|
+
try {
|
|
131
|
+
if (input.kind === "clipboard") {
|
|
132
|
+
const grabber = (await which("wl-paste")) ? ["wl-paste", "--type", "image/png"] : await (async () => (await which("xclip")) ? ["xclip", "-selection", "clipboard", "-t", "image/png", "-o"] : null)();
|
|
133
|
+
if (!grabber) {
|
|
134
|
+
return fail(input.task, "clipboard unavailable: install wl-clipboard (Wayland) or xclip (X11)");
|
|
135
|
+
}
|
|
136
|
+
const out = join(tmp, "clip.png");
|
|
137
|
+
const { stdout } = await execFileAsync(grabber[0], grabber.slice(1), { encoding: "buffer", timeout: 30_000 });
|
|
138
|
+
if (!stdout.length)
|
|
139
|
+
return fail(input.task, "clipboard has no image");
|
|
140
|
+
writeFileSync(out, stdout);
|
|
141
|
+
return runImage(out, tessLang, level, tmp, done);
|
|
142
|
+
}
|
|
143
|
+
if (input.kind === "video")
|
|
144
|
+
return runVideo(input, tessLang, tmp, t0);
|
|
145
|
+
let path = input.path;
|
|
146
|
+
if (input.bytes) {
|
|
147
|
+
if (input.bytes.length > 30 * 1024 * 1024)
|
|
148
|
+
return fail(input.task, "input exceeds 30 MB");
|
|
149
|
+
path = join(tmp, "in.bin");
|
|
150
|
+
writeFileSync(path, input.bytes);
|
|
151
|
+
}
|
|
152
|
+
if (!path)
|
|
153
|
+
return fail(input.task, "path required");
|
|
154
|
+
return runImage(path, tessLang, level, tmp, done);
|
|
155
|
+
}
|
|
156
|
+
catch (e) {
|
|
157
|
+
return fail(input.task, e instanceof Error ? e.message.slice(0, 300) : String(e).slice(0, 300));
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
async function runImage(path, tessLang, level, tmp, done) {
|
|
165
|
+
const src = await maybeDownsample(path, level, tmp);
|
|
166
|
+
if (await which("tesseract")) {
|
|
167
|
+
const { text, blocks } = await ocrSystemTesseract(src, tessLang);
|
|
168
|
+
return done(text, blocks);
|
|
169
|
+
}
|
|
170
|
+
const { text, blocks } = await ocrWasm(src, tessLang);
|
|
171
|
+
return done(text, blocks);
|
|
172
|
+
}
|
|
173
|
+
async function runVideo(input, tessLang, tmp, t0) {
|
|
174
|
+
if (!input.path) {
|
|
175
|
+
return { ok: false, engine: "tesseract", task: "transcribe", text: "", ms: 0, error: "path required" };
|
|
176
|
+
}
|
|
177
|
+
if (!(await which("ffmpeg"))) {
|
|
178
|
+
return {
|
|
179
|
+
ok: false,
|
|
180
|
+
engine: "tesseract",
|
|
181
|
+
task: "transcribe",
|
|
182
|
+
text: "",
|
|
183
|
+
ms: 0,
|
|
184
|
+
error: "video needs ffmpeg: install it (apt install ffmpeg) or pick a single frame",
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
const mode = input.video?.mode ?? "scene";
|
|
188
|
+
const interval = input.video?.interval ?? 2;
|
|
189
|
+
const maxFrames = Math.min(input.video?.maxFrames ?? 60, 120);
|
|
190
|
+
const vf = mode === "all-idr" ? "select='eq(pict_type\\,I)'" : mode === "interval" ? `fps=1/${interval}` : "select='gt(scene\\,0.4)',mpdecimate";
|
|
191
|
+
const framesDir = join(tmp, "frames");
|
|
192
|
+
const { mkdirSync } = await import("node:fs");
|
|
193
|
+
mkdirSync(framesDir, { recursive: true });
|
|
194
|
+
await execFileAsync("ffmpeg", ["-y", "-v", "error", "-i", input.path, "-vf", `${vf},scale='min(1600,iw)':-2`, join(framesDir, "f%04d.png")], { timeout: 180_000 });
|
|
195
|
+
const { readdirSync, readFileSync } = await import("node:fs");
|
|
196
|
+
const frames = readdirSync(framesDir)
|
|
197
|
+
.filter((f) => f.endsWith(".png"))
|
|
198
|
+
.sort()
|
|
199
|
+
.slice(0, maxFrames);
|
|
200
|
+
const transcript = [];
|
|
201
|
+
const useSystem = await which("tesseract");
|
|
202
|
+
let i = 0;
|
|
203
|
+
for (const f of frames) {
|
|
204
|
+
const p = join(framesDir, f);
|
|
205
|
+
const { text } = useSystem
|
|
206
|
+
? await ocrSystemTesseract(p, tessLang)
|
|
207
|
+
: await ocrWasm(readFileSync(p), tessLang);
|
|
208
|
+
if (text.trim())
|
|
209
|
+
transcript.push({ t: mode === "interval" ? Math.round(i * interval) : i, text: text.trim() });
|
|
210
|
+
i++;
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
ok: true,
|
|
214
|
+
engine: "tesseract",
|
|
215
|
+
task: "transcribe",
|
|
216
|
+
text: transcript.map((s) => s.text).join("\n"),
|
|
217
|
+
transcript,
|
|
218
|
+
ms: Date.now() - t0,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=tesseract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tesseract.js","sourceRoot":"","sources":["../../src/engines/tesseract.ts"],"names":[],"mappings":"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"}
|
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { perceive, resolveEngine, isEngineId } from "./engines/index.js";
|
|
6
|
+
import { attach, findOpenPort, listTabs } from "./cdp/attach.js";
|
|
7
|
+
import { captureScreenshot, extractPage, fetchUrl } from "./cdp/extract.js";
|
|
8
|
+
const text = (s) => ({ content: [{ type: "text", text: s }] });
|
|
9
|
+
function engineOf(args) {
|
|
10
|
+
const e = args?.engine;
|
|
11
|
+
if (typeof e === "string") {
|
|
12
|
+
if (!isEngineId(e))
|
|
13
|
+
throw new Error(`unknown engine: ${e}`);
|
|
14
|
+
return e;
|
|
15
|
+
}
|
|
16
|
+
return resolveEngine();
|
|
17
|
+
}
|
|
18
|
+
function langOf(args) {
|
|
19
|
+
const l = args?.lang;
|
|
20
|
+
if (Array.isArray(l))
|
|
21
|
+
return l.filter((x) => typeof x === "string");
|
|
22
|
+
if (typeof l === "string")
|
|
23
|
+
return [l];
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
function levelOf(args) {
|
|
27
|
+
return args?.level === "fast" ? "fast" : args?.level === "accurate" ? "accurate" : undefined;
|
|
28
|
+
}
|
|
29
|
+
function portOf(args) {
|
|
30
|
+
return typeof args?.port === "number" && Number.isFinite(args.port) ? args.port : undefined;
|
|
31
|
+
}
|
|
32
|
+
export function listMcpTools() {
|
|
33
|
+
return [
|
|
34
|
+
{
|
|
35
|
+
name: "peek_tabs",
|
|
36
|
+
description: "List open debug-Chrome tabs. Prefer this over a screenshot when the goal is to read a page.",
|
|
37
|
+
inputSchema: { type: "object", properties: { port: { type: "number" } } },
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "peek_page",
|
|
41
|
+
description: "Read cleaned text or markdown from an attached Chrome tab or URL. Prefer this over a screenshot when the goal is to read text. Use apple-vision unless the user asked to describe.",
|
|
42
|
+
inputSchema: {
|
|
43
|
+
type: "object",
|
|
44
|
+
properties: {
|
|
45
|
+
targetId: { type: "string" },
|
|
46
|
+
url: { type: "string" },
|
|
47
|
+
format: { type: "string", enum: ["text", "markdown"], default: "text" },
|
|
48
|
+
navigate: { type: "boolean", default: false },
|
|
49
|
+
fetch: { type: "boolean", default: false },
|
|
50
|
+
ocrOpaque: { type: "boolean", default: false },
|
|
51
|
+
waitMs: { type: "number" },
|
|
52
|
+
waitText: { type: "string" },
|
|
53
|
+
scroll: { type: "boolean", default: false },
|
|
54
|
+
port: { type: "number" },
|
|
55
|
+
engine: { type: "string", enum: ["apple-vision", "apple-fm", "local-vlm", "cloud-vlm", "tesseract"] },
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: "peek_a11y",
|
|
61
|
+
description: "Accessibility tree (interesting-only YAML with uids) of an attached tab. Prefer over a screenshot.",
|
|
62
|
+
inputSchema: {
|
|
63
|
+
type: "object",
|
|
64
|
+
properties: {
|
|
65
|
+
targetId: { type: "string" },
|
|
66
|
+
url: { type: "string" },
|
|
67
|
+
navigate: { type: "boolean", default: false },
|
|
68
|
+
port: { type: "number" },
|
|
69
|
+
verbose: { type: "boolean" },
|
|
70
|
+
engine: { type: "string" },
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: "ocr_image",
|
|
76
|
+
description: "On-device OCR of a local image, clipboard, or base64. Prefer this over a screenshot when the goal is to read text. Default engine apple-vision.",
|
|
77
|
+
inputSchema: {
|
|
78
|
+
type: "object",
|
|
79
|
+
properties: {
|
|
80
|
+
path: { type: "string" },
|
|
81
|
+
base64: { type: "string" },
|
|
82
|
+
clipboard: { type: "boolean" },
|
|
83
|
+
engine: { type: "string", enum: ["apple-vision", "apple-fm", "local-vlm", "cloud-vlm", "tesseract"] },
|
|
84
|
+
task: { type: "string", enum: ["transcribe", "describe"], default: "transcribe" },
|
|
85
|
+
level: { type: "string", enum: ["accurate", "fast"] },
|
|
86
|
+
lang: { type: "array", items: { type: "string" } },
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: "ocr_video",
|
|
92
|
+
description: "Keyframe text transcript of a local video via apple-vision. Prefer over sending frames to a vision model.",
|
|
93
|
+
inputSchema: {
|
|
94
|
+
type: "object",
|
|
95
|
+
properties: {
|
|
96
|
+
path: { type: "string" },
|
|
97
|
+
mode: { type: "string", enum: ["scene", "interval", "all-idr"] },
|
|
98
|
+
interval: { type: "number" },
|
|
99
|
+
maxFrames: { type: "number" },
|
|
100
|
+
engine: { type: "string" },
|
|
101
|
+
task: { type: "string", enum: ["transcribe", "describe"], default: "transcribe" },
|
|
102
|
+
level: { type: "string", enum: ["accurate", "fast"] },
|
|
103
|
+
lang: { type: "array", items: { type: "string" } },
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
}
|
|
109
|
+
export async function startMcp() {
|
|
110
|
+
const server = new Server({ name: "zero-vision", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
111
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
112
|
+
tools: listMcpTools(),
|
|
113
|
+
}));
|
|
114
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
115
|
+
const args = (req.params.arguments ?? {});
|
|
116
|
+
switch (req.params.name) {
|
|
117
|
+
case "peek_tabs": {
|
|
118
|
+
const { port } = await findOpenPort(portOf(args));
|
|
119
|
+
const tabs = await listTabs(port);
|
|
120
|
+
return text(JSON.stringify({ port, tabs }, null, 2));
|
|
121
|
+
}
|
|
122
|
+
case "peek_page": {
|
|
123
|
+
if (args.fetch && typeof args.url === "string") {
|
|
124
|
+
const { text: t, markdown } = await fetchUrl(args.url);
|
|
125
|
+
return text(args.format === "markdown" ? markdown : t);
|
|
126
|
+
}
|
|
127
|
+
const attached = await attach({
|
|
128
|
+
port: portOf(args),
|
|
129
|
+
tab: typeof args.targetId === "string" ? args.targetId : undefined,
|
|
130
|
+
url: typeof args.url === "string" ? args.url : undefined,
|
|
131
|
+
navigate: Boolean(args.navigate),
|
|
132
|
+
});
|
|
133
|
+
try {
|
|
134
|
+
const extracted = await extractPage(attached.client, attached.sessionId, {
|
|
135
|
+
waitMs: typeof args.waitMs === "number" ? args.waitMs : undefined,
|
|
136
|
+
waitText: typeof args.waitText === "string" ? args.waitText : undefined,
|
|
137
|
+
scroll: Boolean(args.scroll),
|
|
138
|
+
});
|
|
139
|
+
let body = args.format === "markdown" ? extracted.markdown : extracted.text;
|
|
140
|
+
if (args.ocrOpaque && extracted.opaque.fraction >= 0.3) {
|
|
141
|
+
const png = await captureScreenshot(attached.client, attached.sessionId);
|
|
142
|
+
const ocr = await perceive(engineOf(args), { kind: "image", bytes: png, task: "transcribe" });
|
|
143
|
+
if (ocr.ok && ocr.text)
|
|
144
|
+
body += "\n--- opaque ocr ---\n" + ocr.text;
|
|
145
|
+
}
|
|
146
|
+
return text(JSON.stringify({
|
|
147
|
+
title: attached.title,
|
|
148
|
+
url: attached.url,
|
|
149
|
+
text: body,
|
|
150
|
+
opaque: extracted.opaque,
|
|
151
|
+
}));
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
attached.client.close();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
case "peek_a11y": {
|
|
158
|
+
const attached = await attach({
|
|
159
|
+
port: portOf(args),
|
|
160
|
+
tab: typeof args.targetId === "string" ? args.targetId : undefined,
|
|
161
|
+
url: typeof args.url === "string" ? args.url : undefined,
|
|
162
|
+
navigate: Boolean(args.navigate),
|
|
163
|
+
});
|
|
164
|
+
try {
|
|
165
|
+
const extracted = await extractPage(attached.client, attached.sessionId, {
|
|
166
|
+
verbose: Boolean(args.verbose),
|
|
167
|
+
});
|
|
168
|
+
return text(extracted.a11y);
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
attached.client.close();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
case "ocr_image": {
|
|
175
|
+
const engine = engineOf(args);
|
|
176
|
+
const task = args.task === "describe" ? "describe" : "transcribe";
|
|
177
|
+
const level = levelOf(args);
|
|
178
|
+
const lang = langOf(args);
|
|
179
|
+
if (args.clipboard) {
|
|
180
|
+
const r = await perceive(engine, { kind: "clipboard", task, level, lang });
|
|
181
|
+
return text(JSON.stringify(r));
|
|
182
|
+
}
|
|
183
|
+
if (typeof args.base64 === "string") {
|
|
184
|
+
const buf = Buffer.from(args.base64, "base64");
|
|
185
|
+
const r = await perceive(engine, { kind: "image", bytes: buf, task, level, lang });
|
|
186
|
+
return text(JSON.stringify(r));
|
|
187
|
+
}
|
|
188
|
+
if (typeof args.path !== "string")
|
|
189
|
+
throw new Error("path, base64, or clipboard required");
|
|
190
|
+
const r = await perceive(engine, { kind: "image", path: args.path, task, level, lang });
|
|
191
|
+
return text(JSON.stringify(r));
|
|
192
|
+
}
|
|
193
|
+
case "ocr_video": {
|
|
194
|
+
if (typeof args.path !== "string")
|
|
195
|
+
throw new Error("path required");
|
|
196
|
+
const engine = engineOf(args);
|
|
197
|
+
const r = await perceive(engine, {
|
|
198
|
+
kind: "video",
|
|
199
|
+
path: args.path,
|
|
200
|
+
task: args.task === "describe" ? "describe" : "transcribe",
|
|
201
|
+
level: levelOf(args),
|
|
202
|
+
lang: langOf(args),
|
|
203
|
+
video: {
|
|
204
|
+
mode: args.mode ?? "scene",
|
|
205
|
+
interval: typeof args.interval === "number" ? args.interval : 2,
|
|
206
|
+
maxFrames: typeof args.maxFrames === "number" ? args.maxFrames : 60,
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
return text(JSON.stringify(r));
|
|
210
|
+
}
|
|
211
|
+
default:
|
|
212
|
+
throw new Error(`unknown tool ${req.params.name}`);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
const transport = new StdioServerTransport();
|
|
216
|
+
await server.connect(transport);
|
|
217
|
+
}
|
|
218
|
+
const isMain = process.argv[1]?.endsWith("mcp.js") || process.argv[1]?.endsWith("mcp.ts");
|
|
219
|
+
if (isMain) {
|
|
220
|
+
startMcp().catch((err) => {
|
|
221
|
+
process.stderr.write(String(err) + "\n");
|
|
222
|
+
process.exit(1);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=mcp.js.map
|