dsh-office-kit 0.3.0 → 0.3.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 (72) hide show
  1. package/assets/skills/office-documents/render_docx.py +7 -0
  2. package/assets/skills/office-documents/scripts/a11y_audit.py +7 -0
  3. package/assets/skills/office-documents/scripts/accept_tracked_changes.py +7 -0
  4. package/assets/skills/office-documents/scripts/add_tracked_replacements.py +7 -0
  5. package/assets/skills/office-documents/scripts/append_blocks.py +7 -0
  6. package/assets/skills/office-documents/scripts/apply_template_styles.py +7 -0
  7. package/assets/skills/office-documents/scripts/captions_and_crossrefs.py +7 -0
  8. package/assets/skills/office-documents/scripts/comments_add.py +7 -0
  9. package/assets/skills/office-documents/scripts/comments_apply_patch.py +7 -0
  10. package/assets/skills/office-documents/scripts/comments_extract.py +7 -0
  11. package/assets/skills/office-documents/scripts/comments_strip.py +7 -0
  12. package/assets/skills/office-documents/scripts/content_controls.py +7 -0
  13. package/assets/skills/office-documents/scripts/core_properties.py +7 -0
  14. package/assets/skills/office-documents/scripts/docx_kit.py +7 -0
  15. package/assets/skills/office-documents/scripts/docx_ooxml_patch.py +7 -0
  16. package/assets/skills/office-documents/scripts/docx_table_to_csv.py +7 -0
  17. package/assets/skills/office-documents/scripts/fields_materialize.py +7 -0
  18. package/assets/skills/office-documents/scripts/fields_report.py +7 -0
  19. package/assets/skills/office-documents/scripts/flatten_ref_fields.py +7 -0
  20. package/assets/skills/office-documents/scripts/footnotes_report.py +7 -0
  21. package/assets/skills/office-documents/scripts/google_docs_title_sanitize.py +7 -0
  22. package/assets/skills/office-documents/scripts/heading_audit.py +7 -0
  23. package/assets/skills/office-documents/scripts/images_audit.py +7 -0
  24. package/assets/skills/office-documents/scripts/insert_note.py +7 -0
  25. package/assets/skills/office-documents/scripts/insert_ref_fields.py +7 -0
  26. package/assets/skills/office-documents/scripts/insert_toc.py +7 -0
  27. package/assets/skills/office-documents/scripts/internal_nav.py +7 -0
  28. package/assets/skills/office-documents/scripts/make_fixtures.py +7 -0
  29. package/assets/skills/office-documents/scripts/merge_docx_append.py +7 -0
  30. package/assets/skills/office-documents/scripts/privacy_scrub.py +7 -0
  31. package/assets/skills/office-documents/scripts/redact_docx.py +7 -0
  32. package/assets/skills/office-documents/scripts/render_and_diff.py +7 -0
  33. package/assets/skills/office-documents/scripts/replace_text.py +7 -0
  34. package/assets/skills/office-documents/scripts/section_audit.py +7 -0
  35. package/assets/skills/office-documents/scripts/set_protection.py +7 -0
  36. package/assets/skills/office-documents/scripts/style_lint.py +7 -0
  37. package/assets/skills/office-documents/scripts/style_normalize.py +7 -0
  38. package/assets/skills/office-documents/scripts/table_geometry.py +7 -0
  39. package/assets/skills/office-documents/scripts/watermark_add.py +7 -0
  40. package/assets/skills/office-documents/scripts/watermark_audit_remove.py +7 -0
  41. package/assets/skills/office-documents/scripts/xlsx_to_docx_table.py +7 -0
  42. package/assets/skills/office-presentations/container_tools/add_slides.py +7 -0
  43. package/assets/skills/office-presentations/container_tools/core_properties.py +7 -0
  44. package/assets/skills/office-presentations/container_tools/create_montage.py +7 -0
  45. package/assets/skills/office-presentations/container_tools/inspect_presentation_layout_geometry.py +7 -0
  46. package/assets/skills/office-presentations/container_tools/inspect_presentation_package_integrity.py +7 -0
  47. package/assets/skills/office-presentations/container_tools/native_chart_title_gate.py +7 -0
  48. package/assets/skills/office-presentations/container_tools/native_quantitative_chart_gate.py +7 -0
  49. package/assets/skills/office-presentations/container_tools/native_table_arithmetic_gate.py +7 -0
  50. package/assets/skills/office-presentations/container_tools/ooxml_helpers.py +7 -0
  51. package/assets/skills/office-presentations/container_tools/preview_render.py +7 -0
  52. package/assets/skills/office-presentations/container_tools/render_slides.py +7 -0
  53. package/assets/skills/office-presentations/container_tools/replace_text.py +7 -0
  54. package/assets/skills/office-presentations/container_tools/template_source_fidelity_gate.py +7 -0
  55. package/assets/skills/office-presentations/template_following_scripts/check_template_fidelity.py +7 -0
  56. package/assets/skills/office-presentations/template_following_scripts/inspect_template_deck.py +7 -0
  57. package/assets/skills/office-presentations/template_following_scripts/make_contact_sheet.py +7 -0
  58. package/assets/skills/office-presentations/template_following_scripts/prepare_template_starter_deck.py +7 -0
  59. package/assets/skills/office-presentations/template_following_scripts/validate_template_plan.py +7 -0
  60. package/assets/skills/office-spreadsheets/container_tools/add_chart.py +7 -0
  61. package/assets/skills/office-spreadsheets/container_tools/add_image.py +7 -0
  62. package/assets/skills/office-spreadsheets/container_tools/append_rows.py +7 -0
  63. package/assets/skills/office-spreadsheets/container_tools/diff_workbooks.py +7 -0
  64. package/assets/skills/office-spreadsheets/container_tools/inspect_workbook.py +7 -0
  65. package/assets/skills/office-spreadsheets/container_tools/recalc_gate.py +19 -7
  66. package/assets/skills/office-spreadsheets/container_tools/set_cells.py +7 -0
  67. package/assets/skills/office-spreadsheets/container_tools/sheet_ops.py +7 -0
  68. package/assets/skills/office-spreadsheets/container_tools/style_cells.py +7 -0
  69. package/assets/skills/shared/check_runtime.py +7 -0
  70. package/dist/index.js +14 -7
  71. package/package.json +1 -1
  72. package/python/office_cli.py +42 -8
