@arnilo/prism-tools 0.0.0-stage → 0.13.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.
Files changed (331) hide show
  1. package/CHANGELOG.md +418 -0
  2. package/LICENSE +21 -0
  3. package/README.md +44 -2
  4. package/browser/README.md +98 -0
  5. package/browser/skills/browser-automation/SKILL.md +14 -0
  6. package/browser/src/actions/cdp.ts +131 -0
  7. package/browser/src/actions/dialog.ts +25 -0
  8. package/browser/src/actions/download_release.ts +22 -0
  9. package/browser/src/actions/index.ts +37 -0
  10. package/browser/src/actions/locator.ts +70 -0
  11. package/browser/src/actions/navigate.ts +23 -0
  12. package/browser/src/actions/screenshot.ts +27 -0
  13. package/browser/src/actions/scroll.ts +24 -0
  14. package/browser/src/actions/select_page.ts +21 -0
  15. package/browser/src/actions/types.ts +114 -0
  16. package/browser/src/actions/upload.ts +44 -0
  17. package/browser/src/actions/wait.ts +45 -0
  18. package/browser/src/browser.ts +197 -0
  19. package/browser/src/cdp.ts +178 -0
  20. package/browser/src/checkpoint.ts +189 -0
  21. package/browser/src/config.ts +72 -0
  22. package/browser/src/downloads.ts +253 -0
  23. package/browser/src/errors.ts +31 -0
  24. package/browser/src/evaluate.ts +101 -0
  25. package/browser/src/index.ts +129 -0
  26. package/browser/src/limits.ts +271 -0
  27. package/browser/src/manager.ts +730 -0
  28. package/browser/src/network.ts +263 -0
  29. package/browser/src/observe.ts +178 -0
  30. package/browser/src/policy.ts +133 -0
  31. package/browser/src/screenshot.ts +107 -0
  32. package/browser/src/shared-sandbox.ts +53 -0
  33. package/browser/src/snapshot.ts +99 -0
  34. package/browser/src/targets.ts +165 -0
  35. package/browser/src/tools.ts +480 -0
  36. package/browser/src/types.ts +316 -0
  37. package/browser/src/uploads.ts +174 -0
  38. package/coding/README.md +337 -0
  39. package/coding/src/agent/acp-operations.ts +146 -0
  40. package/coding/src/agent/artifacts.ts +38 -0
  41. package/coding/src/agent/ask-user-decision.ts +623 -0
  42. package/coding/src/agent/atomic-write.ts +23 -0
  43. package/coding/src/agent/bounded-file.ts +21 -0
  44. package/coding/src/agent/checks.ts +301 -0
  45. package/coding/src/agent/coding-checkpoint.ts +828 -0
  46. package/coding/src/agent/delete.ts +237 -0
  47. package/coding/src/agent/diagnostics-tool.ts +163 -0
  48. package/coding/src/agent/diagnostics.ts +262 -0
  49. package/coding/src/agent/edit-diff.ts +382 -0
  50. package/coding/src/agent/edit.ts +322 -0
  51. package/coding/src/agent/effects.ts +102 -0
  52. package/coding/src/agent/env.ts +28 -0
  53. package/coding/src/agent/execution-policy.ts +89 -0
  54. package/coding/src/agent/file-mutation-queue.ts +72 -0
  55. package/coding/src/agent/forge/github.ts +603 -0
  56. package/coding/src/agent/forge/index.ts +16 -0
  57. package/coding/src/agent/forge/types.ts +185 -0
  58. package/coding/src/agent/git-aware-repository.ts +305 -0
  59. package/coding/src/agent/git-exec.ts +326 -0
  60. package/coding/src/agent/git-status.ts +177 -0
  61. package/coding/src/agent/git-tools.ts +360 -0
  62. package/coding/src/agent/git.ts +1074 -0
  63. package/coding/src/agent/glob-match.ts +146 -0
  64. package/coding/src/agent/glob.ts +182 -0
  65. package/coding/src/agent/goal-verify.ts +385 -0
  66. package/coding/src/agent/grep-worker.ts +74 -0
  67. package/coding/src/agent/grep.ts +459 -0
  68. package/coding/src/agent/identity.ts +48 -0
  69. package/coding/src/agent/index.ts +676 -0
  70. package/coding/src/agent/language/client.ts +429 -0
  71. package/coding/src/agent/language/framing.ts +190 -0
  72. package/coding/src/agent/language/index.ts +23 -0
  73. package/coding/src/agent/language/intelligence.ts +682 -0
  74. package/coding/src/agent/language/types.ts +187 -0
  75. package/coding/src/agent/lifecycle-events.ts +121 -0
  76. package/coding/src/agent/lifecycle.ts +166 -0
  77. package/coding/src/agent/limits.ts +214 -0
  78. package/coding/src/agent/move.ts +178 -0
  79. package/coding/src/agent/mutation-path.ts +52 -0
  80. package/coding/src/agent/output-accumulator.ts +304 -0
  81. package/coding/src/agent/path-utils.ts +120 -0
  82. package/coding/src/agent/process/index.ts +58 -0
  83. package/coding/src/agent/process/recovery.ts +508 -0
  84. package/coding/src/agent/process/sessions-host.ts +222 -0
  85. package/coding/src/agent/process/sessions-monitor.ts +141 -0
  86. package/coding/src/agent/process/sessions-recovery.ts +241 -0
  87. package/coding/src/agent/process/sessions-spawn.ts +613 -0
  88. package/coding/src/agent/process/sessions-teardown.ts +281 -0
  89. package/coding/src/agent/process/sessions.ts +30 -0
  90. package/coding/src/agent/process/types.ts +358 -0
  91. package/coding/src/agent/read-path-set.ts +126 -0
  92. package/coding/src/agent/read.ts +713 -0
  93. package/coding/src/agent/repository/glob.ts +147 -0
  94. package/coding/src/agent/repository/indexed-search.ts +480 -0
  95. package/coding/src/agent/repository/list.ts +131 -0
  96. package/coding/src/agent/repository/operations.ts +22 -0
  97. package/coding/src/agent/repository/path.ts +98 -0
  98. package/coding/src/agent/repository/search.ts +277 -0
  99. package/coding/src/agent/repository/types.ts +243 -0
  100. package/coding/src/agent/repository/walk.ts +126 -0
  101. package/coding/src/agent/repository.ts +12 -0
  102. package/coding/src/agent/shell-background.ts +264 -0
  103. package/coding/src/agent/shell-output.ts +75 -0
  104. package/coding/src/agent/shell.ts +526 -0
  105. package/coding/src/agent/todo.ts +399 -0
  106. package/coding/src/agent/truncate.ts +251 -0
  107. package/coding/src/agent/write.ts +170 -0
  108. package/coding/src/ask-user-host.ts +48 -0
  109. package/coding/src/config.ts +278 -0
  110. package/coding/src/hard-deny.ts +97 -0
  111. package/coding/src/index.ts +318 -0
  112. package/coding/src/principal-guard.ts +84 -0
  113. package/coding/src/sandbox.ts +92 -0
  114. package/coding/src/security/approval.ts +144 -0
  115. package/coding/src/security/command-rules.ts +57 -0
  116. package/coding/src/security/docker-cli.ts +222 -0
  117. package/coding/src/security/docker-sandbox.ts +1205 -0
  118. package/coding/src/security/e2b-sandbox.ts +853 -0
  119. package/coding/src/security/egress/dns-pin.ts +88 -0
  120. package/coding/src/security/egress/index.ts +32 -0
  121. package/coding/src/security/egress/limits.ts +57 -0
  122. package/coding/src/security/egress/policy.ts +137 -0
  123. package/coding/src/security/egress/proxy.ts +599 -0
  124. package/coding/src/security/egress/types.ts +30 -0
  125. package/coding/src/security/index.ts +199 -0
  126. package/coding/src/security/native-sandbox.ts +639 -0
  127. package/coding/src/security/path-containment.ts +55 -0
  128. package/coding/src/security/sandbox-coding-operations.ts +392 -0
  129. package/coding/src/security/sandbox-fs-operations.ts +644 -0
  130. package/coding/src/security/sandbox-limits.ts +147 -0
  131. package/coding/src/security/sandbox-tar.ts +308 -0
  132. package/coding/src/security/sandbox.ts +179 -0
  133. package/coding/src/security/semaphore.ts +55 -0
  134. package/coding/src/targets.ts +110 -0
  135. package/coding/src/workspace/diff.ts +78 -0
  136. package/coding/src/workspace/errors.ts +29 -0
  137. package/coding/src/workspace/index.ts +5 -0
  138. package/coding/src/workspace/merge.ts +151 -0
  139. package/coding/src/workspace/routing.ts +76 -0
  140. package/coding/src/workspace/service.ts +584 -0
  141. package/computer-use/README.md +105 -0
  142. package/computer-use/skills/computer-use-linux/SKILL.md +17 -0
  143. package/computer-use/src/classify.ts +53 -0
  144. package/computer-use/src/command.ts +29 -0
  145. package/computer-use/src/config.ts +69 -0
  146. package/computer-use/src/create.ts +170 -0
  147. package/computer-use/src/devices.ts +247 -0
  148. package/computer-use/src/env.ts +28 -0
  149. package/computer-use/src/index.ts +157 -0
  150. package/computer-use/src/skill.ts +21 -0
  151. package/openapi/README.md +97 -0
  152. package/openapi/src/compile.ts +386 -0
  153. package/openapi/src/config.ts +73 -0
  154. package/openapi/src/errors.ts +16 -0
  155. package/openapi/src/execute.ts +241 -0
  156. package/openapi/src/index.ts +124 -0
  157. package/openapi/src/limits.ts +63 -0
  158. package/openapi/src/specs.ts +86 -0
  159. package/openapi/src/tools.ts +60 -0
  160. package/openapi/src/types.ts +61 -0
  161. package/package.json +171 -4
  162. package/research/README.md +72 -0
  163. package/research/skills/research-report/SKILL.md +12 -0
  164. package/research/skills/research-scoping/SKILL.md +10 -0
  165. package/research/skills/research-synthesis/SKILL.md +10 -0
  166. package/research/skills/source-triage/SKILL.md +10 -0
  167. package/research/src/check.ts +71 -0
  168. package/research/src/config.ts +41 -0
  169. package/research/src/index.ts +147 -0
  170. package/research/src/ledger.ts +157 -0
  171. package/research/src/urls.ts +21 -0
  172. package/web/README.md +287 -0
  173. package/web/skills/web-research/SKILL.md +17 -0
  174. package/web/src/brave.ts +66 -0
  175. package/web/src/cache.ts +151 -0
  176. package/web/src/config.ts +210 -0
  177. package/web/src/controls.ts +128 -0
  178. package/web/src/domains.ts +114 -0
  179. package/web/src/exa.ts +60 -0
  180. package/web/src/firecrawl.ts +160 -0
  181. package/web/src/index.ts +387 -0
  182. package/web/src/kagi.ts +73 -0
  183. package/web/src/limits.ts +55 -0
  184. package/web/src/native/convert.ts +84 -0
  185. package/web/src/native/fetch.ts +226 -0
  186. package/web/src/normalize.ts +131 -0
  187. package/web/src/obscura/cdp.ts +201 -0
  188. package/web/src/obscura/classify.ts +35 -0
  189. package/web/src/obscura/cli.ts +254 -0
  190. package/web/src/obscura/errors.ts +8 -0
  191. package/web/src/obscura/index.ts +59 -0
  192. package/web/src/obscura/limits.ts +92 -0
  193. package/web/src/obscura/mcp.ts +104 -0
  194. package/web/src/obscura/playwright.ts +133 -0
  195. package/web/src/obscura/process.ts +235 -0
  196. package/web/src/obscura/search-profile.ts +73 -0
  197. package/web/src/obscura/types.ts +48 -0
  198. package/web/src/obscura/web.ts +419 -0
  199. package/web/src/prompt-fetch.ts +88 -0
  200. package/web/src/render.ts +75 -0
  201. package/web/src/searxng.ts +78 -0
  202. package/web/src/slice.ts +139 -0
  203. package/web/src/tavily.ts +77 -0
  204. package/web/src/tools.ts +182 -0
  205. package/web/src/transport.ts +310 -0
  206. package/web/src/types.ts +199 -0
  207. package/work/README.md +77 -0
  208. package/work/THIRD_PARTY_NOTICES +24 -0
  209. package/work/docling/ocr.py +150 -0
  210. package/work/sandbox/Dockerfile +36 -0
  211. package/work/sandbox/soffice.sh +12 -0
  212. package/work/src/command.ts +29 -0
  213. package/work/src/config.ts +138 -0
  214. package/work/src/connectors/cli.ts +343 -0
  215. package/work/src/connectors/drafts.ts +647 -0
  216. package/work/src/connectors/errors.ts +8 -0
  217. package/work/src/connectors/file-bytes.ts +149 -0
  218. package/work/src/connectors/google-workspace-http.ts +399 -0
  219. package/work/src/connectors/google-workspace.ts +465 -0
  220. package/work/src/connectors/gws-tools.ts +378 -0
  221. package/work/src/connectors/http.ts +123 -0
  222. package/work/src/connectors/idempotency.ts +235 -0
  223. package/work/src/connectors/identity.ts +34 -0
  224. package/work/src/connectors/index.ts +93 -0
  225. package/work/src/connectors/limits.ts +50 -0
  226. package/work/src/connectors/m365-tools.ts +308 -0
  227. package/work/src/connectors/microsoft365-http.ts +428 -0
  228. package/work/src/connectors/microsoft365.ts +402 -0
  229. package/work/src/connectors/normalize.ts +120 -0
  230. package/work/src/connectors/tool-helpers.ts +398 -0
  231. package/work/src/connectors/tools.ts +42 -0
  232. package/work/src/connectors/types.ts +399 -0
  233. package/work/src/diagrams/canonicalize.ts +166 -0
  234. package/work/src/diagrams/caps.ts +52 -0
  235. package/work/src/diagrams/embed.ts +340 -0
  236. package/work/src/diagrams/errors.ts +76 -0
  237. package/work/src/diagrams/index.ts +81 -0
  238. package/work/src/diagrams/messages.ts +150 -0
  239. package/work/src/diagrams/origin.ts +52 -0
  240. package/work/src/diagrams/telemetry.ts +34 -0
  241. package/work/src/diagrams/xml.ts +267 -0
  242. package/work/src/document-extraction/index.ts +563 -0
  243. package/work/src/document-reader/ambient.d.ts +2 -0
  244. package/work/src/document-reader/errors.ts +7 -0
  245. package/work/src/document-reader/index.ts +181 -0
  246. package/work/src/document-reader/mistral-ocr.ts +318 -0
  247. package/work/src/document-reader/parsers.ts +171 -0
  248. package/work/src/documents/caps.ts +135 -0
  249. package/work/src/documents/diff.ts +269 -0
  250. package/work/src/documents/errors.ts +44 -0
  251. package/work/src/documents/fidelity.ts +98 -0
  252. package/work/src/documents/generate.ts +96 -0
  253. package/work/src/documents/hash.ts +8 -0
  254. package/work/src/documents/index.ts +14 -0
  255. package/work/src/documents/model-schema.ts +639 -0
  256. package/work/src/documents/parse.ts +252 -0
  257. package/work/src/documents/patch-history.ts +81 -0
  258. package/work/src/documents/patch.ts +476 -0
  259. package/work/src/documents/preview-html.ts +245 -0
  260. package/work/src/documents/preview.ts +227 -0
  261. package/work/src/documents/telemetry.ts +36 -0
  262. package/work/src/documents/translate/docx.ts +292 -0
  263. package/work/src/documents/translate/pptx.ts +304 -0
  264. package/work/src/documents/translate/xlsx.ts +137 -0
  265. package/work/src/documents/types.ts +188 -0
  266. package/work/src/index.ts +311 -0
  267. package/work/src/runtime/worker-pool-entry.ts +102 -0
  268. package/work/src/runtime/worker-pool.ts +267 -0
  269. package/work/src/sandbox/composition.ts +212 -0
  270. package/work/src/sandbox/image.ts +6 -0
  271. package/work/src/sandbox/index.ts +14 -0
  272. package/work/src/sheets/caps.ts +68 -0
  273. package/work/src/sheets/csv.ts +351 -0
  274. package/work/src/sheets/decimal.ts +388 -0
  275. package/work/src/sheets/errors.ts +52 -0
  276. package/work/src/sheets/index.ts +62 -0
  277. package/work/src/sheets/inference.ts +378 -0
  278. package/work/src/sheets/telemetry.ts +34 -0
  279. package/work/src/sheets/types.ts +162 -0
  280. package/work/src/sheets/xlsx.ts +388 -0
  281. package/work/src/skills/index.ts +8 -0
  282. package/work/src/skills/load.ts +43 -0
  283. package/work/src/tools/filesystem.ts +21 -0
  284. package/work/src/tools/index.ts +1 -0
  285. package/work/src/tools/local-filesystem.ts +33 -0
  286. package/work/src/tools/office.ts +311 -0
  287. package/work/vendor/hermes-agent/LICENSE +21 -0
  288. package/work/vendor/hermes-agent/skills/productivity/docx/LICENSE +21 -0
  289. package/work/vendor/hermes-agent/skills/productivity/docx/SKILL.md +196 -0
  290. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_comments.py +289 -0
  291. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_common.py +94 -0
  292. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_create.py +177 -0
  293. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_edit.py +250 -0
  294. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_read.py +149 -0
  295. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_revisions.py +147 -0
  296. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_template.py +70 -0
  297. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_validate.py +156 -0
  298. package/work/vendor/hermes-agent/skills/productivity/pdf/LICENSE +21 -0
  299. package/work/vendor/hermes-agent/skills/productivity/pdf/SKILL.md +125 -0
  300. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/_raster.py +76 -0
  301. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/extract_marker.py +88 -0
  302. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/extract_pymupdf.py +101 -0
  303. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_create.py +130 -0
  304. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_fill_form.py +97 -0
  305. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_form_layout.py +168 -0
  306. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_make_form.py +145 -0
  307. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_merge.py +50 -0
  308. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_meta.py +115 -0
  309. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_page_image.py +99 -0
  310. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_read.py +153 -0
  311. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_secure.py +71 -0
  312. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_split.py +84 -0
  313. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_stamp.py +143 -0
  314. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_watermark.py +51 -0
  315. package/work/vendor/hermes-agent/skills/productivity/powerpoint/LICENSE +21 -0
  316. package/work/vendor/hermes-agent/skills/productivity/powerpoint/SKILL.md +220 -0
  317. package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_create.py +214 -0
  318. package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_edit.py +436 -0
  319. package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_from_template.py +88 -0
  320. package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_read.py +131 -0
  321. package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_render.py +93 -0
  322. package/work/vendor/hermes-agent/skills/productivity/xlsx/LICENSE +21 -0
  323. package/work/vendor/hermes-agent/skills/productivity/xlsx/SKILL.md +196 -0
  324. package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/csv_to_xlsx.py +104 -0
  325. package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_create.py +259 -0
  326. package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_edit.py +263 -0
  327. package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_read.py +160 -0
  328. package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_recalc.py +111 -0
  329. package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_restructure.py +337 -0
  330. package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_to_csv.py +69 -0
  331. package/work/vendor-lock.json +11 -0
