@arnilo/prism-tools 0.0.0-stage → 0.13.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/CHANGELOG.md +410 -0
- package/LICENSE +21 -0
- package/README.md +44 -2
- package/browser/README.md +98 -0
- package/browser/skills/browser-automation/SKILL.md +14 -0
- package/browser/src/actions/cdp.ts +131 -0
- package/browser/src/actions/dialog.ts +25 -0
- package/browser/src/actions/download_release.ts +22 -0
- package/browser/src/actions/index.ts +37 -0
- package/browser/src/actions/locator.ts +70 -0
- package/browser/src/actions/navigate.ts +23 -0
- package/browser/src/actions/screenshot.ts +27 -0
- package/browser/src/actions/scroll.ts +24 -0
- package/browser/src/actions/select_page.ts +21 -0
- package/browser/src/actions/types.ts +114 -0
- package/browser/src/actions/upload.ts +44 -0
- package/browser/src/actions/wait.ts +45 -0
- package/browser/src/browser.ts +197 -0
- package/browser/src/cdp.ts +178 -0
- package/browser/src/checkpoint.ts +189 -0
- package/browser/src/config.ts +72 -0
- package/browser/src/downloads.ts +253 -0
- package/browser/src/errors.ts +31 -0
- package/browser/src/evaluate.ts +101 -0
- package/browser/src/index.ts +129 -0
- package/browser/src/limits.ts +271 -0
- package/browser/src/manager.ts +730 -0
- package/browser/src/network.ts +263 -0
- package/browser/src/observe.ts +178 -0
- package/browser/src/policy.ts +133 -0
- package/browser/src/screenshot.ts +107 -0
- package/browser/src/shared-sandbox.ts +53 -0
- package/browser/src/snapshot.ts +99 -0
- package/browser/src/targets.ts +165 -0
- package/browser/src/tools.ts +480 -0
- package/browser/src/types.ts +316 -0
- package/browser/src/uploads.ts +174 -0
- package/coding/README.md +337 -0
- package/coding/src/agent/acp-operations.ts +146 -0
- package/coding/src/agent/artifacts.ts +38 -0
- package/coding/src/agent/ask-user-decision.ts +623 -0
- package/coding/src/agent/atomic-write.ts +23 -0
- package/coding/src/agent/bounded-file.ts +21 -0
- package/coding/src/agent/checks.ts +301 -0
- package/coding/src/agent/coding-checkpoint.ts +828 -0
- package/coding/src/agent/delete.ts +237 -0
- package/coding/src/agent/diagnostics-tool.ts +163 -0
- package/coding/src/agent/diagnostics.ts +262 -0
- package/coding/src/agent/edit-diff.ts +382 -0
- package/coding/src/agent/edit.ts +322 -0
- package/coding/src/agent/effects.ts +102 -0
- package/coding/src/agent/env.ts +28 -0
- package/coding/src/agent/execution-policy.ts +89 -0
- package/coding/src/agent/file-mutation-queue.ts +72 -0
- package/coding/src/agent/forge/github.ts +603 -0
- package/coding/src/agent/forge/index.ts +16 -0
- package/coding/src/agent/forge/types.ts +185 -0
- package/coding/src/agent/git-aware-repository.ts +305 -0
- package/coding/src/agent/git-exec.ts +326 -0
- package/coding/src/agent/git-status.ts +177 -0
- package/coding/src/agent/git-tools.ts +360 -0
- package/coding/src/agent/git.ts +1074 -0
- package/coding/src/agent/glob-match.ts +146 -0
- package/coding/src/agent/glob.ts +182 -0
- package/coding/src/agent/goal-verify.ts +385 -0
- package/coding/src/agent/grep-worker.ts +74 -0
- package/coding/src/agent/grep.ts +459 -0
- package/coding/src/agent/identity.ts +48 -0
- package/coding/src/agent/index.ts +676 -0
- package/coding/src/agent/language/client.ts +429 -0
- package/coding/src/agent/language/framing.ts +190 -0
- package/coding/src/agent/language/index.ts +23 -0
- package/coding/src/agent/language/intelligence.ts +682 -0
- package/coding/src/agent/language/types.ts +187 -0
- package/coding/src/agent/lifecycle-events.ts +121 -0
- package/coding/src/agent/lifecycle.ts +166 -0
- package/coding/src/agent/limits.ts +214 -0
- package/coding/src/agent/move.ts +178 -0
- package/coding/src/agent/mutation-path.ts +52 -0
- package/coding/src/agent/output-accumulator.ts +304 -0
- package/coding/src/agent/path-utils.ts +120 -0
- package/coding/src/agent/process/index.ts +58 -0
- package/coding/src/agent/process/recovery.ts +508 -0
- package/coding/src/agent/process/sessions-host.ts +222 -0
- package/coding/src/agent/process/sessions-monitor.ts +141 -0
- package/coding/src/agent/process/sessions-recovery.ts +241 -0
- package/coding/src/agent/process/sessions-spawn.ts +613 -0
- package/coding/src/agent/process/sessions-teardown.ts +281 -0
- package/coding/src/agent/process/sessions.ts +30 -0
- package/coding/src/agent/process/types.ts +358 -0
- package/coding/src/agent/read-path-set.ts +126 -0
- package/coding/src/agent/read.ts +713 -0
- package/coding/src/agent/repository/glob.ts +147 -0
- package/coding/src/agent/repository/indexed-search.ts +480 -0
- package/coding/src/agent/repository/list.ts +131 -0
- package/coding/src/agent/repository/operations.ts +22 -0
- package/coding/src/agent/repository/path.ts +98 -0
- package/coding/src/agent/repository/search.ts +277 -0
- package/coding/src/agent/repository/types.ts +243 -0
- package/coding/src/agent/repository/walk.ts +126 -0
- package/coding/src/agent/repository.ts +12 -0
- package/coding/src/agent/shell-background.ts +264 -0
- package/coding/src/agent/shell-output.ts +75 -0
- package/coding/src/agent/shell.ts +526 -0
- package/coding/src/agent/todo.ts +399 -0
- package/coding/src/agent/truncate.ts +251 -0
- package/coding/src/agent/write.ts +170 -0
- package/coding/src/ask-user-host.ts +48 -0
- package/coding/src/config.ts +278 -0
- package/coding/src/hard-deny.ts +97 -0
- package/coding/src/index.ts +318 -0
- package/coding/src/principal-guard.ts +84 -0
- package/coding/src/sandbox.ts +92 -0
- package/coding/src/security/approval.ts +144 -0
- package/coding/src/security/command-rules.ts +57 -0
- package/coding/src/security/docker-cli.ts +222 -0
- package/coding/src/security/docker-sandbox.ts +1205 -0
- package/coding/src/security/e2b-sandbox.ts +853 -0
- package/coding/src/security/egress/dns-pin.ts +88 -0
- package/coding/src/security/egress/index.ts +32 -0
- package/coding/src/security/egress/limits.ts +57 -0
- package/coding/src/security/egress/policy.ts +137 -0
- package/coding/src/security/egress/proxy.ts +599 -0
- package/coding/src/security/egress/types.ts +30 -0
- package/coding/src/security/index.ts +199 -0
- package/coding/src/security/native-sandbox.ts +639 -0
- package/coding/src/security/path-containment.ts +55 -0
- package/coding/src/security/sandbox-coding-operations.ts +392 -0
- package/coding/src/security/sandbox-fs-operations.ts +644 -0
- package/coding/src/security/sandbox-limits.ts +147 -0
- package/coding/src/security/sandbox-tar.ts +308 -0
- package/coding/src/security/sandbox.ts +179 -0
- package/coding/src/security/semaphore.ts +55 -0
- package/coding/src/targets.ts +110 -0
- package/coding/src/workspace/diff.ts +78 -0
- package/coding/src/workspace/errors.ts +28 -0
- package/coding/src/workspace/index.ts +5 -0
- package/coding/src/workspace/merge.ts +151 -0
- package/coding/src/workspace/routing.ts +76 -0
- package/coding/src/workspace/service.ts +545 -0
- package/computer-use/README.md +105 -0
- package/computer-use/skills/computer-use-linux/SKILL.md +17 -0
- package/computer-use/src/classify.ts +53 -0
- package/computer-use/src/command.ts +29 -0
- package/computer-use/src/config.ts +69 -0
- package/computer-use/src/create.ts +170 -0
- package/computer-use/src/devices.ts +247 -0
- package/computer-use/src/env.ts +28 -0
- package/computer-use/src/index.ts +157 -0
- package/computer-use/src/skill.ts +21 -0
- package/openapi/README.md +97 -0
- package/openapi/src/compile.ts +386 -0
- package/openapi/src/config.ts +73 -0
- package/openapi/src/errors.ts +16 -0
- package/openapi/src/execute.ts +241 -0
- package/openapi/src/index.ts +124 -0
- package/openapi/src/limits.ts +63 -0
- package/openapi/src/specs.ts +86 -0
- package/openapi/src/tools.ts +60 -0
- package/openapi/src/types.ts +61 -0
- package/package.json +171 -4
- package/research/README.md +72 -0
- package/research/skills/research-report/SKILL.md +12 -0
- package/research/skills/research-scoping/SKILL.md +10 -0
- package/research/skills/research-synthesis/SKILL.md +10 -0
- package/research/skills/source-triage/SKILL.md +10 -0
- package/research/src/check.ts +71 -0
- package/research/src/config.ts +41 -0
- package/research/src/index.ts +147 -0
- package/research/src/ledger.ts +157 -0
- package/research/src/urls.ts +21 -0
- package/web/README.md +287 -0
- package/web/skills/web-research/SKILL.md +17 -0
- package/web/src/brave.ts +66 -0
- package/web/src/cache.ts +151 -0
- package/web/src/config.ts +210 -0
- package/web/src/controls.ts +128 -0
- package/web/src/domains.ts +114 -0
- package/web/src/exa.ts +60 -0
- package/web/src/firecrawl.ts +160 -0
- package/web/src/index.ts +387 -0
- package/web/src/kagi.ts +73 -0
- package/web/src/limits.ts +55 -0
- package/web/src/native/convert.ts +84 -0
- package/web/src/native/fetch.ts +226 -0
- package/web/src/normalize.ts +131 -0
- package/web/src/obscura/cdp.ts +201 -0
- package/web/src/obscura/classify.ts +35 -0
- package/web/src/obscura/cli.ts +254 -0
- package/web/src/obscura/errors.ts +8 -0
- package/web/src/obscura/index.ts +59 -0
- package/web/src/obscura/limits.ts +92 -0
- package/web/src/obscura/mcp.ts +104 -0
- package/web/src/obscura/playwright.ts +133 -0
- package/web/src/obscura/process.ts +235 -0
- package/web/src/obscura/search-profile.ts +73 -0
- package/web/src/obscura/types.ts +48 -0
- package/web/src/obscura/web.ts +419 -0
- package/web/src/prompt-fetch.ts +88 -0
- package/web/src/render.ts +75 -0
- package/web/src/searxng.ts +78 -0
- package/web/src/slice.ts +139 -0
- package/web/src/tavily.ts +77 -0
- package/web/src/tools.ts +182 -0
- package/web/src/transport.ts +310 -0
- package/web/src/types.ts +199 -0
- package/work/README.md +77 -0
- package/work/THIRD_PARTY_NOTICES +24 -0
- package/work/docling/ocr.py +150 -0
- package/work/sandbox/Dockerfile +36 -0
- package/work/sandbox/soffice.sh +12 -0
- package/work/src/command.ts +29 -0
- package/work/src/config.ts +138 -0
- package/work/src/connectors/cli.ts +343 -0
- package/work/src/connectors/drafts.ts +647 -0
- package/work/src/connectors/errors.ts +8 -0
- package/work/src/connectors/file-bytes.ts +149 -0
- package/work/src/connectors/google-workspace-http.ts +399 -0
- package/work/src/connectors/google-workspace.ts +465 -0
- package/work/src/connectors/gws-tools.ts +378 -0
- package/work/src/connectors/http.ts +123 -0
- package/work/src/connectors/idempotency.ts +235 -0
- package/work/src/connectors/identity.ts +34 -0
- package/work/src/connectors/index.ts +93 -0
- package/work/src/connectors/limits.ts +50 -0
- package/work/src/connectors/m365-tools.ts +308 -0
- package/work/src/connectors/microsoft365-http.ts +428 -0
- package/work/src/connectors/microsoft365.ts +402 -0
- package/work/src/connectors/normalize.ts +120 -0
- package/work/src/connectors/tool-helpers.ts +398 -0
- package/work/src/connectors/tools.ts +42 -0
- package/work/src/connectors/types.ts +399 -0
- package/work/src/diagrams/canonicalize.ts +166 -0
- package/work/src/diagrams/caps.ts +52 -0
- package/work/src/diagrams/embed.ts +340 -0
- package/work/src/diagrams/errors.ts +76 -0
- package/work/src/diagrams/index.ts +81 -0
- package/work/src/diagrams/messages.ts +150 -0
- package/work/src/diagrams/origin.ts +52 -0
- package/work/src/diagrams/telemetry.ts +34 -0
- package/work/src/diagrams/xml.ts +267 -0
- package/work/src/document-extraction/index.ts +563 -0
- package/work/src/document-reader/ambient.d.ts +2 -0
- package/work/src/document-reader/errors.ts +7 -0
- package/work/src/document-reader/index.ts +181 -0
- package/work/src/document-reader/mistral-ocr.ts +318 -0
- package/work/src/document-reader/parsers.ts +171 -0
- package/work/src/documents/caps.ts +135 -0
- package/work/src/documents/diff.ts +269 -0
- package/work/src/documents/errors.ts +44 -0
- package/work/src/documents/fidelity.ts +98 -0
- package/work/src/documents/generate.ts +96 -0
- package/work/src/documents/hash.ts +8 -0
- package/work/src/documents/index.ts +14 -0
- package/work/src/documents/model-schema.ts +639 -0
- package/work/src/documents/parse.ts +252 -0
- package/work/src/documents/patch-history.ts +81 -0
- package/work/src/documents/patch.ts +476 -0
- package/work/src/documents/preview-html.ts +245 -0
- package/work/src/documents/preview.ts +227 -0
- package/work/src/documents/telemetry.ts +36 -0
- package/work/src/documents/translate/docx.ts +292 -0
- package/work/src/documents/translate/pptx.ts +304 -0
- package/work/src/documents/translate/xlsx.ts +137 -0
- package/work/src/documents/types.ts +188 -0
- package/work/src/index.ts +311 -0
- package/work/src/runtime/worker-pool-entry.ts +102 -0
- package/work/src/runtime/worker-pool.ts +267 -0
- package/work/src/sandbox/composition.ts +212 -0
- package/work/src/sandbox/image.ts +6 -0
- package/work/src/sandbox/index.ts +14 -0
- package/work/src/sheets/caps.ts +68 -0
- package/work/src/sheets/csv.ts +351 -0
- package/work/src/sheets/decimal.ts +388 -0
- package/work/src/sheets/errors.ts +52 -0
- package/work/src/sheets/index.ts +62 -0
- package/work/src/sheets/inference.ts +378 -0
- package/work/src/sheets/telemetry.ts +34 -0
- package/work/src/sheets/types.ts +162 -0
- package/work/src/sheets/xlsx.ts +388 -0
- package/work/src/skills/index.ts +8 -0
- package/work/src/skills/load.ts +43 -0
- package/work/src/tools/filesystem.ts +21 -0
- package/work/src/tools/index.ts +1 -0
- package/work/src/tools/local-filesystem.ts +33 -0
- package/work/src/tools/office.ts +311 -0
- package/work/vendor/hermes-agent/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/SKILL.md +196 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_comments.py +289 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_common.py +94 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_create.py +177 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_edit.py +250 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_read.py +149 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_revisions.py +147 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_template.py +70 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_validate.py +156 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/SKILL.md +125 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/_raster.py +76 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/extract_marker.py +88 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/extract_pymupdf.py +101 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_create.py +130 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_fill_form.py +97 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_form_layout.py +168 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_make_form.py +145 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_merge.py +50 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_meta.py +115 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_page_image.py +99 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_read.py +153 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_secure.py +71 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_split.py +84 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_stamp.py +143 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_watermark.py +51 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/SKILL.md +220 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_create.py +214 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_edit.py +436 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_from_template.py +88 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_read.py +131 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_render.py +93 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/SKILL.md +196 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/csv_to_xlsx.py +104 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_create.py +259 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_edit.py +263 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_read.py +160 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_recalc.py +111 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_restructure.py +337 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_to_csv.py +69 -0
- package/work/vendor-lock.json +11 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { parseA1Cell, parseXlsx, XlsxReadContext } from "@office-open/xlsx";
|
|
2
|
+
import type { Element } from "@office-open/xml";
|
|
3
|
+
import { resolveSheetsCaps, validateByteCap, validateZipSignature } from "./caps.js";
|
|
4
|
+
import { SheetsCapError, SheetsError, SheetsParseError } from "./errors.js";
|
|
5
|
+
import { inferAndTransformRows } from "./inference.js";
|
|
6
|
+
import type {
|
|
7
|
+
CellValue,
|
|
8
|
+
FormulaCellValue,
|
|
9
|
+
InferenceWarning,
|
|
10
|
+
ParseWorkbookOptions,
|
|
11
|
+
ResolvedSheetsCaps,
|
|
12
|
+
SheetParse,
|
|
13
|
+
WorkbookParse,
|
|
14
|
+
} from "./types.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Extracts concatenated text content from an OpenXML Element tree.
|
|
18
|
+
*/
|
|
19
|
+
function extractText(el: Element | undefined): string {
|
|
20
|
+
if (!el) return "";
|
|
21
|
+
if (el.text !== undefined && el.text !== null) {
|
|
22
|
+
return String(el.text);
|
|
23
|
+
}
|
|
24
|
+
if (!el.elements || el.elements.length === 0) {
|
|
25
|
+
return "";
|
|
26
|
+
}
|
|
27
|
+
let result = "";
|
|
28
|
+
for (const child of el.elements) {
|
|
29
|
+
if (child.type === "text" || child.text !== undefined) {
|
|
30
|
+
result += String(child.text ?? "");
|
|
31
|
+
} else {
|
|
32
|
+
result += extractText(child);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Extracts the shared strings table from OpenXML sharedStrings.xml (`<sst>`).
|
|
40
|
+
*/
|
|
41
|
+
function extractSharedStrings(sstElement: Element | undefined): string[] {
|
|
42
|
+
if (!sstElement?.elements) return [];
|
|
43
|
+
const strings: string[] = [];
|
|
44
|
+
for (const child of sstElement.elements) {
|
|
45
|
+
if (child.name === "si") {
|
|
46
|
+
strings.push(extractText(child));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return strings;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Built-in standard ECMA-376 number format IDs that represent dates/times.
|
|
54
|
+
*/
|
|
55
|
+
const BUILT_IN_DATE_NUM_FMT_IDS = new Set<number>([14, 15, 16, 17, 18, 19, 20, 21, 22, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 45, 46, 47]);
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Checks whether a number format code represents a date or datetime.
|
|
59
|
+
*/
|
|
60
|
+
function isDateFormatCode(code: string): boolean {
|
|
61
|
+
// Strip quoted literal strings e.g. "Year" or \"
|
|
62
|
+
const stripped = code.replace(/"[^"]*"/g, "").replace(/\[[^\]]*\]/g, "");
|
|
63
|
+
// Check for common date/time format tokens
|
|
64
|
+
return /\b(yy|yyyy|m|mm|d|dd|h|hh|ss|am\/pm)\b/i.test(stripped) || /[yYmMdDhHsS]/.test(stripped);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Builds a Set of style `<xf>` indices that format cell values as dates.
|
|
69
|
+
*/
|
|
70
|
+
function extractDateStyleIndices(stylesElement: Element | undefined): Set<number> {
|
|
71
|
+
const dateStyles = new Set<number>();
|
|
72
|
+
if (!stylesElement?.elements) return dateStyles;
|
|
73
|
+
|
|
74
|
+
// 1. Parse custom numFmts: id -> formatCode
|
|
75
|
+
const customNumFmts = new Map<number, string>();
|
|
76
|
+
const numFmtsElem = stylesElement.elements.find((e) => e.name === "numFmts");
|
|
77
|
+
if (numFmtsElem?.elements) {
|
|
78
|
+
for (const nf of numFmtsElem.elements) {
|
|
79
|
+
if (nf.name === "numFmt" && nf.attributes) {
|
|
80
|
+
const id = Number(nf.attributes.numFmtId);
|
|
81
|
+
const code = String(nf.attributes.formatCode ?? "");
|
|
82
|
+
if (!Number.isNaN(id) && code) {
|
|
83
|
+
customNumFmts.set(id, code);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 2. Parse cellXfs: index -> isDate
|
|
90
|
+
const cellXfsElem = stylesElement.elements.find((e) => e.name === "cellXfs");
|
|
91
|
+
if (cellXfsElem?.elements) {
|
|
92
|
+
let xfIndex = 0;
|
|
93
|
+
for (const xf of cellXfsElem.elements) {
|
|
94
|
+
if (xf.name === "xf" && xf.attributes) {
|
|
95
|
+
const numFmtId = Number(xf.attributes.numFmtId ?? 0);
|
|
96
|
+
if (BUILT_IN_DATE_NUM_FMT_IDS.has(numFmtId)) {
|
|
97
|
+
dateStyles.add(xfIndex);
|
|
98
|
+
} else if (customNumFmts.has(numFmtId)) {
|
|
99
|
+
const code = customNumFmts.get(numFmtId)!;
|
|
100
|
+
if (isDateFormatCode(code)) {
|
|
101
|
+
dateStyles.add(xfIndex);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
xfIndex += 1;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return dateStyles;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Converts an Excel serial date number to ISO-8601 date or datetime string.
|
|
114
|
+
*/
|
|
115
|
+
function excelSerialToIso(serial: number): { type: "date" | "datetime"; value: string } {
|
|
116
|
+
// Excel epoch: 1900-01-01 is day 1, with the 1900 leap year bug
|
|
117
|
+
const adjustedSerial = serial > 60 ? serial - 1 : serial;
|
|
118
|
+
const msPerDay = 86400 * 1000;
|
|
119
|
+
// Milliseconds from 1970-01-01 to 1900-01-01 is 2209161600000 (with day 1 offset: 2209161600000 - 86400000)
|
|
120
|
+
const epochMs = Date.UTC(1899, 11, 31);
|
|
121
|
+
const dateMs = epochMs + Math.round(adjustedSerial * msPerDay);
|
|
122
|
+
const d = new Date(dateMs);
|
|
123
|
+
|
|
124
|
+
const hasTime = serial % 1 !== 0;
|
|
125
|
+
if (hasTime) {
|
|
126
|
+
return { type: "datetime", value: d.toISOString() };
|
|
127
|
+
}
|
|
128
|
+
const yyyy = d.getUTCFullYear().toString().padStart(4, "0");
|
|
129
|
+
const mm = (d.getUTCMonth() + 1).toString().padStart(2, "0");
|
|
130
|
+
const dd = d.getUTCDate().toString().padStart(2, "0");
|
|
131
|
+
return { type: "date", value: `${yyyy}-${mm}-${dd}` };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Maps an individual OpenXML `<c>` element to a strongly-typed `CellValue`.
|
|
136
|
+
*/
|
|
137
|
+
function parseCellElement(cellElem: Element, sharedStrings: readonly string[], dateStyles: ReadonlySet<number>): CellValue {
|
|
138
|
+
const attributes = cellElem.attributes ?? {};
|
|
139
|
+
const t = attributes.t ? String(attributes.t) : undefined;
|
|
140
|
+
const s = attributes.s !== undefined ? Number(attributes.s) : undefined;
|
|
141
|
+
|
|
142
|
+
// 1. Check for formula element <f>
|
|
143
|
+
const fElem = cellElem.elements?.find((e) => e.name === "f");
|
|
144
|
+
if (fElem) {
|
|
145
|
+
const rawFormula = extractText(fElem).trim();
|
|
146
|
+
const formulaStr = rawFormula.startsWith("=") ? rawFormula : `=${rawFormula}`;
|
|
147
|
+
const vElem = cellElem.elements?.find((e) => e.name === "v");
|
|
148
|
+
const cachedVal = vElem ? extractText(vElem) : null;
|
|
149
|
+
const formulaCell: FormulaCellValue = {
|
|
150
|
+
type: "formula",
|
|
151
|
+
formula: formulaStr,
|
|
152
|
+
cachedValue: cachedVal,
|
|
153
|
+
};
|
|
154
|
+
return formulaCell;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 2. Shared String (t="s")
|
|
158
|
+
if (t === "s") {
|
|
159
|
+
const vElem = cellElem.elements?.find((e) => e.name === "v");
|
|
160
|
+
const rawIdx = extractText(vElem).trim();
|
|
161
|
+
const idx = Number.parseInt(rawIdx, 10);
|
|
162
|
+
if (!Number.isNaN(idx) && idx >= 0 && idx < sharedStrings.length) {
|
|
163
|
+
return sharedStrings[idx];
|
|
164
|
+
}
|
|
165
|
+
return rawIdx;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 3. Inline String (t="inlineStr")
|
|
169
|
+
if (t === "inlineStr") {
|
|
170
|
+
const isElem = cellElem.elements?.find((e) => e.name === "is");
|
|
171
|
+
return extractText(isElem);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// 4. String literal (t="str")
|
|
175
|
+
if (t === "str") {
|
|
176
|
+
const vElem = cellElem.elements?.find((e) => e.name === "v");
|
|
177
|
+
return extractText(vElem);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 5. Boolean (t="b")
|
|
181
|
+
if (t === "b") {
|
|
182
|
+
const vElem = cellElem.elements?.find((e) => e.name === "v");
|
|
183
|
+
const vText = extractText(vElem).trim();
|
|
184
|
+
return vText === "1" || vText.toLowerCase() === "true";
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 6. Error cell (t="e")
|
|
188
|
+
if (t === "e") {
|
|
189
|
+
const vElem = cellElem.elements?.find((e) => e.name === "v");
|
|
190
|
+
return extractText(vElem);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// 7. Date cell (t="d")
|
|
194
|
+
if (t === "d") {
|
|
195
|
+
const vElem = cellElem.elements?.find((e) => e.name === "v");
|
|
196
|
+
const vText = extractText(vElem).trim();
|
|
197
|
+
return { type: "date", value: vText };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 8. Numeric / Decimal (t omitted or t="n")
|
|
201
|
+
const vElem = cellElem.elements?.find((e) => e.name === "v");
|
|
202
|
+
if (!vElem) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const rawValueText = extractText(vElem).trim();
|
|
207
|
+
if (rawValueText === "") {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Date style check on numeric serial
|
|
212
|
+
if (s !== undefined && dateStyles.has(s)) {
|
|
213
|
+
const serialNum = Number(rawValueText);
|
|
214
|
+
if (!Number.isNaN(serialNum)) {
|
|
215
|
+
return excelSerialToIso(serialNum);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Decimal-safety: parse raw text without float coercion
|
|
220
|
+
if (/^-?\d+$/.test(rawValueText)) {
|
|
221
|
+
// Pure integer string
|
|
222
|
+
const intVal = Number(rawValueText);
|
|
223
|
+
if (Number.isSafeInteger(intVal)) {
|
|
224
|
+
return intVal;
|
|
225
|
+
}
|
|
226
|
+
return { type: "decimal", value: rawValueText };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (/^-?\d+\.\d+$/.test(rawValueText)) {
|
|
230
|
+
// Real decimal value -> preserve exact string
|
|
231
|
+
return { type: "decimal", value: rawValueText };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Fallback for exponential or other numeric formats
|
|
235
|
+
return rawValueText;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Parses an XLSX workbook binary buffer with strict cap validation and ZIP gating.
|
|
240
|
+
*
|
|
241
|
+
* @param bytes Raw XLSX archive bytes (`Uint8Array`).
|
|
242
|
+
* @param options Caps and optional telemetry.
|
|
243
|
+
* @returns Parsed workbook containing worksheets and warnings.
|
|
244
|
+
*/
|
|
245
|
+
export async function parseWorkbook(bytes: Uint8Array, options?: ParseWorkbookOptions): Promise<WorkbookParse> {
|
|
246
|
+
const caps: ResolvedSheetsCaps = resolveSheetsCaps(options?.caps);
|
|
247
|
+
|
|
248
|
+
// 1. Enforce byte cap before any processing
|
|
249
|
+
validateByteCap(bytes.byteLength, caps);
|
|
250
|
+
|
|
251
|
+
// 2. Enforce ZIP signature gating (PK\x03\x04)
|
|
252
|
+
validateZipSignature(bytes);
|
|
253
|
+
|
|
254
|
+
const span = options?.telemetry?.startSpan("sheets.parse", {
|
|
255
|
+
"sheets.format": "xlsx",
|
|
256
|
+
"sheets.bytes": bytes.byteLength,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
// 3. Low-level archive unpack via @office-open/xlsx
|
|
261
|
+
let doc: ReturnType<typeof parseXlsx>;
|
|
262
|
+
try {
|
|
263
|
+
doc = parseXlsx(bytes);
|
|
264
|
+
} catch (err) {
|
|
265
|
+
if (err instanceof SheetsError) throw err;
|
|
266
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
267
|
+
throw new SheetsParseError(`failed to parse XLSX archive structure: ${message}`);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// 4. Enforce sheet count cap
|
|
271
|
+
const worksheetPaths = doc.worksheets ?? [];
|
|
272
|
+
if (worksheetPaths.length > caps.maxSheets) {
|
|
273
|
+
throw new SheetsCapError(`workbook contains ${worksheetPaths.length} sheets, exceeding maxSheets cap (${caps.maxSheets})`);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const ctx = new XlsxReadContext(doc);
|
|
277
|
+
const sharedStrings = extractSharedStrings(doc.sharedStrings);
|
|
278
|
+
const dateStyles = extractDateStyleIndices(doc.styles);
|
|
279
|
+
|
|
280
|
+
// 5. Match sheet names to part paths
|
|
281
|
+
const sheetDefs: Array<{ name: string; path: string }> = [];
|
|
282
|
+
const sheetsElem = doc.workbook?.elements?.find((e) => e.name === "sheets");
|
|
283
|
+
if (sheetsElem?.elements) {
|
|
284
|
+
for (const s of sheetsElem.elements) {
|
|
285
|
+
if (s.name === "sheet" && s.attributes) {
|
|
286
|
+
const name = String(s.attributes.name ?? `Sheet${sheetDefs.length + 1}`);
|
|
287
|
+
const rId = String(s.attributes["r:id"] ?? "");
|
|
288
|
+
const resolvedPath = rId ? ctx.resolveRelationship(rId) : undefined;
|
|
289
|
+
const targetPath = resolvedPath ?? worksheetPaths[sheetDefs.length];
|
|
290
|
+
if (targetPath) {
|
|
291
|
+
sheetDefs.push({ name, path: targetPath });
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (sheetDefs.length === 0) {
|
|
298
|
+
worksheetPaths.forEach((path, idx) => {
|
|
299
|
+
sheetDefs.push({ name: `Sheet${idx + 1}`, path });
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const resultSheets: SheetParse[] = [];
|
|
304
|
+
const warnings: InferenceWarning[] = [];
|
|
305
|
+
let totalRowCount = 0;
|
|
306
|
+
let maxColumnCount = 0;
|
|
307
|
+
|
|
308
|
+
// 6. Parse each worksheet
|
|
309
|
+
for (const def of sheetDefs) {
|
|
310
|
+
const wsElem = ctx.getPart(def.path);
|
|
311
|
+
if (!wsElem) {
|
|
312
|
+
resultSheets.push({ name: def.name, rows: [], schema: [] });
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const sheetDataElem = wsElem.elements?.find((e) => e.name === "sheetData");
|
|
317
|
+
const rowElements = sheetDataElem?.elements?.filter((e) => e.name === "row") ?? [];
|
|
318
|
+
|
|
319
|
+
if (rowElements.length > caps.maxRows) {
|
|
320
|
+
throw new SheetsCapError(`worksheet "${def.name}" contains ${rowElements.length} rows, exceeding maxRows cap (${caps.maxRows})`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const rows: CellValue[][] = [];
|
|
324
|
+
|
|
325
|
+
for (let rIdx = 0; rIdx < rowElements.length; rIdx++) {
|
|
326
|
+
const rowElem = rowElements[rIdx];
|
|
327
|
+
const cellElements = rowElem.elements?.filter((e) => e.name === "c") ?? [];
|
|
328
|
+
const rowCells: CellValue[] = [];
|
|
329
|
+
|
|
330
|
+
for (const cElem of cellElements) {
|
|
331
|
+
const rAttr = cElem.attributes?.r ? String(cElem.attributes.r) : undefined;
|
|
332
|
+
let colIdx = rowCells.length;
|
|
333
|
+
|
|
334
|
+
if (rAttr) {
|
|
335
|
+
const parsedCoord = parseA1Cell(rAttr);
|
|
336
|
+
if (parsedCoord) {
|
|
337
|
+
colIdx = parsedCoord.col - 1;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (colIdx + 1 > caps.maxColumns) {
|
|
342
|
+
throw new SheetsCapError(`worksheet "${def.name}" column index ${colIdx + 1} exceeds maxColumns cap (${caps.maxColumns})`);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Expand sparse columns with null
|
|
346
|
+
while (rowCells.length < colIdx) {
|
|
347
|
+
rowCells.push(null);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const cellVal = parseCellElement(cElem, sharedStrings, dateStyles);
|
|
351
|
+
rowCells[colIdx] = cellVal;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
rows.push(rowCells);
|
|
355
|
+
if (rowCells.length > maxColumnCount) {
|
|
356
|
+
maxColumnCount = rowCells.length;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
totalRowCount += rows.length;
|
|
361
|
+
const { schema, rows: transformedRows, warnings: sheetWarnings } = inferAndTransformRows(rows, caps);
|
|
362
|
+
for (const w of sheetWarnings) {
|
|
363
|
+
if (warnings.length < caps.maxWarnings) {
|
|
364
|
+
warnings.push(w);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
resultSheets.push({
|
|
368
|
+
name: def.name,
|
|
369
|
+
rows: transformedRows,
|
|
370
|
+
schema,
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
span?.setAttribute("sheets.sheetCount", resultSheets.length);
|
|
375
|
+
span?.setAttribute("sheets.rows", totalRowCount);
|
|
376
|
+
span?.setAttribute("sheets.columns", maxColumnCount);
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
sheets: resultSheets,
|
|
380
|
+
warnings,
|
|
381
|
+
};
|
|
382
|
+
} catch (err) {
|
|
383
|
+
span?.recordError();
|
|
384
|
+
throw err;
|
|
385
|
+
} finally {
|
|
386
|
+
span?.end();
|
|
387
|
+
}
|
|
388
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { parseSkillFile, type Skill } from "@arnilo/prism";
|
|
5
|
+
|
|
6
|
+
export const WORK_SKILL_NAMES = ["docx", "xlsx", "powerpoint", "pdf"] as const;
|
|
7
|
+
export type WorkSkillName = (typeof WORK_SKILL_NAMES)[number];
|
|
8
|
+
export const MAX_SKILL_FILE_BYTES = 64 * 1024;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* What the vendored Hermes skills need. They teach `python`, `openpyxl`, `python-docx`, `pypdf` and `soffice`
|
|
12
|
+
* run through the sandbox, and never name an `office_*` tool, so the one tool they depend on is `work_exec`.
|
|
13
|
+
* Declared here, in code, so the vendored bytes stay untouched and pinned to `vendor-lock.json`.
|
|
14
|
+
*/
|
|
15
|
+
export const WORK_SKILL_REQUIRES_TOOLS = ["work_exec"] as const;
|
|
16
|
+
|
|
17
|
+
export interface LoadWorkSkillsOptions {
|
|
18
|
+
readonly vendorRoot?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const DEFAULT_VENDOR_ROOT = fileURLToPath(new URL("../../vendor/hermes-agent", import.meta.url));
|
|
22
|
+
|
|
23
|
+
/** The four vendored office skills, each declaring `requiresTools: work_exec`, so a request without the sandbox tool hides them. */
|
|
24
|
+
export function loadWorkSkills(options: LoadWorkSkillsOptions = {}): Skill[] {
|
|
25
|
+
const root = options.vendorRoot ?? DEFAULT_VENDOR_ROOT;
|
|
26
|
+
return WORK_SKILL_NAMES.map((name) => loadOne(root, name));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function loadOne(root: string, name: WorkSkillName): Skill {
|
|
30
|
+
const path = join(root, "skills/productivity", name, "SKILL.md");
|
|
31
|
+
const data = readFileSync(path);
|
|
32
|
+
if (data.byteLength > MAX_SKILL_FILE_BYTES) {
|
|
33
|
+
throw new Error(`work skill ${name} exceeds ${MAX_SKILL_FILE_BYTES} byte cap`);
|
|
34
|
+
}
|
|
35
|
+
const skill = parseSkillFile(data.toString("utf8"), path);
|
|
36
|
+
if (skill.name !== name) {
|
|
37
|
+
throw new Error(`Expected skill name ${name}, got ${skill.name}`);
|
|
38
|
+
}
|
|
39
|
+
if (!skill.description || !skill.instructions) {
|
|
40
|
+
throw new Error(`work skill ${name} missing description or instructions`);
|
|
41
|
+
}
|
|
42
|
+
return { ...skill, requiresTools: [...WORK_SKILL_REQUIRES_TOOLS] };
|
|
43
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
2
|
+
|
|
3
|
+
/** Absolute sandbox root with bounded read/write operations. */
|
|
4
|
+
export interface WorkSandboxFilesystem {
|
|
5
|
+
readonly root: string;
|
|
6
|
+
readFile(path: string, options: { readonly maxBytes: number; readonly signal?: AbortSignal }): Promise<Uint8Array>;
|
|
7
|
+
writeFile(path: string, bytes: Uint8Array, options: { readonly maxBytes: number; readonly signal?: AbortSignal }): Promise<void>;
|
|
8
|
+
/** Required by backends that expose host paths. */
|
|
9
|
+
readonly assertPathInsideRoots?: (roots: readonly string[], target: string) => Promise<boolean>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function resolveSandboxPath(filesystem: WorkSandboxFilesystem, path: string): Promise<string> {
|
|
13
|
+
if (isAbsolute(path)) throw new TypeError("sandbox path must be relative");
|
|
14
|
+
const target = resolve(filesystem.root, path);
|
|
15
|
+
const rel = relative(resolve(filesystem.root), target);
|
|
16
|
+
if (rel.startsWith("..") || isAbsolute(rel)) throw new TypeError("sandbox path escapes the configured root");
|
|
17
|
+
if (filesystem.assertPathInsideRoots && !(await filesystem.assertPathInsideRoots([filesystem.root], target))) {
|
|
18
|
+
throw new TypeError("sandbox path escapes the configured root");
|
|
19
|
+
}
|
|
20
|
+
return target;
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./office.js";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { writeFile as fsWriteFile, open } from "node:fs/promises";
|
|
2
|
+
|
|
3
|
+
import type { WorkSandboxFilesystem } from "./filesystem.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `node:fs`-backed `WorkSandboxFilesystem` rooted at an absolute host path. The host chooses the
|
|
7
|
+
* root; every path is resolved and contained by {@link resolveSandboxPath} before it reaches here.
|
|
8
|
+
* Reads are bounded before allocation, so an oversize file fails closed instead of loading.
|
|
9
|
+
*/
|
|
10
|
+
export function createLocalWorkFilesystem(root: string): WorkSandboxFilesystem {
|
|
11
|
+
return {
|
|
12
|
+
root,
|
|
13
|
+
async readFile(path, options) {
|
|
14
|
+
const handle = await open(path, "r");
|
|
15
|
+
try {
|
|
16
|
+
const buffer = Buffer.alloc(options.maxBytes + 1);
|
|
17
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.byteLength, 0);
|
|
18
|
+
if (bytesRead > options.maxBytes) {
|
|
19
|
+
throw new Error(`file exceeds the ${options.maxBytes} byte cap`);
|
|
20
|
+
}
|
|
21
|
+
return buffer.subarray(0, bytesRead);
|
|
22
|
+
} finally {
|
|
23
|
+
await handle.close();
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
async writeFile(path, bytes, options) {
|
|
27
|
+
if (bytes.byteLength > options.maxBytes) {
|
|
28
|
+
throw new Error(`bytes exceed the ${options.maxBytes} byte cap`);
|
|
29
|
+
}
|
|
30
|
+
await fsWriteFile(path, bytes);
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|