@@ -13,6 +13,13 @@ import json
13
13
  import sys
14
14
  from pathlib import Path
15
15
 
16
+ # Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
17
+ for _stream in (sys.stdout, sys.stderr):
18
+ try:
19
+ _stream.reconfigure(encoding="utf-8", errors="replace")
20
+ except (AttributeError, ValueError):
21
+ pass
22
+
16
23
  try:
17
24
  from PIL import Image, ImageDraw, ImageFont
18
25
  except ImportError: # pragma: no cover - whitelist dependency, but fail clearly
@@ -42,6 +42,13 @@ from ooxml_helpers import ( # noqa: E402
42
42
  text_of,
43
43
  )
44
44
 
45
+ # Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
46
+ for _stream in (sys.stdout, sys.stderr):
47
+ try:
48
+ _stream.reconfigure(encoding="utf-8", errors="replace")
49
+ except (AttributeError, ValueError):
50
+ pass
51
+
45
52
  TITLE_OUTSIDE_MARGIN_IN = 0.0
46
53
  DEFAULT_TITLE_FLOOR_PT = 28.0
47
54
  DEFAULT_BODY_FLOOR_PT = 14.0
@@ -16,6 +16,13 @@ import zipfile
16
16
  from pathlib import Path
17
17
  from xml.etree import ElementTree
18
18
 
