@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,311 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { isAbsolute } from "node:path";
|
|
3
|
+
import { JsonObject, SecretRedactor, ToolDefinition, ToolExecutionContext, ToolResult, UNTRUSTED_EXTERNAL_TRUST } from "@arnilo/prism";
|
|
4
|
+
import { type ArtifactBodyRef, ArtifactBodyStore } from "@arnilo/prism/artifacts";
|
|
5
|
+
import { resolveDocumentCaps, validateByteCap } from "../documents/caps.js";
|
|
6
|
+
import type { DocumentCaps, DocumentFormat, DocumentModel, DocumentPatch } from "../documents/index.js";
|
|
7
|
+
import { resolveSandboxPath, type WorkSandboxFilesystem } from "./filesystem.js";
|
|
8
|
+
|
|
9
|
+
const NO_EFFECT = { kind: "none", idempotency: "none" } as const;
|
|
10
|
+
const EXTERNAL_MUTATION = { kind: "external_mutation", idempotency: "unsupported" } as const;
|
|
11
|
+
const READ_EFFECTS = ["read"] as const;
|
|
12
|
+
const WRITE_EFFECTS = ["write"] as const;
|
|
13
|
+
const BASE64 = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
14
|
+
|
|
15
|
+
export type OfficeFilesystem = WorkSandboxFilesystem;
|
|
16
|
+
|
|
17
|
+
export interface OfficeArtifactStore {
|
|
18
|
+
readonly bodies: ArtifactBodyStore;
|
|
19
|
+
/** Host owns artifact naming and ownership; tools only supply verified byte metadata. */
|
|
20
|
+
createRef(input: {
|
|
21
|
+
readonly context: ToolExecutionContext;
|
|
22
|
+
readonly format: DocumentFormat;
|
|
23
|
+
readonly mime: string;
|
|
24
|
+
readonly byteLength: number;
|
|
25
|
+
readonly contentHash: string;
|
|
26
|
+
}): ArtifactBodyRef;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface OfficeToolsOptions {
|
|
30
|
+
readonly caps?: DocumentCaps;
|
|
31
|
+
readonly redactor?: SecretRedactor;
|
|
32
|
+
readonly filesystem?: OfficeFilesystem;
|
|
33
|
+
readonly artifacts?: OfficeArtifactStore;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function objectSchema(properties: JsonObject, required: readonly string[]): JsonObject {
|
|
37
|
+
return { type: "object", properties, required: [...required], additionalProperties: false };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function errorResult(name: string, context: ToolExecutionContext, error: unknown): ToolResult {
|
|
41
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
42
|
+
return { toolCallId: context.toolCallId, name, content: [{ type: "text", text: message }], error: { message } };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function stringArg(args: JsonObject, key: string): string | undefined {
|
|
46
|
+
const value = args[key];
|
|
47
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function documentModel(value: unknown): DocumentModel {
|
|
51
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("model must be an object");
|
|
52
|
+
const model = value as DocumentModel;
|
|
53
|
+
const validateDocumentModel: typeof import("../documents/model-schema.js").validateDocumentModel =
|
|
54
|
+
require("../documents/model-schema.js").validateDocumentModel;
|
|
55
|
+
validateDocumentModel(model);
|
|
56
|
+
return model;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function documentPatches(value: unknown): readonly DocumentPatch[] {
|
|
60
|
+
if (!Array.isArray(value)) throw new TypeError("patches must be an array");
|
|
61
|
+
return value as readonly DocumentPatch[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function documentKind(value: unknown): "doc" | "sheet" | "deck" {
|
|
65
|
+
if (value === "doc" || value === "sheet" || value === "deck") return value;
|
|
66
|
+
throw new TypeError("kind must be doc, sheet, or deck");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function documentFormat(value: unknown): DocumentFormat {
|
|
70
|
+
if (value === "docx" || value === "xlsx" || value === "pptx") return value;
|
|
71
|
+
throw new TypeError("format must be docx, xlsx, or pptx");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function decodeBase64(value: string, maxBytes: number): Buffer {
|
|
75
|
+
const maxEncodedBytes = Math.ceil(maxBytes / 3) * 4;
|
|
76
|
+
if (Buffer.byteLength(value, "ascii") > maxEncodedBytes || value.length % 4 !== 0 || !BASE64.test(value)) {
|
|
77
|
+
throw new TypeError(`bytesBase64 exceeds the ${maxBytes} byte limit or is invalid base64`);
|
|
78
|
+
}
|
|
79
|
+
const bytes = Buffer.from(value, "base64");
|
|
80
|
+
if (bytes.toString("base64") !== value || bytes.byteLength > maxBytes) {
|
|
81
|
+
throw new TypeError(`bytesBase64 exceeds the ${maxBytes} byte limit or is invalid base64`);
|
|
82
|
+
}
|
|
83
|
+
return bytes;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function inputBytes(
|
|
87
|
+
args: JsonObject,
|
|
88
|
+
options: OfficeToolsOptions,
|
|
89
|
+
maxBytes: number,
|
|
90
|
+
context: ToolExecutionContext,
|
|
91
|
+
): Promise<Uint8Array> {
|
|
92
|
+
const bytesBase64 = stringArg(args, "bytesBase64");
|
|
93
|
+
const path = stringArg(args, "path");
|
|
94
|
+
if ((bytesBase64 === undefined) === (path === undefined)) throw new TypeError("provide exactly one of bytesBase64 or path");
|
|
95
|
+
if (bytesBase64 !== undefined) return decodeBase64(bytesBase64, maxBytes);
|
|
96
|
+
if (!options.filesystem) throw new TypeError("path requires a configured filesystem");
|
|
97
|
+
if (path === undefined) throw new TypeError("path is required");
|
|
98
|
+
const target = await resolveSandboxPath(options.filesystem, path);
|
|
99
|
+
const bytes = await options.filesystem.readFile(target, { maxBytes, signal: context.signal });
|
|
100
|
+
validateByteCap(bytes.byteLength, { ...resolveDocumentCaps(options.caps), maxBytes });
|
|
101
|
+
return bytes;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function outputRequested(args: JsonObject): boolean {
|
|
105
|
+
return stringArg(args, "outputPath") !== undefined || args.artifact === true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function outputFormat(args: JsonObject): DocumentFormat | undefined {
|
|
109
|
+
const value = args.format;
|
|
110
|
+
return value === undefined ? undefined : documentFormat(value);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function mime(format: DocumentFormat): string {
|
|
114
|
+
return format === "docx"
|
|
115
|
+
? "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
|
116
|
+
: format === "xlsx"
|
|
117
|
+
? "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
118
|
+
: "application/vnd.openxmlformats-officedocument.presentationml.presentation";
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function writeOutput(
|
|
122
|
+
bytes: Uint8Array,
|
|
123
|
+
contentHash: string,
|
|
124
|
+
format: DocumentFormat,
|
|
125
|
+
args: JsonObject,
|
|
126
|
+
options: OfficeToolsOptions,
|
|
127
|
+
context: ToolExecutionContext,
|
|
128
|
+
): Promise<{ readonly path?: string; readonly artifact?: ArtifactBodyRef }> {
|
|
129
|
+
const outputPath = stringArg(args, "outputPath");
|
|
130
|
+
const artifact = args.artifact === true;
|
|
131
|
+
if (outputPath !== undefined && artifact) throw new TypeError("provide only one of outputPath or artifact");
|
|
132
|
+
if (outputPath !== undefined) {
|
|
133
|
+
if (!options.filesystem) throw new TypeError("outputPath requires a configured filesystem");
|
|
134
|
+
const target = await resolveSandboxPath(options.filesystem, outputPath);
|
|
135
|
+
await options.filesystem.writeFile(target, bytes, { maxBytes: resolveDocumentCaps(options.caps).maxBytes, signal: context.signal });
|
|
136
|
+
return { path: target };
|
|
137
|
+
}
|
|
138
|
+
if (artifact) {
|
|
139
|
+
if (!options.artifacts) throw new TypeError("artifact requires a configured artifact store");
|
|
140
|
+
const ref = options.artifacts.createRef({ context, format, mime: mime(format), byteLength: bytes.byteLength, contentHash });
|
|
141
|
+
await options.artifacts.bodies.put(ref, bytes, { signal: context.signal });
|
|
142
|
+
return { artifact: ref };
|
|
143
|
+
}
|
|
144
|
+
return {};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function generatedValue(
|
|
148
|
+
format: DocumentFormat,
|
|
149
|
+
contentHash: string,
|
|
150
|
+
byteLength: number,
|
|
151
|
+
target: { readonly path?: string; readonly artifact?: ArtifactBodyRef },
|
|
152
|
+
) {
|
|
153
|
+
return { format, contentHash, byteLength, ...target };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const MODEL = { type: "object", description: "Validated Prism document model" } as JsonObject;
|
|
157
|
+
const PATCHES = { type: "array", items: { type: "object" }, description: "Prism document patch operations" } as JsonObject;
|
|
158
|
+
const INPUT = {
|
|
159
|
+
kind: { type: "string", enum: ["doc", "sheet", "deck"] },
|
|
160
|
+
bytesBase64: { type: "string", description: "Bounded OOXML bytes as canonical base64" },
|
|
161
|
+
path: { type: "string", description: "Sandbox-relative OOXML path" },
|
|
162
|
+
} as JsonObject;
|
|
163
|
+
const OUTPUT = {
|
|
164
|
+
outputPath: { type: "string", description: "Sandbox-relative output path" },
|
|
165
|
+
artifact: { type: "boolean", description: "Store bytes through the configured host artifact body store" },
|
|
166
|
+
} as JsonObject;
|
|
167
|
+
|
|
168
|
+
/** Thin, bounded tool facade over the Prism document primitives. */
|
|
169
|
+
export function createOfficeTools(options: OfficeToolsOptions = {}): readonly ToolDefinition[] {
|
|
170
|
+
const caps = resolveDocumentCaps(options.caps);
|
|
171
|
+
if (options.filesystem && !isAbsolute(options.filesystem.root)) throw new TypeError("filesystem.root must be absolute");
|
|
172
|
+
|
|
173
|
+
const observe = NO_EFFECT;
|
|
174
|
+
const mutation = (args: JsonObject) => (outputRequested(args) ? EXTERNAL_MUTATION : NO_EFFECT);
|
|
175
|
+
|
|
176
|
+
return [
|
|
177
|
+
{
|
|
178
|
+
name: "office_parse",
|
|
179
|
+
kind: "read",
|
|
180
|
+
effect: observe,
|
|
181
|
+
effects: READ_EFFECTS,
|
|
182
|
+
description: "Parse bounded OOXML bytes into a Prism document model. Parsed content is untrusted.",
|
|
183
|
+
parameters: objectSchema(INPUT, ["kind"]),
|
|
184
|
+
async execute(args, context) {
|
|
185
|
+
try {
|
|
186
|
+
const { parseDocument } = await import("../documents/index.js");
|
|
187
|
+
const model = await parseDocument(await inputBytes(args, options, caps.maxBytes, context), {
|
|
188
|
+
kind: documentKind(args.kind),
|
|
189
|
+
caps: options.caps,
|
|
190
|
+
redactor: options.redactor,
|
|
191
|
+
});
|
|
192
|
+
return { toolCallId: context.toolCallId, name: "office_parse", value: { model }, metadata: { trust: UNTRUSTED_EXTERNAL_TRUST } };
|
|
193
|
+
} catch (error) {
|
|
194
|
+
return errorResult("office_parse", context, error);
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: "office_import",
|
|
200
|
+
kind: "read",
|
|
201
|
+
effect: observe,
|
|
202
|
+
effects: READ_EFFECTS,
|
|
203
|
+
description: "Import bounded OOXML bytes with a fidelity report. Imported content is untrusted.",
|
|
204
|
+
parameters: objectSchema(INPUT, ["kind"]),
|
|
205
|
+
async execute(args, context) {
|
|
206
|
+
try {
|
|
207
|
+
const { importDocument } = await import("../documents/index.js");
|
|
208
|
+
const imported = await importDocument(await inputBytes(args, options, caps.maxBytes, context), {
|
|
209
|
+
kind: documentKind(args.kind),
|
|
210
|
+
caps: options.caps,
|
|
211
|
+
redactor: options.redactor,
|
|
212
|
+
});
|
|
213
|
+
return { toolCallId: context.toolCallId, name: "office_import", value: imported, metadata: { trust: UNTRUSTED_EXTERNAL_TRUST } };
|
|
214
|
+
} catch (error) {
|
|
215
|
+
return errorResult("office_import", context, error);
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: "office_generate",
|
|
221
|
+
kind: "edit",
|
|
222
|
+
effect: mutation,
|
|
223
|
+
effects: WRITE_EFFECTS,
|
|
224
|
+
description: "Generate OOXML from a validated model. Returns only hash/size; optionally writes sandbox or artifact bytes.",
|
|
225
|
+
parameters: objectSchema({ format: { type: "string", enum: ["docx", "xlsx", "pptx"] }, model: MODEL, ...OUTPUT }, [
|
|
226
|
+
"format",
|
|
227
|
+
"model",
|
|
228
|
+
]),
|
|
229
|
+
async execute(args, context) {
|
|
230
|
+
try {
|
|
231
|
+
const { generateDocument } = await import("../documents/index.js");
|
|
232
|
+
const format = documentFormat(args.format);
|
|
233
|
+
const generated = await generateDocument(documentModel(args.model), { format, caps: options.caps });
|
|
234
|
+
const target = await writeOutput(generated.bytes, generated.contentHash, format, args, options, context);
|
|
235
|
+
return {
|
|
236
|
+
toolCallId: context.toolCallId,
|
|
237
|
+
name: "office_generate",
|
|
238
|
+
value: generatedValue(format, generated.contentHash, generated.bytes.byteLength, target),
|
|
239
|
+
};
|
|
240
|
+
} catch (error) {
|
|
241
|
+
return errorResult("office_generate", context, error);
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
name: "office_patch",
|
|
247
|
+
kind: "edit",
|
|
248
|
+
effect: mutation,
|
|
249
|
+
effects: WRITE_EFFECTS,
|
|
250
|
+
description: "Apply validated document patches; optionally generate the patched model to sandbox or artifact bytes.",
|
|
251
|
+
parameters: objectSchema({ model: MODEL, patches: PATCHES, format: { type: "string", enum: ["docx", "xlsx", "pptx"] }, ...OUTPUT }, [
|
|
252
|
+
"model",
|
|
253
|
+
"patches",
|
|
254
|
+
]),
|
|
255
|
+
async execute(args, context) {
|
|
256
|
+
try {
|
|
257
|
+
const { patchDocument, generateDocument } = await import("../documents/index.js");
|
|
258
|
+
const model = patchDocument(documentModel(args.model), documentPatches(args.patches));
|
|
259
|
+
const format = outputFormat(args);
|
|
260
|
+
if (!outputRequested(args)) return { toolCallId: context.toolCallId, name: "office_patch", value: { model } };
|
|
261
|
+
if (!format) throw new TypeError("format is required when writing patched bytes");
|
|
262
|
+
const generated = await generateDocument(model, { format, caps: options.caps });
|
|
263
|
+
const target = await writeOutput(generated.bytes, generated.contentHash, format, args, options, context);
|
|
264
|
+
return {
|
|
265
|
+
toolCallId: context.toolCallId,
|
|
266
|
+
name: "office_patch",
|
|
267
|
+
value: { model, ...generatedValue(format, generated.contentHash, generated.bytes.byteLength, target) },
|
|
268
|
+
};
|
|
269
|
+
} catch (error) {
|
|
270
|
+
return errorResult("office_patch", context, error);
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
name: "office_diff",
|
|
276
|
+
kind: "read",
|
|
277
|
+
effect: observe,
|
|
278
|
+
effects: READ_EFFECTS,
|
|
279
|
+
description: "Return a bounded structural diff between two validated Prism document models.",
|
|
280
|
+
parameters: objectSchema({ from: MODEL, to: MODEL }, ["from", "to"]),
|
|
281
|
+
async execute(args, context) {
|
|
282
|
+
try {
|
|
283
|
+
const { diffDocument } = await import("../documents/index.js");
|
|
284
|
+
return {
|
|
285
|
+
toolCallId: context.toolCallId,
|
|
286
|
+
name: "office_diff",
|
|
287
|
+
value: diffDocument(documentModel(args.from), documentModel(args.to)),
|
|
288
|
+
};
|
|
289
|
+
} catch (error) {
|
|
290
|
+
return errorResult("office_diff", context, error);
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
name: "office_preview",
|
|
296
|
+
kind: "read",
|
|
297
|
+
effect: observe,
|
|
298
|
+
effects: READ_EFFECTS,
|
|
299
|
+
description: "Render a bounded, inert HTML preview of a validated Prism document model.",
|
|
300
|
+
parameters: objectSchema({ model: MODEL }, ["model"]),
|
|
301
|
+
async execute(args, context) {
|
|
302
|
+
try {
|
|
303
|
+
const { renderPreviewHtml } = await import("../documents/index.js");
|
|
304
|
+
return { toolCallId: context.toolCallId, name: "office_preview", value: { html: renderPreviewHtml(documentModel(args.model)) } };
|
|
305
|
+
} catch (error) {
|
|
306
|
+
return errorResult("office_preview", context, error);
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
];
|
|
311
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nous Research
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nous Research
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: docx
|
|
3
|
+
description: Create, read, edit, template, and review Word .docx files.
|
|
4
|
+
version: 1.1.0
|
|
5
|
+
author: Nous Research
|
|
6
|
+
license: MIT
|
|
7
|
+
platforms: [linux, macos, windows]
|
|
8
|
+
metadata:
|
|
9
|
+
hermes:
|
|
10
|
+
tags: [word, docx, documents, office, templates, revisions, comments]
|
|
11
|
+
category: productivity
|
|
12
|
+
related_skills: [pdf, xlsx, powerpoint]
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Docx Skill
|
|
16
|
+
|
|
17
|
+
Create, read, edit, and template Microsoft Word `.docx` files with
|
|
18
|
+
python-docx via small CLIs. It handles text, styles, lists, tables,
|
|
19
|
+
images, headers/footers, `{{token}}` templating, tracked changes
|
|
20
|
+
(list/accept/reject), comments (list/add/delete), TOC and page-number
|
|
21
|
+
fields, and package health checks. It does not render documents itself
|
|
22
|
+
(PDF needs LibreOffice — see Converting to PDF) or edit legacy `.doc`.
|
|
23
|
+
|
|
24
|
+
## When to Use
|
|
25
|
+
|
|
26
|
+
- The user asks to generate a Word document (report, letter, contract).
|
|
27
|
+
- You need the text, outline, styles, or embedded images of a `.docx`.
|
|
28
|
+
- You must change an existing `.docx`: replace text, edit table cells,
|
|
29
|
+
insert/delete paragraphs, apply styles, merge fragmented runs.
|
|
30
|
+
- You have a `.docx` template with `{{placeholders}}` to fill from data.
|
|
31
|
+
- The document has tracked changes to review, accept, or reject.
|
|
32
|
+
- You need to read reviewers' comments, or add/delete comments.
|
|
33
|
+
- A `.docx` won't open or behaves oddly and you need corruption triage.
|
|
34
|
+
- The document needs a table of contents or "Page X of Y" footers.
|
|
35
|
+
- Not for: `.doc` (legacy), `.odt`, or WYSIWYG layout work.
|
|
36
|
+
|
|
37
|
+
## Prerequisites
|
|
38
|
+
|
|
39
|
+
- Python 3.10+ with `python-docx` installed:
|
|
40
|
+
`pip install python-docx` (import name is `docx`; lxml comes with it).
|
|
41
|
+
- Comments `add` uses the native API on python-docx >= 1.2 and an XML
|
|
42
|
+
fallback on older versions — both are automatic.
|
|
43
|
+
- For image blocks: the image files must exist locally (PNG/JPEG).
|
|
44
|
+
|
|
45
|
+
## How to Run
|
|
46
|
+
|
|
47
|
+
All helpers live in `scripts/` next to this file. Run them with the
|
|
48
|
+
`terminal` tool; each supports `--help` and prints JSON to stdout.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
python scripts/docx_create.py spec.json out.docx
|
|
52
|
+
python scripts/docx_read.py out.docx --text
|
|
53
|
+
python scripts/docx_edit.py replace out.docx --find old --replace new
|
|
54
|
+
python scripts/docx_template.py tpl.docx values.json filled.docx
|
|
55
|
+
python scripts/docx_revisions.py list out.docx
|
|
56
|
+
python scripts/docx_comments.py list out.docx
|
|
57
|
+
python scripts/docx_validate.py out.docx
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Quick Reference
|
|
61
|
+
|
|
62
|
+
| Task | Command |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| Create from JSON spec | `docx_create.py spec.json out.docx` |
|
|
65
|
+
| Full text (body+tables+headers/footers) | `docx_read.py f.docx --text` |
|
|
66
|
+
| Heading outline + table shapes | `docx_read.py f.docx --structure` |
|
|
67
|
+
| Styles actually used | `docx_read.py f.docx --styles` |
|
|
68
|
+
| Extract embedded images | `docx_read.py f.docx --images outdir/` |
|
|
69
|
+
| Detect tracked changes/comments | `docx_read.py f.docx --revisions` |
|
|
70
|
+
| Find/replace (formatting kept) | `docx_edit.py replace f.docx --find A --replace B -o out.docx` |
|
|
71
|
+
| Set a table cell | `docx_edit.py set-cell f.docx --table 0 --row 1 --col 2 --text X` |
|
|
72
|
+
| Insert paragraph before index N | `docx_edit.py insert f.docx --index N --text X --style Normal` |
|
|
73
|
+
| Delete paragraph N | `docx_edit.py delete f.docx --index N` |
|
|
74
|
+
| Apply style to paragraph N | `docx_edit.py style f.docx --index N --style "Heading 1"` |
|
|
75
|
+
| Merge equal-format adjacent runs | `docx_edit.py normalize f.docx -o out.docx` |
|
|
76
|
+
| Insert TOC field before para N | `docx_edit.py toc f.docx --index N -o out.docx` |
|
|
77
|
+
| "Page X of Y" footer fields | `docx_edit.py page-numbers f.docx` |
|
|
78
|
+
| Fill `{{tokens}}` | `docx_template.py tpl.docx values.json out.docx --strict` |
|
|
79
|
+
| List revisions (id/author/date/text) | `docx_revisions.py list f.docx` |
|
|
80
|
+
| Accept / reject all revisions | `docx_revisions.py accept-all f.docx -o out.docx` (or `reject-all`) |
|
|
81
|
+
| Accept / reject one revision | `docx_revisions.py accept f.docx --id 3 -o out.docx` |
|
|
82
|
+
| List comments (+anchored text) | `docx_comments.py list f.docx` |
|
|
83
|
+
| Add comment anchored to text | `docx_comments.py add f.docx --target "phrase" --text "note" --author You` |
|
|
84
|
+
| Delete comment by id | `docx_comments.py delete f.docx --id 0` |
|
|
85
|
+
| Health-check the package | `docx_validate.py f.docx` (exit 1 on errors) |
|
|
86
|
+
|
|
87
|
+
## Procedure
|
|
88
|
+
|
|
89
|
+
1. **Create.** Write a JSON spec with `write_file`, then run
|
|
90
|
+
`scripts/docx_create.py`. The spec supports: `page` (size + margins in
|
|
91
|
+
mm), `header`/`footer` strings, `footer_page_numbers` (adds a
|
|
92
|
+
"Page X of Y" field footer), `styles` (custom paragraph styles with
|
|
93
|
+
font, size, bold/italic, hex `color`), and `blocks` — `heading`
|
|
94
|
+
(level 1-9), `paragraph` (either `text` or a `runs` list where each run
|
|
95
|
+
may set `bold`/`italic`/`underline`), `bullet_list`, `numbered_list`,
|
|
96
|
+
`table` (`header` row rendered bold, `rows`, optional built-in table
|
|
97
|
+
`style` such as `Table Grid`), `image` (`path`, optional `width_mm`),
|
|
98
|
+
`toc` (Table of Contents field), and `page_break`. The full spec
|
|
99
|
+
format is documented at the top of `scripts/docx_create.py`.
|
|
100
|
+
2. **Read.** Use `scripts/docx_read.py` with exactly one mode flag.
|
|
101
|
+
`--text` returns body paragraphs, all table cell text, and
|
|
102
|
+
header/footer text as JSON. `--structure` returns the heading outline
|
|
103
|
+
plus paragraph/table/section counts. `--images DIR` copies every file
|
|
104
|
+
under `word/media/` out of the package.
|
|
105
|
+
3. **Edit.** Use `scripts/docx_edit.py`. `replace` walks body, tables
|
|
106
|
+
(nested included), headers and footers, and preserves run formatting;
|
|
107
|
+
add `--body-only` to skip headers/footers. Pass `-o out.docx` to keep
|
|
108
|
+
the original; omit it to edit in place. Paragraph indices for
|
|
109
|
+
`insert`/`delete`/`style`/`toc` refer to `--structure`/`--text` body
|
|
110
|
+
order. Run `normalize` first on documents that came out of heavy Word
|
|
111
|
+
editing — it merges adjacent runs with identical formatting so later
|
|
112
|
+
find-replace matches reliably.
|
|
113
|
+
4. **Review revisions.** `docx_revisions.py list` reports every `w:ins`
|
|
114
|
+
and `w:del` (id, author, date, affected text) anywhere in body,
|
|
115
|
+
tables, headers, or footers. `accept-all` / `reject-all` resolve them
|
|
116
|
+
in bulk; `accept`/`reject --id N` handles a single revision. Accept
|
|
117
|
+
keeps insertions and drops deleted text; reject does the reverse.
|
|
118
|
+
5. **Comments.** `docx_comments.py list` returns each comment's id,
|
|
119
|
+
author, date, body text, and the document text it is anchored to.
|
|
120
|
+
`add --target "some phrase"` anchors a new comment to the first
|
|
121
|
+
occurrence of that phrase (runs are split as needed; formatting is
|
|
122
|
+
preserved). `delete --id N` removes the comment and its markers
|
|
123
|
+
without touching document text.
|
|
124
|
+
6. **Template.** Put `{{name}}`-style tokens in the document. Run
|
|
125
|
+
`scripts/docx_template.py` with a JSON object of values. Use
|
|
126
|
+
`--strict` to fail when tokens remain unfilled; the JSON output lists
|
|
127
|
+
`filled` counts and `unfilled_tokens` either way.
|
|
128
|
+
7. **Verify** (always): re-read the output with `--text` or
|
|
129
|
+
`--structure`, and run `docx_validate.py` on anything you produced
|
|
130
|
+
via revision/comment surgery.
|
|
131
|
+
|
|
132
|
+
## Converting to PDF
|
|
133
|
+
|
|
134
|
+
No script needed. When LibreOffice is installed, convert headlessly:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
soffice --headless --convert-to pdf --outdir outdir/ file.docx
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Check availability first (`command -v soffice || command -v
|
|
141
|
+
libreoffice`). If neither exists, tell the user PDF conversion is
|
|
142
|
+
unavailable in this environment rather than improvising — python-docx
|
|
143
|
+
cannot render PDFs, and layout fidelity requires a real renderer.
|
|
144
|
+
|
|
145
|
+
## Pitfalls
|
|
146
|
+
|
|
147
|
+
- **Tokens split across runs.** Word often fragments text into several
|
|
148
|
+
runs. The replace helpers collapse matched runs (replacement inherits
|
|
149
|
+
the first run's formatting); running `docx_edit.py normalize` first
|
|
150
|
+
reduces fragmentation for all later edits.
|
|
151
|
+
- **Revision coverage.** `docx_revisions.py` resolves run-level
|
|
152
|
+
insertions and deletions (the overwhelming majority). Paragraph-mark
|
|
153
|
+
and table-row revisions, format-change records, and moves are detected
|
|
154
|
+
by `--revisions` but not auto-resolved — see
|
|
155
|
+
`references/revisions-and-comments.md` and hand those to Word.
|
|
156
|
+
- **Comment threading.** Replies and "resolved" status live in
|
|
157
|
+
`commentsExtended.xml`, which this skill ignores; comments it adds are
|
|
158
|
+
plain top-level comments.
|
|
159
|
+
- **Field results are computed by Word.** `toc`, `page-numbers`, and the
|
|
160
|
+
`toc`/`footer_page_numbers` spec options write *field codes*.
|
|
161
|
+
Word/LibreOffice populates the actual entries and numbers when the
|
|
162
|
+
file is opened (Word may prompt to update fields); python-docx never
|
|
163
|
+
computes them, so placeholder text shows until then.
|
|
164
|
+
- **Validation is a health check, not schema validation.**
|
|
165
|
+
`docx_validate.py` verifies the zip, required parts, relationship
|
|
166
|
+
targets, image magic bytes, and referenced styles. It is NOT XSD
|
|
167
|
+
validation — a file can pass and still contain XML Word dislikes.
|
|
168
|
+
- **Style names must exist.** Applying a style that isn't defined in the
|
|
169
|
+
document raises `KeyError`. Built-ins like `Heading 1`, `List Bullet`,
|
|
170
|
+
`List Number`, `Table Grid` exist in the default template; custom
|
|
171
|
+
styles must be declared in the create spec first.
|
|
172
|
+
- **Numbered lists restart.** `List Number` relies on Word's default
|
|
173
|
+
numbering; separate lists in one document may continue numbering
|
|
174
|
+
instead of restarting. Warn users needing precise multi-list numbering.
|
|
175
|
+
- **Cell writes replace formatting.** `set-cell` uses `cell.text = ...`,
|
|
176
|
+
which resets runs in that cell to plain formatting.
|
|
177
|
+
- **Encoding.** All JSON specs/values files are read as UTF-8 explicitly;
|
|
178
|
+
never rely on locale defaults when writing your own glue code.
|
|
179
|
+
- **Don't unzip-and-sed the XML.** Edit through the scripts (or
|
|
180
|
+
python-docx); raw text substitution in `document.xml` corrupts files
|
|
181
|
+
easily. Use `patch`/`write_file` only for the JSON inputs, never on the
|
|
182
|
+
`.docx` itself.
|
|
183
|
+
|
|
184
|
+
## Verification
|
|
185
|
+
|
|
186
|
+
- After create/edit/template, run `docx_read.py out.docx --text` and
|
|
187
|
+
check the expected strings appear (and old strings are gone).
|
|
188
|
+
- After accept/reject, `docx_revisions.py list` should return `[]` (or
|
|
189
|
+
only the ids you intentionally left); after comment surgery,
|
|
190
|
+
`docx_comments.py list` should reflect the change and `--text` output
|
|
191
|
+
must be unchanged.
|
|
192
|
+
- `docx_validate.py out.docx` exits 0 with `"ok": true` on a healthy
|
|
193
|
+
package — run it after any revision/comment/field manipulation.
|
|
194
|
+
- For templates run with `--strict`, or check `unfilled_tokens == []`.
|
|
195
|
+
- Structure checks: `--structure` should show the expected heading
|
|
196
|
+
outline and table shapes; `--styles` confirms custom styles applied.
|