@jungjaehoon/mama-os 0.26.0 → 0.27.1
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/CHANGELOG.md +22 -0
- package/README.md +1 -1
- package/dist/agent/agent-loop.d.ts.map +1 -1
- package/dist/agent/agent-loop.js +92 -9
- package/dist/agent/agent-loop.js.map +1 -1
- package/dist/agent/attachment-text-extractor.d.ts +2 -0
- package/dist/agent/attachment-text-extractor.d.ts.map +1 -0
- package/dist/agent/attachment-text-extractor.js +128 -0
- package/dist/agent/attachment-text-extractor.js.map +1 -0
- package/dist/agent/code-act/constants.d.ts.map +1 -1
- package/dist/agent/code-act/constants.js +17 -2
- package/dist/agent/code-act/constants.js.map +1 -1
- package/dist/agent/code-act/host-bridge.d.ts.map +1 -1
- package/dist/agent/code-act/host-bridge.js +78 -5
- package/dist/agent/code-act/host-bridge.js.map +1 -1
- package/dist/agent/code-act/index.d.ts +2 -2
- package/dist/agent/code-act/index.d.ts.map +1 -1
- package/dist/agent/code-act/index.js +3 -1
- package/dist/agent/code-act/index.js.map +1 -1
- package/dist/agent/code-act/sandbox.d.ts +6 -2
- package/dist/agent/code-act/sandbox.d.ts.map +1 -1
- package/dist/agent/code-act/sandbox.js +275 -34
- package/dist/agent/code-act/sandbox.js.map +1 -1
- package/dist/agent/code-act/tool-policy.d.ts +7 -1
- package/dist/agent/code-act/tool-policy.d.ts.map +1 -1
- package/dist/agent/code-act/tool-policy.js +28 -1
- package/dist/agent/code-act/tool-policy.js.map +1 -1
- package/dist/agent/code-act/types.d.ts +20 -0
- package/dist/agent/code-act/types.d.ts.map +1 -1
- package/dist/agent/code-act/types.js +5 -1
- package/dist/agent/code-act/types.js.map +1 -1
- package/dist/agent/codex-app-server-process.d.ts +4 -1
- package/dist/agent/codex-app-server-process.d.ts.map +1 -1
- package/dist/agent/codex-app-server-process.js +45 -7
- package/dist/agent/codex-app-server-process.js.map +1 -1
- package/dist/agent/drive-tools.d.ts +6 -2
- package/dist/agent/drive-tools.d.ts.map +1 -1
- package/dist/agent/drive-tools.js +17 -6
- package/dist/agent/drive-tools.js.map +1 -1
- package/dist/agent/gateway-tool-executor.d.ts +10 -3
- package/dist/agent/gateway-tool-executor.d.ts.map +1 -1
- package/dist/agent/gateway-tool-executor.js +232 -32
- package/dist/agent/gateway-tool-executor.js.map +1 -1
- package/dist/agent/gateway-tools.md +5 -1
- package/dist/agent/image-translation-tools.d.ts +48 -0
- package/dist/agent/image-translation-tools.d.ts.map +1 -0
- package/dist/agent/image-translation-tools.js +274 -0
- package/dist/agent/image-translation-tools.js.map +1 -0
- package/dist/agent/model-runner.d.ts +13 -0
- package/dist/agent/model-runner.d.ts.map +1 -1
- package/dist/agent/model-runner.js +17 -1
- package/dist/agent/model-runner.js.map +1 -1
- package/dist/agent/role-manager.d.ts.map +1 -1
- package/dist/agent/role-manager.js +9 -1
- package/dist/agent/role-manager.js.map +1 -1
- package/dist/agent/tool-registry.d.ts.map +1 -1
- package/dist/agent/tool-registry.js +25 -1
- package/dist/agent/tool-registry.js.map +1 -1
- package/dist/agent/types.d.ts +5 -34
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/api/graph-api-types.d.ts +3 -0
- package/dist/api/graph-api-types.d.ts.map +1 -1
- package/dist/cli/commands/start.d.ts +11 -1
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +34 -17
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/config/types.d.ts.map +1 -1
- package/dist/cli/config/types.js +5 -1
- package/dist/cli/config/types.js.map +1 -1
- package/dist/cli/runtime/envelope-bootstrap.js +2 -2
- package/dist/cli/runtime/envelope-bootstrap.js.map +1 -1
- package/dist/gateways/message-router.d.ts +3 -0
- package/dist/gateways/message-router.d.ts.map +1 -1
- package/dist/gateways/message-router.js +428 -336
- package/dist/gateways/message-router.js.map +1 -1
- package/dist/gateways/session-store.d.ts +2 -0
- package/dist/gateways/session-store.d.ts.map +1 -1
- package/dist/gateways/session-store.js +41 -1
- package/dist/gateways/session-store.js.map +1 -1
- package/dist/gateways/telegram-media.d.ts +13 -0
- package/dist/gateways/telegram-media.d.ts.map +1 -1
- package/dist/gateways/telegram-media.js +77 -0
- package/dist/gateways/telegram-media.js.map +1 -1
- package/dist/gateways/telegram-message-ledger.d.ts +49 -0
- package/dist/gateways/telegram-message-ledger.d.ts.map +1 -0
- package/dist/gateways/telegram-message-ledger.js +266 -0
- package/dist/gateways/telegram-message-ledger.js.map +1 -0
- package/dist/gateways/telegram-response-presenter.d.ts +10 -0
- package/dist/gateways/telegram-response-presenter.d.ts.map +1 -1
- package/dist/gateways/telegram-response-presenter.js +129 -32
- package/dist/gateways/telegram-response-presenter.js.map +1 -1
- package/dist/gateways/telegram.d.ts +20 -4
- package/dist/gateways/telegram.d.ts.map +1 -1
- package/dist/gateways/telegram.js +300 -64
- package/dist/gateways/telegram.js.map +1 -1
- package/dist/mcp/code-act-api-client.d.ts +19 -0
- package/dist/mcp/code-act-api-client.d.ts.map +1 -0
- package/dist/mcp/code-act-api-client.js +80 -0
- package/dist/mcp/code-act-api-client.js.map +1 -0
- package/dist/mcp/code-act-server.js +18 -47
- package/dist/mcp/code-act-server.js.map +1 -1
- package/dist/mcp/code-act-terminal-transport.d.ts +35 -0
- package/dist/mcp/code-act-terminal-transport.d.ts.map +1 -0
- package/dist/mcp/code-act-terminal-transport.js +101 -0
- package/dist/mcp/code-act-terminal-transport.js.map +1 -0
- package/dist/multi-agent/runtime-process.d.ts +2 -1
- package/dist/multi-agent/runtime-process.d.ts.map +1 -1
- package/dist/multi-agent/runtime-process.js +11 -0
- package/dist/multi-agent/runtime-process.js.map +1 -1
- package/dist/operator/operator-interfaces.d.ts +1 -1
- package/dist/operator/operator-interfaces.d.ts.map +1 -1
- package/dist/operator/operator-trigger-loop.d.ts +12 -0
- package/dist/operator/operator-trigger-loop.d.ts.map +1 -1
- package/dist/operator/operator-trigger-loop.js +184 -38
- package/dist/operator/operator-trigger-loop.js.map +1 -1
- package/dist/operator/pending-report-store.d.ts +35 -0
- package/dist/operator/pending-report-store.d.ts.map +1 -0
- package/dist/operator/pending-report-store.js +155 -0
- package/dist/operator/pending-report-store.js.map +1 -0
- package/dist/operator/runtime-config.d.ts +10 -0
- package/dist/operator/runtime-config.d.ts.map +1 -0
- package/dist/operator/runtime-config.js +21 -0
- package/dist/operator/runtime-config.js.map +1 -0
- package/dist/operator/situation-report.d.ts +37 -0
- package/dist/operator/situation-report.d.ts.map +1 -1
- package/dist/operator/situation-report.js +122 -9
- package/dist/operator/situation-report.js.map +1 -1
- package/dist/operator/task-ledger.d.ts +2 -1
- package/dist/operator/task-ledger.d.ts.map +1 -1
- package/dist/operator/task-ledger.js +11 -2
- package/dist/operator/task-ledger.js.map +1 -1
- package/dist/operator/workorder-consumer.d.ts +2 -1
- package/dist/operator/workorder-consumer.d.ts.map +1 -1
- package/dist/operator/workorder-consumer.js +43 -16
- package/dist/operator/workorder-consumer.js.map +1 -1
- package/dist/tools/browser-tool.d.ts +4 -2
- package/dist/tools/browser-tool.d.ts.map +1 -1
- package/dist/tools/browser-tool.js +56 -18
- package/dist/tools/browser-tool.js.map +1 -1
- package/package.json +3 -1
- package/scripts/attachment/extract-office-text.js +179 -0
- package/scripts/attachment/extract-pdf-text.swift +36 -0
- package/scripts/image/fb-overlay.py +128 -0
- package/scripts/image/ocr-image.py +38 -0
- package/scripts/setup-ocr.js +120 -0
- package/templates/skills/image-translate.md +17 -22
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.BrowserTool = void 0;
|
|
10
10
|
exports.getBrowserTool = getBrowserTool;
|
|
11
|
+
const node_fs_1 = require("node:fs");
|
|
12
|
+
const node_path_1 = require("node:path");
|
|
13
|
+
const node_crypto_1 = require("node:crypto");
|
|
11
14
|
const DEFAULT_CONFIG = {
|
|
12
15
|
headless: true,
|
|
13
16
|
viewportWidth: 1280,
|
|
@@ -69,6 +72,29 @@ class BrowserTool {
|
|
|
69
72
|
}
|
|
70
73
|
return this.page;
|
|
71
74
|
}
|
|
75
|
+
screenshotPaths(filename, prefix) {
|
|
76
|
+
const configuredRoot = (0, node_path_1.resolve)(this.config.screenshotDir);
|
|
77
|
+
(0, node_fs_1.mkdirSync)(configuredRoot, { recursive: true });
|
|
78
|
+
const root = (0, node_fs_1.realpathSync)(configuredRoot);
|
|
79
|
+
const name = filename?.trim() || `${prefix}-${Date.now()}-${(0, node_crypto_1.randomUUID)()}.png`;
|
|
80
|
+
if (name !== (0, node_path_1.basename)(name)) {
|
|
81
|
+
throw new Error('Screenshot filename must not contain directory components');
|
|
82
|
+
}
|
|
83
|
+
const outputPath = (0, node_path_1.resolve)(root, name);
|
|
84
|
+
const relativePath = (0, node_path_1.relative)(root, outputPath);
|
|
85
|
+
if (!relativePath || relativePath.startsWith('..') || (0, node_path_1.isAbsolute)(relativePath)) {
|
|
86
|
+
throw new Error('Screenshot filename must resolve inside the configured screenshot directory');
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
outputPath,
|
|
90
|
+
temporaryPath: (0, node_path_1.resolve)(root, `.mama-screenshot-${(0, node_crypto_1.randomUUID)()}.tmp.png`),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
removeOwnedArtifact(path) {
|
|
94
|
+
if ((0, node_fs_1.existsSync)(path)) {
|
|
95
|
+
(0, node_fs_1.unlinkSync)(path);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
72
98
|
/**
|
|
73
99
|
* Navigate to URL
|
|
74
100
|
*/
|
|
@@ -86,32 +112,44 @@ class BrowserTool {
|
|
|
86
112
|
/**
|
|
87
113
|
* Take screenshot
|
|
88
114
|
*/
|
|
89
|
-
async screenshot(filename) {
|
|
115
|
+
async screenshot(filename, signal) {
|
|
116
|
+
signal?.throwIfAborted();
|
|
90
117
|
const page = await this.ensureBrowser();
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
118
|
+
signal?.throwIfAborted();
|
|
119
|
+
const { outputPath, temporaryPath } = this.screenshotPaths(filename, 'screenshot');
|
|
120
|
+
try {
|
|
121
|
+
await page.screenshot({ path: temporaryPath, fullPage: false });
|
|
122
|
+
signal?.throwIfAborted();
|
|
123
|
+
(0, node_fs_1.copyFileSync)(temporaryPath, outputPath, node_fs_1.constants.COPYFILE_EXCL);
|
|
124
|
+
this.removeOwnedArtifact(temporaryPath);
|
|
94
125
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
126
|
+
catch (error) {
|
|
127
|
+
this.removeOwnedArtifact(temporaryPath);
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
console.log(`[Browser] Screenshot saved: ${outputPath}`);
|
|
131
|
+
return { success: true, path: outputPath };
|
|
100
132
|
}
|
|
101
133
|
/**
|
|
102
134
|
* Take full page screenshot
|
|
103
135
|
*/
|
|
104
|
-
async screenshotFullPage(filename) {
|
|
136
|
+
async screenshotFullPage(filename, signal) {
|
|
137
|
+
signal?.throwIfAborted();
|
|
105
138
|
const page = await this.ensureBrowser();
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
139
|
+
signal?.throwIfAborted();
|
|
140
|
+
const { outputPath, temporaryPath } = this.screenshotPaths(filename, 'fullpage');
|
|
141
|
+
try {
|
|
142
|
+
await page.screenshot({ path: temporaryPath, fullPage: true });
|
|
143
|
+
signal?.throwIfAborted();
|
|
144
|
+
(0, node_fs_1.copyFileSync)(temporaryPath, outputPath, node_fs_1.constants.COPYFILE_EXCL);
|
|
145
|
+
this.removeOwnedArtifact(temporaryPath);
|
|
109
146
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
147
|
+
catch (error) {
|
|
148
|
+
this.removeOwnedArtifact(temporaryPath);
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
console.log(`[Browser] Full page screenshot saved: ${outputPath}`);
|
|
152
|
+
return { success: true, path: outputPath };
|
|
115
153
|
}
|
|
116
154
|
/**
|
|
117
155
|
* Click element by selector
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-tool.js","sourceRoot":"","sources":["../../src/tools/browser-tool.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;
|
|
1
|
+
{"version":3,"file":"browser-tool.js","sourceRoot":"","sources":["../../src/tools/browser-tool.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jungjaehoon/mama-os",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.1",
|
|
4
4
|
"description": "MAMA OS - Local AI Runtime. Your scattered knowledge, organized by AI agents that never sleep.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
13
|
"postinstall": "node scripts/postinstall.js",
|
|
14
|
+
"setup:ocr": "node scripts/setup-ocr.js",
|
|
15
|
+
"check:ocr": "node scripts/setup-ocr.js --check",
|
|
14
16
|
"build": "tsc && pnpm exec tsx scripts/generate-gateway-tools.ts && rm -rf public/viewer/js && tsc -p public/viewer/tsconfig.viewer.json && pnpm run build:ui",
|
|
15
17
|
"build:ui": "pnpm --dir ui run bundle",
|
|
16
18
|
"dev:ui": "pnpm --dir ui run dev",
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { readFileSync, statSync } = require('node:fs');
|
|
4
|
+
const { inflateRawSync } = require('node:zlib');
|
|
5
|
+
|
|
6
|
+
const MAX_ARCHIVE_BYTES = 64 * 1024 * 1024;
|
|
7
|
+
const MAX_ARCHIVE_ENTRIES = 2_000;
|
|
8
|
+
|
|
9
|
+
const [, , filePath, extension, maxOutputRaw] = process.argv;
|
|
10
|
+
const maxOutputBytes = Number(maxOutputRaw);
|
|
11
|
+
if (!filePath || !['.docx', '.xlsx'].includes(extension) || !Number.isSafeInteger(maxOutputBytes)) {
|
|
12
|
+
throw new Error('usage: extract-office-text.js <path> <.docx|.xlsx> <max-output-bytes>');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const prefixes =
|
|
16
|
+
extension === '.docx' ? ['word/document.xml'] : ['xl/sharedStrings.xml', 'xl/worksheets/'];
|
|
17
|
+
const entries = readOfficeEntries(filePath, prefixes);
|
|
18
|
+
const text = extension === '.docx' ? extractDocxText(entries) : extractXlsxText(entries);
|
|
19
|
+
process.stdout.write(truncateUtf8(text, maxOutputBytes));
|
|
20
|
+
|
|
21
|
+
function readOfficeEntries(path, wantedPrefixes) {
|
|
22
|
+
if (statSync(path).size > MAX_ARCHIVE_BYTES) {
|
|
23
|
+
throw new Error('Office attachment is too large');
|
|
24
|
+
}
|
|
25
|
+
const archive = readFileSync(path);
|
|
26
|
+
const endOffset = findEndOfCentralDirectory(archive);
|
|
27
|
+
const entryCount = archive.readUInt16LE(endOffset + 10);
|
|
28
|
+
const centralOffset = archive.readUInt32LE(endOffset + 16);
|
|
29
|
+
if (entryCount > MAX_ARCHIVE_ENTRIES) {
|
|
30
|
+
throw new Error('Office attachment has too many entries');
|
|
31
|
+
}
|
|
32
|
+
const result = new Map();
|
|
33
|
+
let cursor = centralOffset;
|
|
34
|
+
let extractedBytes = 0;
|
|
35
|
+
for (let index = 0; index < entryCount; index += 1) {
|
|
36
|
+
requireSignature(archive, cursor, 0x02014b50, 'central directory');
|
|
37
|
+
const flags = archive.readUInt16LE(cursor + 8);
|
|
38
|
+
const method = archive.readUInt16LE(cursor + 10);
|
|
39
|
+
const compressedSize = archive.readUInt32LE(cursor + 20);
|
|
40
|
+
const uncompressedSize = archive.readUInt32LE(cursor + 24);
|
|
41
|
+
const nameLength = archive.readUInt16LE(cursor + 28);
|
|
42
|
+
const extraLength = archive.readUInt16LE(cursor + 30);
|
|
43
|
+
const commentLength = archive.readUInt16LE(cursor + 32);
|
|
44
|
+
const localOffset = archive.readUInt32LE(cursor + 42);
|
|
45
|
+
const nameStart = cursor + 46;
|
|
46
|
+
const name = archive.subarray(nameStart, nameStart + nameLength).toString('utf8');
|
|
47
|
+
cursor = nameStart + nameLength + extraLength + commentLength;
|
|
48
|
+
if (!wantedPrefixes.some((prefix) => name === prefix || name.startsWith(prefix))) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if ((flags & 0x1) !== 0) {
|
|
52
|
+
throw new Error('Encrypted Office attachments are not supported');
|
|
53
|
+
}
|
|
54
|
+
extractedBytes += uncompressedSize;
|
|
55
|
+
if (extractedBytes > MAX_ARCHIVE_BYTES) {
|
|
56
|
+
throw new Error('Office attachment expands too far');
|
|
57
|
+
}
|
|
58
|
+
requireSignature(archive, localOffset, 0x04034b50, 'local file header');
|
|
59
|
+
const nameSize = archive.readUInt16LE(localOffset + 26);
|
|
60
|
+
const extraSize = archive.readUInt16LE(localOffset + 28);
|
|
61
|
+
const dataStart = localOffset + 30 + nameSize + extraSize;
|
|
62
|
+
const compressed = archive.subarray(dataStart, dataStart + compressedSize);
|
|
63
|
+
const data =
|
|
64
|
+
method === 0
|
|
65
|
+
? compressed
|
|
66
|
+
: method === 8
|
|
67
|
+
? inflateRawSync(compressed, { maxOutputLength: MAX_ARCHIVE_BYTES })
|
|
68
|
+
: unsupportedCompression(method);
|
|
69
|
+
if (data.length !== uncompressedSize) {
|
|
70
|
+
throw new Error('Corrupt Office entry');
|
|
71
|
+
}
|
|
72
|
+
result.set(name, data.toString('utf8'));
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function findEndOfCentralDirectory(archive) {
|
|
78
|
+
const minimumOffset = Math.max(0, archive.length - 65_557);
|
|
79
|
+
for (let cursor = archive.length - 22; cursor >= minimumOffset; cursor -= 1) {
|
|
80
|
+
if (archive.readUInt32LE(cursor) === 0x06054b50) {
|
|
81
|
+
return cursor;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
throw new Error('Invalid Office attachment: ZIP directory not found');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function requireSignature(archive, offset, expected, label) {
|
|
88
|
+
if (offset < 0 || offset + 4 > archive.length || archive.readUInt32LE(offset) !== expected) {
|
|
89
|
+
throw new Error(`Invalid Office attachment ${label}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function unsupportedCompression(method) {
|
|
94
|
+
throw new Error(`Unsupported Office compression method: ${method}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function extractDocxText(entries) {
|
|
98
|
+
const xml = entries.get('word/document.xml');
|
|
99
|
+
if (!xml) {
|
|
100
|
+
throw new Error('Invalid DOCX attachment: word/document.xml is missing');
|
|
101
|
+
}
|
|
102
|
+
return [...xml.matchAll(/<w:p\b[^>]*>([\s\S]*?)<\/w:p>/g)]
|
|
103
|
+
.map((match) => extractXmlText(match[1], 'w:t'))
|
|
104
|
+
.filter(Boolean)
|
|
105
|
+
.join('\n');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function extractXlsxText(entries) {
|
|
109
|
+
const sharedXml = entries.get('xl/sharedStrings.xml') || '';
|
|
110
|
+
const shared = [...sharedXml.matchAll(/<si\b[^>]*>([\s\S]*?)<\/si>/g)].map((match) =>
|
|
111
|
+
extractXmlText(match[1], 't')
|
|
112
|
+
);
|
|
113
|
+
const sheets = [...entries.entries()]
|
|
114
|
+
.filter(([name]) => /^xl\/worksheets\/sheet[^/]*\.xml$/.test(name))
|
|
115
|
+
.sort(([left], [right]) => left.localeCompare(right));
|
|
116
|
+
if (sheets.length === 0) {
|
|
117
|
+
throw new Error('Invalid XLSX attachment: no worksheets found');
|
|
118
|
+
}
|
|
119
|
+
const lines = [];
|
|
120
|
+
for (const [name, xml] of sheets) {
|
|
121
|
+
lines.push(`[${name.slice(name.lastIndexOf('/') + 1, -4)}]`);
|
|
122
|
+
for (const cell of xml.matchAll(/<c\b([^>]*)>([\s\S]*?)<\/c>/g)) {
|
|
123
|
+
const reference = readXmlAttribute(cell[1], 'r') || '?';
|
|
124
|
+
const type = readXmlAttribute(cell[1], 't');
|
|
125
|
+
const raw = firstXmlTagText(cell[2], 'v');
|
|
126
|
+
const value =
|
|
127
|
+
type === 's'
|
|
128
|
+
? shared[Number(raw)] || ''
|
|
129
|
+
: type === 'inlineStr'
|
|
130
|
+
? extractXmlText(cell[2], 't')
|
|
131
|
+
: decodeXmlEntities(raw);
|
|
132
|
+
if (value) {
|
|
133
|
+
lines.push(`${reference}=${value}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return lines.join('\n');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function extractXmlText(xml, tagName) {
|
|
141
|
+
const tag = tagName.replace(':', '\\:');
|
|
142
|
+
return [...xml.matchAll(new RegExp(`<${tag}\\b[^>]*>([\\s\\S]*?)<\\/${tag}>`, 'g'))]
|
|
143
|
+
.map((match) => decodeXmlEntities(match[1]))
|
|
144
|
+
.join('');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function firstXmlTagText(xml, tagName) {
|
|
148
|
+
const match = new RegExp(`<${tagName}\\b[^>]*>([\\s\\S]*?)<\\/${tagName}>`).exec(xml);
|
|
149
|
+
return match ? decodeXmlEntities(match[1]) : '';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function readXmlAttribute(attributes, name) {
|
|
153
|
+
const match = new RegExp(`(?:^|\\s)${name}=(?:"([^"]*)"|'([^']*)')`).exec(attributes);
|
|
154
|
+
return match ? match[1] || match[2] : undefined;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function decodeXmlEntities(value) {
|
|
158
|
+
return value
|
|
159
|
+
.replace(/<[^>]+>/g, '')
|
|
160
|
+
.replace(/</g, '<')
|
|
161
|
+
.replace(/>/g, '>')
|
|
162
|
+
.replace(/"/g, '"')
|
|
163
|
+
.replace(/'/g, "'")
|
|
164
|
+
.replace(/&/g, '&')
|
|
165
|
+
.replace(/&#(\d+);/g, (_match, code) => String.fromCodePoint(Number(code)))
|
|
166
|
+
.replace(/&#x([0-9a-f]+);/gi, (_match, code) => String.fromCodePoint(parseInt(code, 16)));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function truncateUtf8(content, maxBytes) {
|
|
170
|
+
const bytes = Buffer.from(content);
|
|
171
|
+
if (bytes.length <= maxBytes) {
|
|
172
|
+
return content;
|
|
173
|
+
}
|
|
174
|
+
let end = maxBytes;
|
|
175
|
+
while (end > 0 && (bytes[end] & 0xc0) === 0x80) {
|
|
176
|
+
end -= 1;
|
|
177
|
+
}
|
|
178
|
+
return `${bytes.subarray(0, end).toString('utf8')}\n\n[Truncated at ${maxBytes} bytes]`;
|
|
179
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import PDFKit
|
|
3
|
+
|
|
4
|
+
guard CommandLine.arguments.count == 3, let maxBytes = Int(CommandLine.arguments[2]), maxBytes > 0 else {
|
|
5
|
+
fputs("usage: extract-pdf-text.swift <pdf-path> <max-output-bytes>\n", stderr)
|
|
6
|
+
exit(2)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let url = URL(fileURLWithPath: CommandLine.arguments[1])
|
|
10
|
+
guard let document = PDFDocument(url: url) else {
|
|
11
|
+
fputs("unable to open PDF\n", stderr)
|
|
12
|
+
exit(1)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
var written = 0
|
|
16
|
+
for index in 0..<document.pageCount {
|
|
17
|
+
if let text = document.page(at: index)?.string, !text.isEmpty {
|
|
18
|
+
let separator = written == 0 ? "" : "\n\n"
|
|
19
|
+
let data = (separator + text).data(using: .utf8) ?? Data()
|
|
20
|
+
let remaining = maxBytes - written
|
|
21
|
+
if remaining <= 0 { break }
|
|
22
|
+
var bounded = Data()
|
|
23
|
+
var prefixLength = min(remaining, data.count)
|
|
24
|
+
while prefixLength > 0 {
|
|
25
|
+
let candidate = Data(data.prefix(prefixLength))
|
|
26
|
+
if String(data: candidate, encoding: .utf8) != nil {
|
|
27
|
+
bounded = candidate
|
|
28
|
+
break
|
|
29
|
+
}
|
|
30
|
+
prefixLength -= 1
|
|
31
|
+
}
|
|
32
|
+
FileHandle.standardOutput.write(bounded)
|
|
33
|
+
written += bounded.count
|
|
34
|
+
if bounded.count < data.count { break }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Render translated text over OCR regions and return the output path as JSON."""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
PADDING = 10
|
|
11
|
+
MIN_FONT_SIZE = 18
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def font_candidates():
|
|
15
|
+
configured = os.environ.get("MAMA_KOREAN_FONT")
|
|
16
|
+
return [
|
|
17
|
+
configured,
|
|
18
|
+
"/System/Library/Fonts/AppleSDGothicNeo.ttc",
|
|
19
|
+
"/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
|
|
20
|
+
"/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def load_font(image_draw, text, max_width, start_size):
|
|
25
|
+
from PIL import ImageFont
|
|
26
|
+
|
|
27
|
+
for candidate in font_candidates():
|
|
28
|
+
if not candidate or not Path(candidate).exists():
|
|
29
|
+
continue
|
|
30
|
+
size = max(start_size, MIN_FONT_SIZE)
|
|
31
|
+
while size >= MIN_FONT_SIZE:
|
|
32
|
+
font = ImageFont.truetype(candidate, size)
|
|
33
|
+
bounds = image_draw.textbbox((0, 0), text, font=font)
|
|
34
|
+
if bounds[2] - bounds[0] <= max_width or size == MIN_FONT_SIZE:
|
|
35
|
+
return font
|
|
36
|
+
size -= 1
|
|
37
|
+
raise RuntimeError("Korean font not found; set MAMA_KOREAN_FONT")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def sample_text_color(array, points):
|
|
41
|
+
import numpy
|
|
42
|
+
|
|
43
|
+
x1 = max(min(point[0] for point in points), 0)
|
|
44
|
+
y1 = max(min(point[1] for point in points), 0)
|
|
45
|
+
x2 = min(max(point[0] for point in points), array.shape[1])
|
|
46
|
+
y2 = min(max(point[1] for point in points), array.shape[0])
|
|
47
|
+
region = array[y1:y2, x1:x2]
|
|
48
|
+
if region.size == 0:
|
|
49
|
+
return (20, 20, 20)
|
|
50
|
+
dark_pixels = region.reshape(-1, 3)[region.mean(axis=2).flatten() < 80]
|
|
51
|
+
if len(dark_pixels) < 3:
|
|
52
|
+
return (20, 20, 20)
|
|
53
|
+
return tuple(int(channel) for channel in dark_pixels.mean(axis=0))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def main():
|
|
57
|
+
if len(sys.argv) < 3:
|
|
58
|
+
raise ValueError("Usage: fb-overlay.py <image> <annotations.json> [output.png]")
|
|
59
|
+
|
|
60
|
+
from PIL import Image, ImageDraw
|
|
61
|
+
import numpy
|
|
62
|
+
|
|
63
|
+
image_path = sys.argv[1]
|
|
64
|
+
annotations_path = sys.argv[2]
|
|
65
|
+
output_path = (
|
|
66
|
+
sys.argv[3]
|
|
67
|
+
if len(sys.argv) > 3
|
|
68
|
+
else str(Path(image_path).with_name(f"{Path(image_path).stem}_KR.png"))
|
|
69
|
+
)
|
|
70
|
+
with open(annotations_path, encoding="utf-8") as handle:
|
|
71
|
+
annotations = json.load(handle)
|
|
72
|
+
|
|
73
|
+
original = Image.open(image_path).convert("RGB")
|
|
74
|
+
image = original.copy()
|
|
75
|
+
pixels = numpy.array(original)
|
|
76
|
+
draw = ImageDraw.Draw(image)
|
|
77
|
+
render_items = []
|
|
78
|
+
|
|
79
|
+
for annotation in annotations:
|
|
80
|
+
points = annotation["bbox"]
|
|
81
|
+
translated = annotation.get("translated", "")
|
|
82
|
+
if not translated:
|
|
83
|
+
continue
|
|
84
|
+
x1 = min(point[0] for point in points)
|
|
85
|
+
y1 = min(point[1] for point in points)
|
|
86
|
+
x2 = max(point[0] for point in points)
|
|
87
|
+
y2 = max(point[1] for point in points)
|
|
88
|
+
font = load_font(draw, translated, x2 - x1, int((y2 - y1) * 0.92))
|
|
89
|
+
bounds = draw.textbbox((0, 0), translated, font=font)
|
|
90
|
+
width = bounds[2] - bounds[0]
|
|
91
|
+
height = bounds[3] - bounds[1]
|
|
92
|
+
render_items.append(
|
|
93
|
+
{
|
|
94
|
+
"box": (
|
|
95
|
+
x1 - PADDING,
|
|
96
|
+
y1 - PADDING,
|
|
97
|
+
max(x2, x1 + width) + PADDING,
|
|
98
|
+
max(y2, y1 + height + 4) + PADDING,
|
|
99
|
+
),
|
|
100
|
+
"text": translated,
|
|
101
|
+
"font": font,
|
|
102
|
+
"color": sample_text_color(pixels, points),
|
|
103
|
+
"height": height,
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
for item in render_items:
|
|
108
|
+
draw.rectangle(list(item["box"]), fill=(255, 255, 255))
|
|
109
|
+
for item in render_items:
|
|
110
|
+
x1, y1, _x2, y2 = item["box"]
|
|
111
|
+
text_y = y1 + ((y2 - y1) - item["height"]) // 2
|
|
112
|
+
draw.text(
|
|
113
|
+
(x1 + PADDING, text_y),
|
|
114
|
+
item["text"],
|
|
115
|
+
font=item["font"],
|
|
116
|
+
fill=item["color"],
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
image.save(output_path, quality=95)
|
|
120
|
+
print(json.dumps({"outputPath": output_path}, ensure_ascii=False))
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
if __name__ == "__main__":
|
|
124
|
+
try:
|
|
125
|
+
main()
|
|
126
|
+
except Exception as error:
|
|
127
|
+
print(json.dumps({"error": str(error)}, ensure_ascii=False), file=sys.stderr)
|
|
128
|
+
sys.exit(1)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Extract OCR regions as JSON for MAMA image translation tools."""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main():
|
|
9
|
+
if len(sys.argv) < 2:
|
|
10
|
+
raise ValueError("Usage: ocr-image.py <image_path> [--lang ja,en]")
|
|
11
|
+
|
|
12
|
+
image_path = sys.argv[1]
|
|
13
|
+
languages = ["ja", "en"]
|
|
14
|
+
for index, argument in enumerate(sys.argv[2:], 2):
|
|
15
|
+
if argument == "--lang" and index + 1 < len(sys.argv):
|
|
16
|
+
languages = sys.argv[index + 1].split(",")
|
|
17
|
+
|
|
18
|
+
import easyocr
|
|
19
|
+
|
|
20
|
+
reader = easyocr.Reader(languages, gpu=False, verbose=False)
|
|
21
|
+
output = []
|
|
22
|
+
for bbox, text, confidence in reader.readtext(image_path, detail=1):
|
|
23
|
+
output.append(
|
|
24
|
+
{
|
|
25
|
+
"bbox": [[int(point[0]), int(point[1])] for point in bbox],
|
|
26
|
+
"text": text,
|
|
27
|
+
"conf": round(confidence, 2),
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
print(json.dumps(output, ensure_ascii=False))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
if __name__ == "__main__":
|
|
34
|
+
try:
|
|
35
|
+
main()
|
|
36
|
+
except Exception as error:
|
|
37
|
+
print(json.dumps({"error": str(error)}, ensure_ascii=False), file=sys.stderr)
|
|
38
|
+
sys.exit(1)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { existsSync, mkdirSync } = require('node:fs');
|
|
4
|
+
const { homedir } = require('node:os');
|
|
5
|
+
const { dirname, join, resolve } = require('node:path');
|
|
6
|
+
const { spawnSync } = require('node:child_process');
|
|
7
|
+
|
|
8
|
+
const REQUIREMENTS = ['easyocr==1.7.2', 'Pillow>=10,<13', 'numpy>=1.26,<3'];
|
|
9
|
+
const FONT_GUIDANCE =
|
|
10
|
+
'Install a Korean/CJK font (Ubuntu/Debian: apt install fonts-noto-cjk) or set ' +
|
|
11
|
+
'MAMA_KOREAN_FONT to a readable .ttf/.ttc font file.';
|
|
12
|
+
const RUNTIME_CHECK = String.raw`
|
|
13
|
+
import os
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
import easyocr, PIL, numpy
|
|
16
|
+
from PIL import ImageFont
|
|
17
|
+
|
|
18
|
+
candidates = [
|
|
19
|
+
os.environ.get("MAMA_KOREAN_FONT"),
|
|
20
|
+
"/System/Library/Fonts/AppleSDGothicNeo.ttc",
|
|
21
|
+
"/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
|
|
22
|
+
"/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc",
|
|
23
|
+
]
|
|
24
|
+
for candidate in candidates:
|
|
25
|
+
if not candidate or not Path(candidate).is_file():
|
|
26
|
+
continue
|
|
27
|
+
try:
|
|
28
|
+
font = ImageFont.truetype(candidate, 18)
|
|
29
|
+
if font.getbbox("한글"):
|
|
30
|
+
print(easyocr.__version__)
|
|
31
|
+
raise SystemExit(0)
|
|
32
|
+
except OSError:
|
|
33
|
+
pass
|
|
34
|
+
raise SystemExit("Korean/CJK font is unavailable")
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
function runtimeRoot() {
|
|
38
|
+
return resolve(process.env.MAMA_OCR_ENV || join(homedir(), '.mama', 'ocr-env'));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function runtimePython(root) {
|
|
42
|
+
return process.platform === 'win32'
|
|
43
|
+
? join(root, 'Scripts', 'python.exe')
|
|
44
|
+
: join(root, 'bin', 'python3');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function run(command, args, options = {}) {
|
|
48
|
+
const result = spawnSync(command, args, {
|
|
49
|
+
stdio: options.capture ? ['ignore', 'pipe', 'pipe'] : 'inherit',
|
|
50
|
+
encoding: 'utf8',
|
|
51
|
+
env: process.env,
|
|
52
|
+
});
|
|
53
|
+
if (result.error || result.status !== 0) {
|
|
54
|
+
const detail = result.error?.message || result.stderr?.trim() || `exit ${result.status}`;
|
|
55
|
+
throw new Error(`${command} ${args.join(' ')} failed: ${detail}`);
|
|
56
|
+
}
|
|
57
|
+
return result.stdout || '';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function check() {
|
|
61
|
+
const python = runtimePython(runtimeRoot());
|
|
62
|
+
if (!existsSync(python)) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
run(python, ['-c', RUNTIME_CHECK], { capture: true });
|
|
67
|
+
return true;
|
|
68
|
+
} catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function setup() {
|
|
74
|
+
const root = runtimeRoot();
|
|
75
|
+
const bootstrapPython = process.env.MAMA_OCR_BOOTSTRAP_PYTHON || 'python3';
|
|
76
|
+
mkdirSync(dirname(root), { recursive: true, mode: 0o700 });
|
|
77
|
+
if (!existsSync(runtimePython(root))) {
|
|
78
|
+
run(bootstrapPython, ['-m', 'venv', root]);
|
|
79
|
+
}
|
|
80
|
+
const python = runtimePython(root);
|
|
81
|
+
run(python, ['-m', 'pip', 'install', '--upgrade', 'pip']);
|
|
82
|
+
run(python, ['-m', 'pip', 'install', ...REQUIREMENTS]);
|
|
83
|
+
if (!check()) {
|
|
84
|
+
throw new Error(`MAMA OCR runtime failed its dependency/font check. ${FONT_GUIDANCE}`);
|
|
85
|
+
}
|
|
86
|
+
process.stdout.write(`MAMA OCR runtime ready: ${python}\n`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function main() {
|
|
90
|
+
const command = process.argv[2];
|
|
91
|
+
if (command === '--print-requirements') {
|
|
92
|
+
process.stdout.write(`${REQUIREMENTS.join('\n')}\n`);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (command === '--print-font-guidance') {
|
|
96
|
+
process.stdout.write(`${FONT_GUIDANCE}\n`);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (command === '--check') {
|
|
100
|
+
if (check()) {
|
|
101
|
+
process.stdout.write(`MAMA OCR runtime ready: ${runtimePython(runtimeRoot())}\n`);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
process.stderr.write(
|
|
105
|
+
`MAMA OCR runtime is not ready at ${runtimeRoot()}. Run: pnpm setup:ocr\n${FONT_GUIDANCE}\n`
|
|
106
|
+
);
|
|
107
|
+
process.exitCode = 1;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
setup();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
main();
|
|
115
|
+
} catch (error) {
|
|
116
|
+
process.stderr.write(
|
|
117
|
+
`MAMA OCR setup failed: ${error instanceof Error ? error.message : error}\n`
|
|
118
|
+
);
|
|
119
|
+
process.exitCode = 1;
|
|
120
|
+
}
|