19
+ # Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
20
+ for _stream in (sys.stdout, sys.stderr):
21
+ try:
22
+ _stream.reconfigure(encoding="utf-8", errors="replace")
23
+ except (AttributeError, ValueError):
24
+ pass
25
+
19
26
  REL_NS = "{http://schemas.openxmlformats.org/package/2006/relationships}"
20
27
  CT_NS = "{http://schemas.openxmlformats.org/package/2006/content-types}"
21
28
  P_NS = "{http://schemas.openxmlformats.org/presentationml/2006/main}"
@@ -20,6 +20,13 @@ import zipfile
20
20
  from pathlib import Path
21
21
  from xml.etree import ElementTree
22
22
 
23
+ # Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
24
+ for _stream in (sys.stdout, sys.stderr):
25
+ try:
26
+ _stream.reconfigure(encoding="utf-8", errors="replace")
27
+ except (AttributeError, ValueError):
28
+ pass
29
+
23
30
  sys.path.insert(0, str(Path(__file__).resolve().parent))
24
31
 
25
32
  C_NS = "{http://schemas.openxmlformats.org/drawingml/2006/chart}"
@@ -22,6 +22,13 @@ from decimal import Decimal, InvalidOperation
22
22
  from pathlib import Path
23
23
  from xml.etree import ElementTree
24
24
 
25
+ # Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
26
+ for _stream in (sys.stdout, sys.stderr):
27
+ try:
28
+ _stream.reconfigure(encoding="utf-8", errors="replace")
29
+ except (AttributeError, ValueError):
30
+ pass
31
+
25
32
  sys.path.insert(0, str(Path(__file__).resolve().parent))
26
33
 
27
34
  C_NS = "{http://schemas.openxmlformats.org/drawingml/2006/chart}"
@@ -24,6 +24,13 @@ import sys
24
24
  from decimal import Decimal, InvalidOperation
25
25
  from pathlib import Path
26
26
 
27
+ # Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
28
+ for _stream in (sys.stdout, sys.stderr):
29
+ try:
30
+ _stream.reconfigure(encoding="utf-8", errors="replace")
31
+ except (AttributeError, ValueError):
32
+ pass
33
+
27
34
  sys.path.insert(0, str(Path(__file__).resolve().parent))
28
35
 
29
36
  TOTAL_PATTERN = re.compile(r"\btotal\b", re.IGNORECASE)
@@ -23,6 +23,13 @@ import sys
23
23
  from pathlib import Path
24
24
  from typing import Any, Iterable, Iterator
25
25
 
26
+ # Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
27
+ for _stream in (sys.stdout, sys.stderr):
28
+ try:
29
+ _stream.reconfigure(encoding="utf-8", errors="replace")
30
+ except (AttributeError, ValueError):
31
+ pass
32
+
26
33
  EMU_PER_INCH = 914400
27
34
 
28
35
  # Anything that renders about a full em wide and wraps mid-token.
@@ -48,6 +48,13 @@ from ooxml_helpers import ( # noqa: E402
48
48
  WIDE_CHAR,
49
49
  )
50
50
 
51
+ # Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
52
+ for _stream in (sys.stdout, sys.stderr):
53
+ try:
54
+ _stream.reconfigure(encoding="utf-8", errors="replace")
55
+ except (AttributeError, ValueError):
56
+ pass
57
+
51
58
  INK = (0.13, 0.15, 0.18)
52
59
  MUTED = (0.42, 0.45, 0.50)
53
60
  RULE = (0.80, 0.82, 0.85)
@@ -23,6 +23,13 @@ import sys
23
23
  import tempfile
24
24
  from pathlib import Path
25
25
 
26
+ # Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
27
+ for _stream in (sys.stdout, sys.stderr):
28
+ try:
29
+ _stream.reconfigure(encoding="utf-8", errors="replace")
30
+ except (AttributeError, ValueError):
31
+ pass
32
+
26
33
  sys.path.insert(0, str(Path(__file__).resolve().parent))
27
34
 