@@ -0,0 +1,250 @@
1
+ #!/usr/bin/env python3
2
+ # MIT License. Part of the Hermes docx skill.
3
+ """Edit an existing .docx in place (or to a new file).
4
+
5
+ Subcommands:
6
+ replace find-and-replace text, preserving run formatting
7
+ set-cell set the text of a table cell
8
+ insert insert a paragraph before a given body paragraph index
9
+ delete delete a body paragraph by index
10
+ style apply a paragraph style to a body paragraph by index
11
+ normalize merge adjacent runs with identical formatting
12
+ toc insert a Table of Contents field at a body paragraph index
13
+ page-numbers add "Page X of Y" (PAGE/NUMPAGES fields) to the footer
14
+
15
+ Examples:
16
+ docx_edit.py replace in.docx --find old --replace new -o out.docx
17
+ docx_edit.py set-cell in.docx --table 0 --row 1 --col 2 --text "42"
18
+ docx_edit.py insert in.docx --index 3 --text "New para" --style Normal
19
+ docx_edit.py delete in.docx --index 3
20
+ docx_edit.py style in.docx --index 0 --style "Heading 1"
21
+ docx_edit.py normalize in.docx -o out.docx
22
+ docx_edit.py toc in.docx --index 1 -o out.docx
23
+ docx_edit.py page-numbers in.docx -o out.docx
24
+
25
+ Field results (TOC entries, page numbers) are computed by Word or
26
+ LibreOffice when the document is opened, not by python-docx; until then
27
+ the fields show placeholder text.
28
+ """
29
+ from __future__ import annotations
30
+
31
+ import argparse
32
+ import json
33
+ import sys
34
+
35
+ from docx import Document
36
+
37
+ from docx_common import iter_all_paragraphs, replace_in_paragraph
38
+
39
+ W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
40
+
41
+
42
+ def _q(tag: str) -> str:
43
+ return f"{{{W}}}{tag}"
44
+
45
+
46
+ def cmd_replace(doc, args) -> dict:
47
+ n = 0
48
+ for para in iter_all_paragraphs(doc):
49
+ n += replace_in_paragraph(para, args.find, args.replace)
50
+ return {"replacements": n}
51
+
52
+
53
+ def cmd_set_cell(doc, args) -> dict:
54
+ cell = doc.tables[args.table].cell(args.row, args.col)
55
+ cell.text = args.text
56
+ return {"table": args.table, "row": args.row, "col": args.col}
57
+
58
+
59
+ def cmd_insert(doc, args) -> dict:
60
+ paras = doc.paragraphs
61
+ if args.index < len(paras):
62
+ anchor = paras[args.index]
63
+ new_para = anchor.insert_paragraph_before(args.text, style=args.style)
64
+ else:
65
+ new_para = doc.add_paragraph(args.text, style=args.style)
66
+ return {"inserted_at": args.index, "text": new_para.text}
67
+
68
+
69
+ def cmd_delete(doc, args) -> dict:
70
+ para = doc.paragraphs[args.index]
71
+ el = para._element
72
+ el.getparent().remove(el)
73
+ return {"deleted_index": args.index}
74
+
75
+
76
+ def cmd_style(doc, args) -> dict:
77
+ doc.paragraphs[args.index].style = doc.styles[args.style]
78
+ return {"index": args.index, "style": args.style}
79
+
80
+
81
+ def _run_format_key(r_el) -> str:
82
+ """Canonical string for a run's w:rPr (None when absent)."""
83
+ from lxml import etree
84
+ rpr = r_el.find(_q("rPr"))
85
+ return "" if rpr is None else etree.tostring(rpr).decode("utf-8")
86
+
87
+
88
+ def cmd_normalize(doc) -> dict:
89
+ """Merge adjacent sibling runs with identical formatting."""
90
+ merged = 0
91
+ for para in iter_all_paragraphs(doc):
92
+ prev = None
93
+ for r_el in list(para._p):
94
+ if r_el.tag != _q("r"):
95
+ prev = None
96
+ continue
97
+ # only merge plain-text runs (no breaks, tabs, drawings...)
98
+ kids = {c.tag for c in r_el} - {_q("rPr"), _q("t")}
99
+ if kids:
100
+ prev = None
101
+ continue
102
+ if (prev is not None
103
+ and _run_format_key(prev) == _run_format_key(r_el)):
104
+ pt = prev.find(_q("t"))
105
+ ct = r_el.find(_q("t"))
106
+ if pt is None:
107
+ pt = prev.makeelement(_q("t"), {})
108
+ prev.append(pt)
109
+ pt.text = (pt.text or "") + ((ct.text or "")
110
+ if ct is not None else "")
111
+ pt.set("{http://www.w3.org/XML/1998/namespace}space",
112
+ "preserve")
113
+ r_el.getparent().remove(r_el)
114
+ merged += 1
115
+ else:
116
+ prev = r_el
117
+ return {"runs_merged": merged}
118
+
119
+
120
+ def _add_field(para, instr: str, placeholder: str) -> None:
121
+ """Append a complex field (begin/instrText/separate/result/end)."""
122
+ p = para._p
123
+ for ftype, extra in (("begin", None), (None, instr),
124
+ ("separate", None), (None, placeholder),
125
+ ("end", None)):
126
+ r = p.makeelement(_q("r"), {})
127
+ p.append(r)
128
+ if ftype is not None:
129
+ fld = r.makeelement(_q("fldChar"), {_q("fldCharType"): ftype})
130
+ r.append(fld)
131
+ elif extra is instr:
132
+ it = r.makeelement(_q("instrText"), {})
133
+ it.set("{http://www.w3.org/XML/1998/namespace}space", "preserve")
134
+ it.text = instr
135
+ r.append(it)
136
+ else:
137
+ t = r.makeelement(_q("t"), {})
138
+ t.text = extra
139
+ r.append(t)
140
+
141
+
142
+ def cmd_toc(doc, args) -> dict:
143
+ paras = doc.paragraphs
144
+ if args.index < len(paras):
145
+ para = paras[args.index].insert_paragraph_before("")
146
+ else:
147
+ para = doc.add_paragraph("")
148
+ _add_field(para, r' TOC \o "1-3" \h \z \u ',
149
+ "Table of contents - open in Word/LibreOffice and update "
150
+ "fields to populate.")
151
+ return {"toc_inserted_at": args.index}
152
+
153
+
154
+ def cmd_page_numbers(doc, args) -> dict:
155
+ footer = doc.sections[0].footer
156
+ para = footer.paragraphs[0] if footer.paragraphs \
157
+ else footer.add_paragraph()
158
+ para.add_run("Page ")
159
+ _add_field(para, " PAGE ", "1")
160
+ para.add_run(" of ")
161
+ _add_field(para, " NUMPAGES ", "1")
162
+ return {"footer_fields": ["PAGE", "NUMPAGES"]}
163
+
164
+
165
+ def main() -> int:
166
+ ap = argparse.ArgumentParser(description="Edit a .docx file.")
167
+ sub = ap.add_subparsers(dest="cmd", required=True)
168
+
169
+ def common(p):
170
+ p.add_argument("path", help="input .docx")
171
+ p.add_argument("-o", "--output",
172
+ help="output path (default: overwrite input)")
173
+
174
+ p = sub.add_parser("replace", help="find-and-replace text")
175
+ common(p)
176
+ p.add_argument("--find", required=True)
177
+ p.add_argument("--replace", required=True)
178
+ p.add_argument("--body-only", action="store_true",
179
+ help="skip headers/footers")
180
+
181
+ p = sub.add_parser("set-cell", help="set table cell text")
182
+ common(p)
183
+ p.add_argument("--table", type=int, required=True, help="table index")
184
+ p.add_argument("--row", type=int, required=True)
185
+ p.add_argument("--col", type=int, required=True)
186
+ p.add_argument("--text", required=True)
187
+
188
+ p = sub.add_parser("insert", help="insert paragraph at body index")
189
+ common(p)
190
+ p.add_argument("--index", type=int, required=True)
191
+ p.add_argument("--text", required=True)
192
+ p.add_argument("--style", default=None)
193
+
194
+ p = sub.add_parser("delete", help="delete body paragraph by index")
195
+ common(p)
196
+ p.add_argument("--index", type=int, required=True)
197
+
198
+ p = sub.add_parser("style", help="apply style to body paragraph")
199
+ common(p)
200
+ p.add_argument("--index", type=int, required=True)
201
+ p.add_argument("--style", required=True)
202
+
203
+ p = sub.add_parser("normalize",
204
+ help="merge adjacent runs with identical formatting")
205
+ common(p)
206
+
207
+ p = sub.add_parser("toc", help="insert a TOC field (Word computes it)")
208
+ common(p)
209
+ p.add_argument("--index", type=int, default=0,
210
+ help="body paragraph index to insert before (default 0)")
211
+
212
+ p = sub.add_parser("page-numbers",
213
+ help="add PAGE/NUMPAGES fields to the footer")
214
+ common(p)
215
+
216
+ args = ap.parse_args()
217
+ doc = Document(args.path)
218
+
219
+ if args.cmd == "replace":
220
+ if args.body_only:
221
+ n = 0
222
+ for para in iter_all_paragraphs(doc, include_headers_footers=False):
223
+ n += replace_in_paragraph(para, args.find, args.replace)
224
+ result = {"replacements": n}
225
+ else:
226
+ result = cmd_replace(doc, args)
227
+ elif args.cmd == "set-cell":
228
+ result = cmd_set_cell(doc, args)
229
+ elif args.cmd == "insert":
230
+ result = cmd_insert(doc, args)
231
+ elif args.cmd == "delete":
232
+ result = cmd_delete(doc, args)
233
+ elif args.cmd == "normalize":
234
+ result = cmd_normalize(doc)
235
+ elif args.cmd == "toc":
236
+ result = cmd_toc(doc, args)
237
+ elif args.cmd == "page-numbers":
238
+ result = cmd_page_numbers(doc, args)
239
+ else:
240
+ result = cmd_style(doc, args)
241
+
242
+ out = args.output or args.path
243
+ doc.save(out)
244
+ result.update({"ok": True, "output": out})
245
+ print(json.dumps(result, ensure_ascii=False))
246
+ return 0
247
+
248
+
249
+ if __name__ == "__main__":
250
+ sys.exit(main())
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env python3
2
+ # MIT License. Part of the Hermes docx skill.
3
+ """Read a .docx: text, structure outline, styles, images, revision detection.
4
+
5
+ Usage:
6
+ docx_read.py file.docx --text # full text incl. tables + headers/footers
7
+ docx_read.py file.docx --structure # JSON outline (headings, tables, counts)
8
+ docx_read.py file.docx --styles # JSON list of styles actually used
9
+ docx_read.py file.docx --images DIR # extract embedded images into DIR
10
+ docx_read.py file.docx --revisions # JSON: tracked changes / comments present?
11
+
12
+ Text output is JSON: {"body": [...], "tables": [[...rows]], "headers": [...],
13
+ "footers": [...]}. Body text is the accepted/as-is text (python-docx ignores
14
+ deleted-in-revision text and shows inserted text).
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import argparse
19
+ import json
20
+ import os
21
+ import sys
22
+ import zipfile
23
+
24
+ from docx import Document
25
+
26
+
27
+ def table_to_rows(table) -> list:
28
+ return [[cell.text for cell in row.cells] for row in table.rows]
29
+
30
+
31
+ def extract_text(doc) -> dict:
32
+ out = {"body": [p.text for p in doc.paragraphs],
33
+ "tables": [table_to_rows(t) for t in doc.tables],
34
+ "headers": [], "footers": []}
35
+ for section in doc.sections:
36
+ out["headers"].extend(p.text for p in section.header.paragraphs)
37
+ out["footers"].extend(p.text for p in section.footer.paragraphs)
38
+ for t in section.header.tables:
39
+ out["headers"].append(json.dumps(table_to_rows(t), ensure_ascii=False))
40
+ for t in section.footer.tables:
41
+ out["footers"].append(json.dumps(table_to_rows(t), ensure_ascii=False))
42
+ return out
43
+
44
+
45
+ def extract_structure(doc) -> dict:
46
+ outline = []
47
+ for i, para in enumerate(doc.paragraphs):
48
+ style = para.style.name if para.style else ""
49
+ if style.startswith("Heading"):
50
+ try:
51
+ level = int(style.split()[-1])
52
+ except ValueError:
53
+ level = 1
54
+ outline.append({"index": i, "level": level, "text": para.text})
55
+ return {
56
+ "outline": outline,
57
+ "paragraph_count": len(doc.paragraphs),
58
+ "table_count": len(doc.tables),
59
+ "tables": [{"rows": len(t.rows), "cols": len(t.columns)}
60
+ for t in doc.tables],
61
+ "section_count": len(doc.sections),
62
+ }
63
+
64
+
65
+ def styles_used(doc) -> list:
66
+ used = set()
67
+ for para in doc.paragraphs:
68
+ if para.style:
69
+ used.add(para.style.name)
70
+ for run in para.runs:
71
+ if run.style:
72
+ used.add(run.style.name)
73
+ for table in doc.tables:
74
+ if table.style:
75
+ used.add(table.style.name)
76
+ for row in table.rows:
77
+ for cell in row.cells:
78
+ for para in cell.paragraphs:
79
+ if para.style:
80
+ used.add(para.style.name)
81
+ return sorted(used)
82
+
83
+
84
+ def extract_images(path: str, outdir: str) -> list:
85
+ os.makedirs(outdir, exist_ok=True)
86
+ written = []
87
+ with zipfile.ZipFile(path) as zf:
88
+ for name in zf.namelist():
89
+ if name.startswith("word/media/"):
90
+ target = os.path.join(outdir, os.path.basename(name))
91
+ with open(target, "wb") as f:
92
+ f.write(zf.read(name))
93
+ written.append(target)
94
+ return written
95
+
96
+
97
+ def detect_revisions(path: str) -> dict:
98
+ """Detect tracked changes and comments by scanning the raw XML parts."""
99
+ markers = {"insertions": b"<w:ins ", "deletions": b"<w:del ",
100
+ "format_changes": b"<w:rPrChange"}
101
+ result = {k: False for k in markers}
102
+ result["comments"] = False
103
+ with zipfile.ZipFile(path) as zf:
104
+ names = zf.namelist()
105
+ result["comments"] = any(n.startswith("word/comments") for n in names)
106
+ for name in names:
107
+ if name.startswith("word/") and name.endswith(".xml"):
108
+ data = zf.read(name)
109
+ for key, marker in markers.items():
110
+ if marker in data:
111
+ result[key] = True
112
+ result["has_tracked_changes"] = any(
113
+ result[k] for k in ("insertions", "deletions", "format_changes"))
114
+ return result
115
+
116
+
117
+ def main() -> int:
118
+ ap = argparse.ArgumentParser(description="Read/inspect a .docx file.")
119
+ ap.add_argument("path", help=".docx file to read")
120
+ g = ap.add_mutually_exclusive_group(required=True)
121
+ g.add_argument("--text", action="store_true", help="extract all text as JSON")
122
+ g.add_argument("--structure", action="store_true", help="outline JSON")
123
+ g.add_argument("--styles", action="store_true", help="styles used, JSON")
124
+ g.add_argument("--images", metavar="DIR", help="extract images to DIR")
125
+ g.add_argument("--revisions", action="store_true",
126
+ help="detect tracked changes / comments")
127
+ args = ap.parse_args()
128
+
129
+ if args.images:
130
+ print(json.dumps({"images": extract_images(args.path, args.images)},
131
+ ensure_ascii=False))
132
+ return 0
133
+ if args.revisions:
134
+ print(json.dumps(detect_revisions(args.path), ensure_ascii=False))
135
+ return 0
136
+
137
+ doc = Document(args.path)
138
+ if args.text:
139
+ out = extract_text(doc)
140
+ elif args.structure:
141
+ out = extract_structure(doc)
142
+ else:
143
+ out = {"styles": styles_used(doc)}
144
+ print(json.dumps(out, ensure_ascii=False, indent=2))
145
+ return 0
146
+
147
+
148
+ if __name__ == "__main__":
149
+ sys.exit(main())
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env python3
2
+ # MIT License. Part of the Hermes docx skill.
3
+ """Inspect and resolve tracked changes (w:ins / w:del) in a .docx.
4
+
5
+ Subcommands:
6
+ list JSON list of revisions: id, author, date, type, text
7
+ accept-all accept every insertion and deletion
8
+ reject-all reject every insertion and deletion
9
+ accept accept one revision by --id
10
+ reject reject one revision by --id
11
+
12
+ Examples:
13
+ docx_revisions.py list report.docx
14
+ docx_revisions.py accept-all report.docx -o accepted.docx
15
+ docx_revisions.py reject report.docx --id 3 -o out.docx
16
+
17
+ Semantics (direct XML manipulation, python-docx oxml layer):
18
+ accept w:ins -> unwrap (keep inserted runs) reject w:ins -> remove
19
+ accept w:del -> remove reject w:del -> restore
20
+ (restore = w:delText tags renamed to w:t, wrapper unwrapped)
21
+
22
+ Covers run-level insertions/deletions anywhere in body, tables (nested
23
+ included), headers and footers. Row/paragraph-mark revisions and format
24
+ changes (w:rPrChange etc.) are reported by docx_read.py --revisions but
25
+ not resolved here.
26
+ """
27
+ from __future__ import annotations
28
+
29
+ import argparse
30
+ import json
31
+ import sys
32
+
33
+ from docx import Document
34
+
35
+ from docx_common import iter_part_roots
36
+
37
+ W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
38
+
39
+
40
+ def q(tag: str) -> str:
41
+ return f"{{{W}}}{tag}"
42
+
43
+
44
+ INS, DEL = q("ins"), q("del")
45
+
46
+
47
+ def _iter_revision_elements(doc):
48
+ """Yield every w:ins / w:del element across body, headers, footers."""
49
+ for root in iter_part_roots(doc):
50
+ for el in root.iter(INS, DEL):
51
+ yield el
52
+
53
+
54
+ def _rev_text(el) -> str:
55
+ tag = q("delText") if el.tag == DEL else q("t")
56
+ return "".join(t.text or "" for t in el.iter(tag))
57
+
58
+
59
+ def _rev_record(el) -> dict:
60
+ return {
61
+ "id": el.get(q("id")),
62
+ "author": el.get(q("author")),
63
+ "date": el.get(q("date")),
64
+ "type": "insertion" if el.tag == INS else "deletion",
65
+ "text": _rev_text(el),
66
+ }
67
+
68
+
69
+ def _unwrap(el) -> None:
70
+ """Replace `el` with its children, keeping document order."""
71
+ parent = el.getparent()
72
+ idx = list(parent).index(el)
73
+ for child in list(el):
74
+ parent.insert(idx, child)
75
+ idx += 1
76
+ parent.remove(el)
77
+
78
+
79
+ def _apply(el, accept: bool) -> None:
80
+ if el.getparent() is None: # already detached via an outer wrapper
81
+ return
82
+ if el.tag == INS:
83
+ if accept:
84
+ _unwrap(el)
85
+ else:
86
+ el.getparent().remove(el)
87
+ else: # w:del
88
+ if accept:
89
+ el.getparent().remove(el)
90
+ else:
91
+ for dt in list(el.iter(q("delText"))):
92
+ dt.tag = q("t")
93
+ _unwrap(el)
94
+
95
+
96
+ def resolve(doc, accept: bool, rev_id: str | None = None) -> int:
97
+ targets = [el for el in _iter_revision_elements(doc)
98
+ if rev_id is None or el.get(q("id")) == rev_id]
99
+ for el in targets:
100
+ _apply(el, accept)
101
+ return len(targets)
102
+
103
+
104
+ def main() -> int:
105
+ ap = argparse.ArgumentParser(
106
+ description="List, accept, or reject tracked changes in a .docx.")
107
+ sub = ap.add_subparsers(dest="cmd", required=True)
108
+
109
+ def common(p, out=True):
110
+ p.add_argument("path", help="input .docx")
111
+ if out:
112
+ p.add_argument("-o", "--output",
113
+ help="output path (default: overwrite input)")
114
+
115
+ common(sub.add_parser("list", help="list revisions as JSON"), out=False)
116
+ common(sub.add_parser("accept-all", help="accept every revision"))
117
+ common(sub.add_parser("reject-all", help="reject every revision"))
118
+ for name in ("accept", "reject"):
119
+ p = sub.add_parser(name, help=f"{name} one revision by id")
120
+ common(p)
121
+ p.add_argument("--id", required=True, help="revision id (w:id)")
122
+
123
+ args = ap.parse_args()
124
+ doc = Document(args.path)
125
+
126
+ if args.cmd == "list":
127
+ revs = [_rev_record(el) for el in _iter_revision_elements(doc)]
128
+ print(json.dumps({"ok": True, "revisions": revs}, ensure_ascii=False))
129
+ return 0
130
+
131
+ accept = args.cmd in ("accept-all", "accept")
132
+ rev_id = getattr(args, "id", None)
133
+ n = resolve(doc, accept, rev_id)
134
+ if rev_id is not None and n == 0:
135
+ print(json.dumps({"ok": False,
136
+ "error": f"no revision with id {rev_id}"}))
137
+ return 1
138
+ out = args.output or args.path
139
+ doc.save(out)
140
+ print(json.dumps({"ok": True, "output": out, "resolved": n,
141
+ "action": "accept" if accept else "reject"},
142
+ ensure_ascii=False))
143
+ return 0
144
+
145
+
146
+ if __name__ == "__main__":
147
+ sys.exit(main())
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env python3
2
+ # MIT License. Part of the Hermes docx skill.
3
+ """Fill {{placeholder}} tokens in a .docx from a JSON mapping.
4
+
5
+ Tokens are replaced everywhere: body paragraphs, tables (including nested
6
+ tables), headers and footers. Run formatting is preserved; tokens split
7
+ across runs are handled.
8
+
9
+ Usage:
10
+ docx_template.py template.docx values.json output.docx
11
+ docx_template.py template.docx values.json output.docx --strict
12
+
13
+ values.json: {"name": "Ada", "date": "2026-01-01"} fills {{name}}, {{date}}.
14
+ With --strict, exits 1 if any {{token}} remains unfilled after processing.
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import argparse
19
+ import json
20
+ import re
21
+ import sys
22
+
23
+ from docx import Document
24
+
25
+ from docx_common import iter_all_paragraphs, replace_in_paragraph
26
+
27
+ TOKEN_RE = re.compile(r"\{\{\s*([A-Za-z0-9_.-]+)\s*\}\}")
28
+
29
+
30
+ def main() -> int:
31
+ ap = argparse.ArgumentParser(
32
+ description="Fill {{token}} placeholders in a .docx from JSON.")
33
+ ap.add_argument("template", help="input .docx with {{tokens}}")
34
+ ap.add_argument("values", help="JSON file of token -> value")
35
+ ap.add_argument("output", help="output .docx path")
36
+ ap.add_argument("--strict", action="store_true",
37
+ help="fail if any token remains unfilled")
38
+ args = ap.parse_args()
39
+
40
+ with open(args.values, encoding="utf-8") as f:
41
+ values = json.load(f)
42
+
43
+ doc = Document(args.template)
44
+ filled = {}
45
+ for para in iter_all_paragraphs(doc):
46
+ # Normalize whitespace variants like {{ name }} first.
47
+ for m in set(TOKEN_RE.findall(para.text)):
48
+ if m in values:
49
+ # Replace any spacing variant with canonical token, then fill.
50
+ for variant in set(
51
+ t.group(0) for t in TOKEN_RE.finditer(para.text)
52
+ if t.group(1) == m):
53
+ n = replace_in_paragraph(para, variant, str(values[m]))
54
+ filled[m] = filled.get(m, 0) + n
55
+
56
+ remaining = sorted({m for para in iter_all_paragraphs(doc)
57
+ for m in TOKEN_RE.findall(para.text)})
58
+ doc.save(args.output)
59
+ result = {"ok": True, "output": args.output, "filled": filled,
60
+ "unfilled_tokens": remaining}
61
+ if args.strict and remaining:
62
+ result["ok"] = False
63
+ print(json.dumps(result, ensure_ascii=False))
64
+ return 1
65
+ print(json.dumps(result, ensure_ascii=False))
66
+ return 0
67
+
68
+
69
+ if __name__ == "__main__":
70
+ sys.exit(main())