28
35
 
@@ -26,6 +26,13 @@ import json
26
26
  import sys
27
27
  from pathlib import Path
28
28
 
29
+ # Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
30
+ for _stream in (sys.stdout, sys.stderr):
31
+ try:
32
+ _stream.reconfigure(encoding="utf-8", errors="replace")
33
+ except (AttributeError, ValueError):
34
+ pass
35
+
29
36
 
30
37
  def _emit(payload: dict, code: int = 0) -> int:
31
38
  print(json.dumps(payload, ensure_ascii=False, indent=2))
@@ -30,6 +30,13 @@ sys.path.insert(0, str(Path(__file__).resolve().parent))
30
30
 
31
31
  from ooxml_helpers import box, is_picture, slide_size_in # noqa: E402
32
32
 
33
+ # Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
34
+ for _stream in (sys.stdout, sys.stderr):
35
+ try:
36
+ _stream.reconfigure(encoding="utf-8", errors="replace")
37
+ except (AttributeError, ValueError):
38
+ pass
39
+
33
40
  EMU_PER_INCH = 914400
34
41
  DEFAULT_TOLERANCE_EMU = 9144 # 0.01 in
35
42
 
@@ -20,6 +20,13 @@ sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "container_tools
20
20
 
21
21
  from ooxml_helpers import box, has_visible_text, is_picture, overlap_ratio, text_of # noqa: E402
22
22
 
23
+ # Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
24
+ for _stream in (sys.stdout, sys.stderr):
25
+ try:
26
+ _stream.reconfigure(encoding="utf-8", errors="replace")
27
+ except (AttributeError, ValueError):
28
+ pass
29
+
23
30
  PLACEHOLDER_PATTERNS = [
24
31
  re.compile(pattern, re.IGNORECASE)
25
32
  for pattern in (
@@ -23,6 +23,13 @@ sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "container_tools
23
23
 
24
24
  from ooxml_helpers import box, has_visible_text, is_chart, is_picture, is_table, placeholder_kind, slide_size_in, text_of # noqa: E402
25
25
 
26
+ # Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
27
+ for _stream in (sys.stdout, sys.stderr):
28
+ try:
29
+ _stream.reconfigure(encoding="utf-8", errors="replace")
30
+ except (AttributeError, ValueError):
31
+ pass
32
+
26
33
 
27
34
  def _theme(presentation) -> dict:
28
35
  fonts: set[str] = set()
@@ -15,6 +15,13 @@ import math
15
15
  import sys
16
16
  from pathlib import Path
17
17
 
18
+ # Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
19
+ for _stream in (sys.stdout, sys.stderr):
20
+ try:
21
+ _stream.reconfigure(encoding="utf-8", errors="replace")
22
+ except (AttributeError, ValueError):
23
+ pass
24
+
18
25
  sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "container_tools"))
19
26
 
20
27
 
@@ -23,6 +23,13 @@ sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "container_tools
23
23
 
24
24
  from ooxml_helpers import box, placeholder_kind, slide_size_in # noqa: E402
25
25
 
26
+ # Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
27
+ for _stream in (sys.stdout, sys.stderr):
28
+ try:
29
+ _stream.reconfigure(encoding="utf-8", errors="replace")
30
+ except (AttributeError, ValueError):
31
+ pass
32
+
26
33
  PLACEHOLDER_ALIASES = {
27
34
  "title": ("TITLE", "CENTER_TITLE"),
28
35
  "subtitle": ("SUBTITLE",),
@@ -26,6 +26,13 @@ import json
26
26
  import sys
27
27
  from pathlib import Path
28
28
 
29
+ # Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
30
+ for _stream in (sys.stdout, sys.stderr):
31
+ try:
32
+ _stream.reconfigure(encoding="utf-8", errors="replace")
33
+ except (AttributeError, ValueError):
34
+ pass
35
+
29
36
  sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "container_tools"))
30
37
 
31
38
  ALLOWED_ACTIONS = {"rewrite", "rewrite-and-reposition", "replace", "delete", "keep", "fill-placeholder"}
@@ -35,6 +35,13 @@ import json
35
35
  import sys
36
36
  from pathlib import Path
37
37
 
38
+ # Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
39
+ for _stream in (sys.stdout, sys.stderr):
40
+ try:
41
+ _stream.reconfigure(encoding="utf-8", errors="replace")
42
+ except (AttributeError, ValueError):
43
+ pass
44
+
38
45
  if hasattr(sys.stdout, "reconfigure"):
39
46
  sys.stdout.reconfigure(errors="replace")
40
47
 
@@ -23,6 +23,13 @@ import json
23
23
  import sys
24
24
  from pathlib import Path
25
25
 
26
+ # Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
27
+ for _stream in (sys.stdout, sys.stderr):
28
+ try:
29
+ _stream.reconfigure(encoding="utf-8", errors="replace")
30
+ except (AttributeError, ValueError):
31
+ pass
32
+
26
33
  if hasattr(sys.stdout, "reconfigure"):
27
34
  sys.stdout.reconfigure(errors="replace")
28
35
 
@@ -29,6 +29,13 @@ import json
29
29
  import sys
30
30
  from pathlib import Path
31
31
 
32
+ # Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
33
+ for _stream in (sys.stdout, sys.stderr):
34
+ try:
35
+ _stream.reconfigure(encoding="utf-8", errors="replace")
36
+ except (AttributeError, ValueError):
37
+ pass
38
+
32
39
  if hasattr(sys.stdout, "reconfigure"):
33
40
  sys.stdout.reconfigure(errors="replace")
34
41
 
@@ -21,6 +21,13 @@ import json
21
21
  import sys
22
22
  from pathlib import Path
23
23
 
24
+ # Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
25
+ for _stream in (sys.stdout, sys.stderr):
26
+ try:
27
+ _stream.reconfigure(encoding="utf-8", errors="replace")
28
+ except (AttributeError, ValueError):
29
+ pass
30
+
24
31
  if hasattr(sys.stdout, "reconfigure"):
25
32
  # Legacy console codepages (cp936, cp1252) must not abort a report.
26
33
  sys.stdout.reconfigure(errors="replace")
@@ -22,6 +22,13 @@ import sys
22
22
  import zipfile
23
23
  from pathlib import Path
24
24
 
25
+ # Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
26
+ for _stream in (sys.stdout, sys.stderr):
27
+ try:
28
+ _stream.reconfigure(encoding="utf-8", errors="replace")
29
+ except (AttributeError, ValueError):
30
+ pass
31
+
25
32
  if hasattr(sys.stdout, "reconfigure"):
26
33
  # Legacy console codepages (cp936, cp1252) must not abort a report.
27
34
  sys.stdout.reconfigure(errors="replace")
@@ -28,6 +28,13 @@ import subprocess
28
28
  import sys
29
29
  from pathlib import Path
30
30
 
31
+ # Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
32
+ for _stream in (sys.stdout, sys.stderr):
33
+ try:
34
+ _stream.reconfigure(encoding="utf-8", errors="replace")
35
+ except (AttributeError, ValueError):
36
+ pass
37
+
31
38
  if hasattr(sys.stdout, "reconfigure"):
32
39
  # Legacy console codepages (cp936, cp1252) must not abort a report.
33
40
  sys.stdout.reconfigure(errors="replace")
@@ -116,23 +123,28 @@ def _referenced_sheets(formula: str) -> list[str]:
116
123
  return found
117
124
 
118
125
 
119
- def _recalculate(python: str, cli: Path, source: Path, out: Path) -> tuple[bool, str | None]:
126
+ def _recalculate(python: str, cli: Path, source: Path, out: Path) -> tuple[bool, str | None, str | None]:
120
127
  command = [python, str(cli), "xlsx", "recalc", "--input", str(source), "--out", str(out)]
121
128
  try:
122
129
  completed = subprocess.run(command, capture_output=True, text=True, timeout=300)
123
130
  except FileNotFoundError as error:
124
- return False, f"cannot start the helper CLI: {error}"
131
+ return False, f"cannot start the helper CLI: {error}", None
125
132
  except subprocess.TimeoutExpired:
126
- return False, "recalculation timed out after 300 seconds"
133
+ return False, "recalculation timed out after 300 seconds", None
127
134
  if completed.returncode == 0 and out.is_file():
128
- return True, None
135
+ renderer = None
136
+ try:
137
+ renderer = json.loads(completed.stdout).get("renderer")
138
+ except (json.JSONDecodeError, AttributeError):
139
+ renderer = None
140
+ return True, None, renderer
129
141
  message = (completed.stderr or completed.stdout or "").strip()
130
142
  try:
131
143
  parsed = json.loads(message)
132
144
  message = str(parsed.get("error", message))
133
145
  except json.JSONDecodeError:
134
146
  pass
135
- return False, message or f"helper CLI exited with code {completed.returncode}"
147
+ return False, message or f"helper CLI exited with code {completed.returncode}", None
136
148
 
137
149
 
138
150
  def _ignored(entries: str | None) -> dict[str, set[str]]:
@@ -176,10 +188,10 @@ def main() -> int:
176
188
  if not cli.is_file():
177
189
  recalc_error = f"helper CLI not found: {cli}; pass --cli or use --no-recalc"
178
190
  else:
179
- recalculated, recalc_error = _recalculate(args.python, cli, source, out)
191
+ recalculated, recalc_error, renderer = _recalculate(args.python, cli, source, out)
180
192
  if recalculated:
181
193
  checked = out
182
- engine = "microsoft excel"
194
+ engine = renderer or "recalculation engine"
183
195
 
184
196
  try:
185
197
  cached = _cached_values(checked, cells) if cells else {}
@@ -26,6 +26,13 @@ import json
26
26
  import sys
27
27
  from pathlib import Path
28
28
 
29
+ # Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
30
+ for _stream in (sys.stdout, sys.stderr):
31
+ try:
32
+ _stream.reconfigure(encoding="utf-8", errors="replace")
33
+ except (AttributeError, ValueError):
34
+ pass
35
+
29
36
  if hasattr(sys.stdout, "reconfigure"):
30
37
  sys.stdout.reconfigure(errors="replace")
31
38
 
@@ -24,6 +24,13 @@ import json
24
24
  import sys
25
25
  from pathlib import Path
26
26
 
27
+ # Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
28
+ for _stream in (sys.stdout, sys.stderr):
29
+ try:
30
+ _stream.reconfigure(encoding="utf-8", errors="replace")
31
+ except (AttributeError, ValueError):
32
+ pass
33
+
27
34
  if hasattr(sys.stdout, "reconfigure"):
28
35
  sys.stdout.reconfigure(errors="replace")
29
36
 
@@ -30,6 +30,13 @@ import json
30
30
  import sys
31
31
  from pathlib import Path
32
32
 
33
+ # Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
34
+ for _stream in (sys.stdout, sys.stderr):
35
+ try:
36
+ _stream.reconfigure(encoding="utf-8", errors="replace")
37
+ except (AttributeError, ValueError):
38
+ pass
39
+
33
40
  if hasattr(sys.stdout, "reconfigure"):
34
41
  sys.stdout.reconfigure(errors="replace")
35
42
 
@@ -24,6 +24,13 @@ import subprocess
24
24
  import sys
25
25
  from pathlib import Path
26
26
 
27
+ # Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
28
+ for _stream in (sys.stdout, sys.stderr):
29
+ try:
30
+ _stream.reconfigure(encoding="utf-8", errors="replace")
31
+ except (AttributeError, ValueError):
32
+ pass
33
+
27
34
  DEFAULT_ROOT = Path(__file__).resolve().parents[3]
28
35
  RENDERER_HINT = (
29
36
  "docx/pptx/xlsx rendering needs LibreOffice, Microsoft Office, or WPS Office; "
package/dist/index.js CHANGED
@@ -324,20 +324,27 @@ function runPostinstall() {
324
324
  }
325
325
  let cache;
326
326
  let healing;
327
+ /** A cached `ok` is only worth reusing while its interpreter still exists on disk. */
328
+ function runtimeAlive(status) {
329
+ return status.state !== "ok" || status.python != null && isFile(status.python.path);
330
+ }
327
331
  /**
328
332
  * Resolve a runtime that is proven to work, self-healing via postinstall.mjs when it
329
333
  * is missing or incomplete. Never returns a bare interpreter without dependencies.
334
+ * Pass `force` for an explicit status check that must not trust the process cache.
330
335
  */
331
- function ensureRuntime(configFile = officePaths().configFile) {
332
- if (cache != null && (cache.status.state === "ok" || Date.now() - cache.at < FAILURE_RETRY_DELAY)) return Promise.resolve(cache.status);
336
+ function ensureRuntime(configFile = officePaths().configFile, force = false) {
337
+ if (!force && cache != null) {
338
+ if (cache.status.state === "ok" ? runtimeAlive(cache.status) : Date.now() - cache.at < FAILURE_RETRY_DELAY) return Promise.resolve(cache.status);
339
+ }
333
340
  healing ??= (async () => {
334
- const fresh = await inspectRuntime(configFile);
335
- if (fresh.state === "ok") {
341
+ const probed = await inspectRuntime(configFile);
342
+ if (probed.state === "ok") {
336
343
  cache = {
337
- status: fresh,
344
+ status: probed,
338
345
  at: Date.now()
339
346
  };
340
- return fresh;
347
+ return probed;
341
348
  }
342
349
  const healError = await runPostinstall();
343
350
  const status = {
@@ -519,7 +526,7 @@ function apply(ctx) {
519
526
  },
520
527
  timeoutMs: 6e5,
521
528
  async execute() {
522
- const runtime = await ensureRuntime(paths.configFile);
529
+ const runtime = await ensureRuntime(paths.configFile, true);
523
530
  const report = {
524
531
  plugin: {
525
532
  name,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-office-kit",
3
3
  "type": "module",
4
- "version": "0.3.0",
4
+ "version": "0.3.1",
5
5
  "description": "Office documents, PDF, presentations, and spreadsheets for DeepSeek Harness",
6
6
  "author": "Hairy",
7
7
  "license": "MIT",
@@ -21,7 +21,18 @@ import tempfile
21
21
  from pathlib import Path
22
22
  from typing import Any, Iterable
23
23
 
24
- VERSION = "0.1.0"
24
+ PACKAGE_ROOT = Path(__file__).resolve().parents[1]
25
+
26
+
27
+ def _package_version() -> str:
28
+ try:
29
+ data = json.loads((PACKAGE_ROOT / "package.json").read_text(encoding="utf-8"))
30
+ return str(data["version"])
31
+ except (OSError, ValueError, KeyError, TypeError):
32
+ return "0.0.0"
33
+
34
+
35
+ VERSION = _package_version()
25
36
 
26
37
  # Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
27
38
  for _stream in (sys.stdout, sys.stderr):
@@ -339,6 +350,16 @@ def _docx_add_equation(paragraph, latex: str) -> bool:
339
350
  return False
340
351
 
341
352
 
353
+ def _docx_mark_header_row(row) -> None:
354
+ """Repeat this row across pages and mark it as a header for assistive tech."""
355
+ from docx.oxml.ns import qn
356
+
357
+ properties = row._tr.get_or_add_trPr()
358
+ if properties.find(qn("w:tblHeader")) is None:
359
+ header = properties.makeelement(qn("w:tblHeader"), {})
360
+ properties.append(header)
361
+
362
+
342
363
  def _docx_add_block(document, block: dict[str, Any], counters: dict[str, int]) -> None:
343
364
  from docx.shared import Inches, Pt
344
365
 
@@ -391,6 +412,7 @@ def _docx_add_block(document, block: dict[str, Any], counters: dict[str, int]) -
391
412
  for paragraph in cells[index].paragraphs:
392
413
  for run in paragraph.runs:
393
414
  run.bold = True
415
+ _docx_mark_header_row(table.rows[0])
394
416
  for row in rows:
395
417
  cells = table.add_row().cells
396
418
  for index, value in enumerate(row):
@@ -459,6 +481,10 @@ def docx_create(spec: dict[str, Any], out: Path) -> dict[str, Any]:
459
481
  if spec.get("author") or spec.get("title"):
460
482
  document.core_properties.title = str(spec.get("title", ""))
461
483
  document.core_properties.author = str(spec.get("author", ""))
484
+ # A fresh python-docx document carries the library name in comments; a
485
+ # delivered file should not advertise the tool that wrote it.
486
+ document.core_properties.comments = str(spec.get("comments", ""))
487
+ document.core_properties.last_modified_by = str(spec.get("author", ""))
462
488
  if spec.get("header"):
463
489
  document.sections[0].header.paragraphs[0].text = str(spec["header"])
464
490
  if spec.get("footer"):
@@ -659,17 +685,21 @@ def pdf_create(spec: dict[str, Any], out: Path) -> dict[str, Any]:
659
685
  author=str(spec.get("author", "")),
660
686
  )
661
687
  counters = {key: 0 for key in ("headings", "paragraphs", "list_items", "tables", "images")}
688
+ following: list[dict[str, Any]] = []
689
+ for page in spec.get("pages", []):
690
+ if isinstance(page, dict):
691
+ following.extend(page.get("blocks", []))
692
+ if page.get("break", True):
693
+ following.append({"type": "page_break"})
694
+ following.extend(spec.get("blocks", []))
695
+ kinds = {str(block.get("type", "")).lower() for block in following if isinstance(block, dict)}
662
696
  blocks: list[dict[str, Any]] = []
663
- if spec.get("title"):
697
+ # `title` is metadata; only draw it when the blocks do not already carry one.
698
+ if spec.get("title") and "title" not in kinds:
664
699
  blocks.append({"type": "title", "text": spec["title"]})
665
700
  if spec.get("subtitle"):
666
701
  blocks.append({"type": "paragraph", "text": spec["subtitle"]})
667
- for page in spec.get("pages", []):
668
- if isinstance(page, dict):
669
- blocks.extend(page.get("blocks", []))
670
- if page.get("break", True):
671
- blocks.append({"type": "page_break"})
672
- blocks.extend(spec.get("blocks", []))
702
+ blocks.extend(following)
673
703
  document.build(_pdf_flowables(blocks, font, counters))
674
704
  return {"ok": True, "kind": "pdf", "command": "create", "output": str(out), "font": font, "counts": counters}
675
705
 
@@ -849,6 +879,10 @@ def pptx_create(spec: dict[str, Any], out: Path) -> dict[str, Any]:
849
879
  prs.slide_height = int(7.5 * 914400)
850
880
  prs.core_properties.title = str(spec.get("title", ""))
851
881
  prs.core_properties.author = str(spec.get("author", "DSH Office Suite"))
882
+ # python-pptx leaves its own name in the properties; a delivered deck should
883
+ # carry the author's, not the library's.
884
+ prs.core_properties.comments = str(spec.get("comments", ""))
885
+ prs.core_properties.last_modified_by = str(spec.get("author", "DSH Office Suite"))
852
886
  counters = {key: 0 for key in ("slides", "bullets", "tables", "charts", "images")}
853
887
  for slide_spec in spec.get("slides", []):
854
888
  if isinstance(slide_spec, dict):