dsh-office-kit 0.4.2 → 0.4.3

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 (113) hide show
  1. package/assets/skills/excel-live-control/SKILL.md +119 -152
  2. package/assets/skills/excel-live-control/domain_guidance/financial_models.md +42 -58
  3. package/assets/skills/excel-live-control/domain_guidance/healthcare.md +36 -42
  4. package/assets/skills/excel-live-control/domain_guidance/marketing_advertising.md +33 -34
  5. package/assets/skills/excel-live-control/domain_guidance/scientific_research.md +34 -36
  6. package/assets/skills/excel-live-control/references/office-automation.md +127 -153
  7. package/assets/skills/excel-live-control/references/read_only_qna.md +40 -45
  8. package/assets/skills/excel-live-control/style_guidelines.md +56 -64
  9. package/assets/skills/excel-live-control/workflows/create_workflows.md +40 -47
  10. package/assets/skills/excel-live-control/workflows/edit_workflows.md +37 -45
  11. package/assets/skills/office-documents/SKILL.md +24 -60
  12. package/assets/skills/office-documents/examples/end_to_end_smoke_test.md +5 -5
  13. package/assets/skills/office-documents/ooxml/comments.md +8 -8
  14. package/assets/skills/office-documents/ooxml/hyperlinks_and_fields.md +10 -10
  15. package/assets/skills/office-documents/ooxml/rels_and_content_types.md +7 -7
  16. package/assets/skills/office-documents/ooxml/tracked_changes.md +12 -12
  17. package/assets/skills/office-documents/references/docx-structure.md +11 -13
  18. package/assets/skills/office-documents/references/template-elicitation.md +11 -13
  19. package/assets/skills/office-documents/references/writing-quality.md +20 -20
  20. package/assets/skills/office-documents/tasks/accessibility_a11y.md +16 -22
  21. package/assets/skills/office-documents/tasks/captions_crossrefs.md +23 -26
  22. package/assets/skills/office-documents/tasks/clean_tracked_changes.md +17 -22
  23. package/assets/skills/office-documents/tasks/comments_manage.md +9 -11
  24. package/assets/skills/office-documents/tasks/compare_diff.md +10 -16
  25. package/assets/skills/office-documents/tasks/create_edit.md +14 -16
  26. package/assets/skills/office-documents/tasks/equations.md +17 -28
  27. package/assets/skills/office-documents/tasks/fields_update.md +14 -16
  28. package/assets/skills/office-documents/tasks/fixtures_edge_cases.md +19 -33
  29. package/assets/skills/office-documents/tasks/footnotes_endnotes.md +12 -14
  30. package/assets/skills/office-documents/tasks/forms_content_controls.md +16 -20
  31. package/assets/skills/office-documents/tasks/headings_numbering.md +17 -22
  32. package/assets/skills/office-documents/tasks/images_figures.md +15 -25
  33. package/assets/skills/office-documents/tasks/multi_doc_merge.md +13 -23
  34. package/assets/skills/office-documents/tasks/navigation_internal_links.md +18 -21
  35. package/assets/skills/office-documents/tasks/privacy_scrub_metadata.md +16 -22
  36. package/assets/skills/office-documents/tasks/protection_restrict_editing.md +19 -21
  37. package/assets/skills/office-documents/tasks/read_review.md +9 -20
  38. package/assets/skills/office-documents/tasks/redaction_anonymization.md +17 -18
  39. package/assets/skills/office-documents/tasks/sections_layout.md +16 -23
  40. package/assets/skills/office-documents/tasks/style_lint_normalize.md +18 -22
  41. package/assets/skills/office-documents/tasks/tables_spreadsheets.md +21 -31
  42. package/assets/skills/office-documents/tasks/templates_style_packs.md +17 -21
  43. package/assets/skills/office-documents/tasks/toc_workflow.md +10 -14
  44. package/assets/skills/office-documents/tasks/verify_render.md +11 -23
  45. package/assets/skills/office-documents/tasks/watermarks_background.md +11 -15
  46. package/assets/skills/office-documents/troubleshooting/libreoffice_headless.md +6 -6
  47. package/assets/skills/office-documents/troubleshooting/run_splitting.md +12 -12
  48. package/assets/skills/office-pdf/SKILL.md +36 -51
  49. package/assets/skills/office-pdf/references/form-fields.md +20 -20
  50. package/assets/skills/office-pdf/references/geometry.md +10 -10
  51. package/assets/skills/office-pdf/references/layout-quality.md +13 -13
  52. package/assets/skills/office-pdf/references/limits.md +12 -56
  53. package/assets/skills/office-pdf/tasks/annotations.md +5 -5
  54. package/assets/skills/office-pdf/tasks/create.md +6 -9
  55. package/assets/skills/office-pdf/tasks/edit-pages.md +14 -14
  56. package/assets/skills/office-pdf/tasks/forms.md +15 -15
  57. package/assets/skills/office-pdf/tasks/read.md +9 -9
  58. package/assets/skills/office-pdf/tasks/redact.md +18 -18
  59. package/assets/skills/office-pdf/tasks/stamp.md +10 -10
  60. package/assets/skills/office-presentations/SKILL.md +71 -91
  61. package/assets/skills/office-presentations/container_tools/render_slides.py +1 -1
  62. package/assets/skills/office-presentations/domain_guidance/finance.md +16 -16
  63. package/assets/skills/office-presentations/recipes/README.md +24 -35
  64. package/assets/skills/office-presentations/recipes/autolayout.md +12 -13
  65. package/assets/skills/office-presentations/recipes/connectors.md +11 -12
  66. package/assets/skills/office-presentations/recipes/images.md +11 -12
  67. package/assets/skills/office-presentations/recipes/master-and-layouts.md +18 -18
  68. package/assets/skills/office-presentations/recipes/native-charts.md +8 -8
  69. package/assets/skills/office-presentations/recipes/rich-text-runs.md +13 -13
  70. package/assets/skills/office-presentations/recipes/speaker-notes.md +7 -7
  71. package/assets/skills/office-presentations/recipes/tables.md +14 -14
  72. package/assets/skills/office-presentations/recipes/textbox-at-coordinate.md +9 -9
  73. package/assets/skills/office-presentations/recipes/theme-tokens.md +7 -8
  74. package/assets/skills/office-presentations/references/authoring-workflow.md +38 -43
  75. package/assets/skills/office-presentations/references/finalization.md +10 -12
  76. package/assets/skills/office-presentations/references/native-bullets.md +14 -18
  77. package/assets/skills/office-presentations/references/native-evidence.md +18 -32
  78. package/assets/skills/office-presentations/references/style-guidelines.md +27 -32
  79. package/assets/skills/office-presentations/references/templates.md +31 -60
  80. package/assets/skills/office-presentations/routing/google_slides.md +4 -4
  81. package/assets/skills/office-presentations/tasks/apply-annotations.md +17 -17
  82. package/assets/skills/office-presentations/tasks/charts-tables-notes.md +20 -23
  83. package/assets/skills/office-presentations/tasks/create-deck.md +19 -19
  84. package/assets/skills/office-presentations/tasks/edit-deck.md +14 -16
  85. package/assets/skills/office-presentations/tasks/inspect-and-verify.md +31 -34
  86. package/assets/skills/office-presentations/tasks/template-following.md +23 -28
  87. package/assets/skills/office-spreadsheets/SKILL.md +32 -39
  88. package/assets/skills/office-spreadsheets/recipes/charts.md +8 -26
  89. package/assets/skills/office-spreadsheets/recipes/conditional-formats.md +9 -26
  90. package/assets/skills/office-spreadsheets/recipes/data-tables.md +7 -23
  91. package/assets/skills/office-spreadsheets/recipes/named-ranges.md +8 -21
  92. package/assets/skills/office-spreadsheets/recipes/number-formats.md +7 -23
  93. package/assets/skills/office-spreadsheets/recipes/sparklines.md +7 -20
  94. package/assets/skills/office-spreadsheets/references/charts.md +15 -34
  95. package/assets/skills/office-spreadsheets/references/domain/financial-models.md +10 -25
  96. package/assets/skills/office-spreadsheets/references/domain/healthcare.md +8 -19
  97. package/assets/skills/office-spreadsheets/references/domain/marketing.md +8 -22
  98. package/assets/skills/office-spreadsheets/references/domain/research.md +8 -19
  99. package/assets/skills/office-spreadsheets/references/formula-patterns.md +20 -50
  100. package/assets/skills/office-spreadsheets/references/style-guidelines.md +19 -44
  101. package/assets/skills/office-spreadsheets/references/workbook-structure.md +12 -35
  102. package/assets/skills/office-spreadsheets/routing/google-sheets.md +11 -26
  103. package/assets/skills/office-spreadsheets/tasks/analyze-workbook.md +10 -25
  104. package/assets/skills/office-spreadsheets/tasks/annotations.md +12 -30
  105. package/assets/skills/office-spreadsheets/tasks/create-workbook.md +5 -5
  106. package/assets/skills/office-spreadsheets/tasks/diff-workbooks.md +2 -2
  107. package/assets/skills/office-spreadsheets/tasks/edit-workbook.md +5 -5
  108. package/assets/skills/office-spreadsheets/tasks/inspect-workbook.md +9 -9
  109. package/assets/skills/office-spreadsheets/tasks/recalc-and-check.md +10 -30
  110. package/assets/skills/shared/annotation-recipes.md +98 -120
  111. package/assets/skills/shared/common-workflow.md +70 -80
  112. package/dist/index.js +1 -1
  113. package/package.json +1 -1
@@ -1,25 +1,15 @@
1
1
  # Recalculate and gate consistency
2
2
 
3
- A workbook written by openpyxl or by a script has formula text and no cached
4
- results. Open it in a viewer that does not calculate and every formula cell shows
5
- blank. `xlsx recalc` runs the real engine to fill the cache; the gate then proves
6
- every formula cell got a value and none of them is an error.
3
+ A workbook written by openpyxl or by a script has formula text and no cached results. Open it in a viewer that does not calculate and every formula cell shows blank. `xlsx recalc` runs the real engine to fill the cache; the gate then proves every formula cell got a value and none is an error.
7
4
 
8
5
  ```bash
9
6
  "<python>" "<cli>" xlsx recalc --input "<book.xlsx>" --out "<book-recalculated.xlsx>"
10
7
  "<python>" "<skill>/container_tools/recalc_gate.py" --input "<book-recalculated.xlsx>"
11
8
  ```
12
9
 
13
- - `recalc` needs the DSH LibreOffice kit (the same renderer used for images);
14
- it runs on Windows, macOS and Linux. Without the kit the verb fails with a
15
- plain message and changes nothing.
16
- - `recalc_gate.py` runs `recalc` for you when given `--cli`: it takes
17
- `--input`, writes `<stem>-recalculated.xlsx` unless `--out` is given, then
18
- scans every formula cell.
19
- - It exits `0` clean, `1` with failures, `2` on a usage or file error, and `3`
20
- when recalculation was requested but no engine exists. Exit `3` with
21
- `verdict: unverified` is an honest result — report it as "structure verified,
22
- numbers not recalculated", never as "numbers verified".
10
+ - `recalc` needs the DSH LibreOffice kit (the same renderer used for images) and runs on Windows, macOS and Linux. Without the kit the verb fails with a plain message and changes nothing.
11
+ - `recalc_gate.py --cli` runs `recalc` for you: it takes `--input`, writes `<stem>-recalculated.xlsx` unless `--out` is given, then scans every formula cell.
12
+ - It exits `0` clean, `1` with failures, `2` on a usage or file error, and `3` when recalculation was requested but no engine exists. Exit `3` with `verdict: unverified` is an honest result — report it as "structure verified, numbers not recalculated", never as "numbers verified".
23
13
 
24
14
  ## What the gate flags
25
15
 
@@ -30,28 +20,18 @@ every formula cell got a value and none of them is an error.
30
20
  | `dangling` | Formula references a sheet that does not exist | Renaming a sheet without updating formulas does this; restore the name or repoint the reference |
31
21
  | `ignored` | Cells you passed with `--ignore` | Pre-existing errors the user asked you to leave alone |
32
22
 
33
- `--strict` treats missing values as failures instead of notes, which is what you
34
- want after a successful recalc. `--ignore 'Rates!B7,B9'` keeps a known,
35
- user-accepted error out of the verdict without hiding the rest.
23
+ `--strict` treats missing values as failures instead of notes, which is what you want after a successful recalc. `--ignore 'Rates!B7,B9'` keeps a known, user-accepted error out of the verdict without hiding the rest.
36
24
 
37
25
  ## Consistency checks the gate cannot do
38
26
 
39
27
  Recalculation proves formulas evaluate, not that they are right. Check by hand:
40
28
 
41
- - Totals equal the sum of the rows above them, and detail rows add to the
42
- summary — not just that both computed something.
29
+ - Totals equal the sum of the rows above them, and detail rows add to the summary — not just that both computed something.
43
30
  - Percentages are stored as fractions and formatted, not typed as `6.4`.
44
- - A case selector drives every tab that claims to follow the case. Two tabs
45
- showing different cases is the most expensive defect in a model.
46
- - Hard-coded overrides are visible (a different fill or a comment) and the
47
- formula path still exists. If an override replaced a formula, the workbook can
48
- no longer update.
49
- - Every input the user expects to change is in a cell they can find, not inside a
50
- formula constant.
31
+ - A case selector drives every tab that claims to follow the case. Two tabs showing different cases is the most expensive defect in a model.
32
+ - Hard-coded overrides are visible (a different fill or a comment) and the formula path still exists. If an override replaced a formula, the workbook can no longer update.
33
+ - Every input the user expects to change is in a cell they can find, not inside a formula constant.
51
34
 
52
35
  ## When there is no engine
53
36
 
54
- Report exactly what happened: the file was created and structurally verified, the
55
- numbers are formulas, and they will populate the first time the user opens the
56
- file in Excel, LibreOffice, or Sheets. Offer to render so the user sees the layout
57
- and labels. Do not invent values to make the preview look complete.
37
+ Report exactly what happened: the file was created and structurally verified, the numbers are formulas, and they will populate the first time the user opens the file in Excel, LibreOffice, or Sheets. Offer to render so the user sees the layout and labels. Do not invent values to make the preview look complete.
@@ -1,9 +1,9 @@
1
1
  # Annotation recipes
2
2
 
3
- The user may reply to a rendered preview with an `office-annotations` block: a
3
+ The user may reply to a rendered preview with an `office-annotations` payload: a
4
4
  JSON array wrapped in `<office-annotations>` … `</office-annotations>` tags.
5
- Each format sends a slightly different item; the shapes below are what the
6
- client actually emits.
5
+ Each format sends a slightly different item; these are the shapes the client
6
+ emits.
7
7
 
8
8
  | Format | Item keys | `page` means |
9
9
  | --- | --- | --- |
@@ -23,50 +23,53 @@ client actually emits.
23
23
  Rules that apply everywhere:
24
24
 
25
25
  1. Render the *current* file first and look at the same page the user annotated.
26
- Annotation geometry is only meaningful against a render you can reproduce.
26
+ Geometry is only meaningful against a render you can reproduce.
27
27
  2. Locate the concrete object, then apply the smallest edit that satisfies
28
- `requirement`. Do not restyle the document, do not rewrite neighbours.
29
- 3. Re-render and look at the edited page again. The item is done when the region
30
- shows the requested change and the layout still holds.
28
+ `requirement`. Do not restyle the document or rewrite neighbours.
29
+ 3. Re-render and look at the edited page. The item is done when the region shows
30
+ the requested change and the layout still holds.
31
31
  4. If the payload cannot be tied to an object (empty region, no geometry, no
32
32
  matching text), say what you could not locate. Never guess and never move the
33
33
  region to make it fit.
34
34
 
35
- Worked examples below use `report.docx`, `report.pdf`, `deck.pptx` and
36
- `book.xlsx` and the `python`/`cli` values from the `## Office runtime` block.
35
+ Worked examples use `report.docx`, `report.pdf`, `deck.pptx`, and `book.xlsx`,
36
+ plus the `python`/`cli`/`skill` values from the `## Office runtime` block.
37
37
 
38
- ## docx — rendered-PDF page plus text matching
39
-
40
- `page` is the page of the **rendered PDF preview**, not a Word page number.
41
-
42
- ```bash
43
- "<python>" "<cli>" docx render --input "report.docx" --out-dir "render"
44
- ```
45
-
46
- This writes `render/page-1.png` and keeps `render/report.pdf`. In the example
47
- the user circled a sentence on preview page 1 and asked to round it.
48
-
49
- Locate it from the text, not from the pixels:
38
+ ## Turn a region into page text
50
39
 
51
- - `"<python>" "<cli>" docx read --input "report.docx"` lists every paragraph with
52
- its style. Find the paragraph containing `text`.
53
- - Use `region` only to break ties between two similar paragraphs, or to confirm
54
- the preview page number. If they disagree, trust `text` and say so.
55
-
56
- Extract the words under the region when you need confirmation. Save this as
57
- `locate.py` and run it with the runtime python:
40
+ Save this as `region_text.py`; it prints the page rect and the words under the
41
+ region. Run it with the runtime python.
58
42
 
59
43
  ```python
60
44
  import sys, pymupdf
61
45
  pdf, page_no, l, t, w, h = sys.argv[1], int(sys.argv[2]), *map(float, sys.argv[3:7])
62
46
  page = pymupdf.open(pdf)[page_no - 1]
63
47
  r = page.rect
64
- clip = pymupdf.Rect(r.x0 + l * r.width, r.y0 + t * r.height,
48
+ rect = pymupdf.Rect(r.x0 + l * r.width, r.y0 + t * r.height,
65
49
  r.x0 + (l + w) * r.width, r.y0 + (t + h) * r.height)
66
- print(" ".join(word[4] for word in page.get_text("words", clip=clip)))
50
+ print(rect, " ".join(word[4] for word in page.get_text("words", clip=rect)))
67
51
  ```
68
52
 
69
- Apply the edit and write `<original>-edited.docx`:
53
+ ## docx — rendered-PDF page plus text matching
54
+
55
+ `page` is the page of the **rendered PDF preview**, not a Word page number.
56
+
57
+ ```bash
58
+ "<python>" "<cli>" docx render --input "report.docx" --out-dir "render" --pdf
59
+ ```
60
+
61
+ Writes `render/page-1.png` and `render/report.pdf`.
62
+
63
+ Locate it from the text, not the pixels:
64
+
65
+ - `docx read --input "report.docx"` lists paragraphs with styles. Find the one
66
+ containing `text`.
67
+ - Use `region` only to break ties or confirm the preview page; if they disagree,
68
+ trust `text` and say so.
69
+ - `region_text.py` on `render/report.pdf` confirms the words under the region.
70
+
71
+ Apply the edit and write `<original>-edited.docx` with the pack's replacement
72
+ tool:
70
73
 
71
74
  ```json
72
75
  {
@@ -78,63 +81,55 @@ Apply the edit and write `<original>-edited.docx`:
78
81
  ```
79
82
 
80
83
  ```bash
81
- "<python>" "<cli>" docx edit --input "report.docx" --spec "edit.json" --out "report-edited.docx"
84
+ "<python>" "<skill>/scripts/replace_text.py" --input "report.docx" --spec "edit.json" --out "report-edited.docx" --require-all
82
85
  ```
83
86
 
84
- The report returns `replacements`. `0` means `find` did not match verbatim —
85
- re-read the paragraph text instead of guessing. Then re-render
86
- `report-edited.docx` and `read_image` page 1. Note that one longer sentence can
87
- reflow the rest of the page: check the paragraphs below the edit, not just the
88
- edited line.
87
+ The report returns `replacements` (total), per-pair counts in `pairs`, and
88
+ `misses`; `--require-all` exits 1 when any `find` did not match. Zero means the
89
+ text split across runs or differs verbatim — re-read the paragraph instead of
90
+ guessing. Re-render `report-edited.docx` and `read_image` page 1. A longer
91
+ sentence can reflow the page: check the paragraphs below the edit too.
89
92
 
90
93
  ## pdf — convert the region and work on the converted rect
91
94
 
92
95
  `page` is a 1-based page of the PDF itself. `region × page.rect` is the real
93
- place in the file, so convert once and use the device rect directly.
94
-
95
- ```python
96
- import sys, pymupdf
97
- pdf, page_no, l, t, w, h = sys.argv[1], int(sys.argv[2]), *map(float, sys.argv[3:7])
98
- page = pymupdf.open(pdf)[page_no - 1]
99
- r = page.rect
100
- rect = pymupdf.Rect(r.x0 + l * r.width, r.y0 + t * r.height,
101
- r.x0 + (l + w) * r.width, r.y0 + (t + h) * r.height)
102
- print(rect, " ".join(word[4] for word in page.get_text("words", clip=rect)))
103
- ```
96
+ place in the file, so convert once and use the device rect directly. Run
97
+ `region_text.py` on `report.pdf`.
104
98
 
105
99
  Example: page 1, region over the title, `text` "Field report", requirement
106
- "make the title say Field report (revised), keep the author". The converted rect
107
- confirms the words under the mark are `Field report`. `imageName`, when present,
108
- is a screenshot of that region — open it with `read_image` if the text layer is
109
- ambiguous or the page is scanned.
100
+ "make the title say Field report (revised), keep the author". If `imageName` is
101
+ present, `read_image` it when the text layer is ambiguous or the page is
102
+ scanned.
110
103
 
111
- Choose the edit that the helper actually supports:
104
+ Choose the edit the pack scripts support:
112
105
 
113
- - Metadata, rotation, page order, merge, page removal → `pdf edit`:
106
+ - Metadata → `metadata.py`. Repeat `--set`; unset fields keep their old values.
114
107
 
115
- ```json
116
- { "metadata": { "title": "Field report (revised)" } }
108
+ ```bash
109
+ "<python>" "<skill>/scripts/metadata.py" --input "report.pdf" --set "title=Field report (revised)" --out "report-edited.pdf"
117
110
  ```
118
111
 
112
+ - Rotation, page order, merge, page removal → `page_ops.py`. Indices are
113
+ zero-based.
114
+
119
115
  ```bash
120
- "<python>" "<cli>" pdf edit --input "report.pdf" --spec "edit.json" --out "report-edited.pdf"
116
+ "<python>" "<skill>/scripts/page_ops.py" --input "report.pdf" --out "report-edited.pdf" --merge "cover.pdf" --order "2,0,1"
121
117
  ```
122
118
 
123
- - Text or graphics **on** the page → the helper has no in-place content edit.
124
- Rebuild the page from a `pdf create` spec using the text from `pdf read`, then
125
- say plainly that the PDF was rebuilt rather than patched. Do not describe a
126
- rebuild as a surgical edit.
119
+ - Text or graphics **on** the page → no in-place edit. Rebuild from a
120
+ `pdf create` spec using `pdf read` text, and say the PDF was rebuilt, not
121
+ patched. Never call a rebuild a surgical edit.
127
122
 
128
- Verify with `<cli> pdf render --input "report-edited.pdf" --out-dir "render"` and
123
+ Verify with `pdf render --input "report-edited.pdf" --out-dir "render"` and
129
124
  `read_image render/page-1.png`. `pdf render` uses PyMuPDF and works without the
130
125
  LibreOffice kit, so a PDF annotation is never blocked by a missing renderer.
131
126
 
132
127
  ## pptx — select the shapes that overlap the region
133
128
 
134
- `page` is the rendered PDF page, and the deck renders one slide per page, so
135
- page N is slide N. `pptx read` returns slide text but **no shape geometry**, so
136
- compute overlap yourself with python-pptx. Shape boxes and the payload region are
137
- both fractions of the slide, so the comparison is direct.
129
+ `page` is the rendered PDF page; one slide per page, so page N is slide N.
130
+ `pptx read` returns slide text but **no shape geometry**, so compute overlap
131
+ yourself with python-pptx. Shape boxes and the payload region are both
132
+ fractions of the slide, so the comparison is direct.
138
133
 
139
134
  ```python
140
135
  import sys
@@ -149,42 +144,37 @@ for shape in prs.slides[slide_no - 1].shapes:
149
144
  print(shape.shape_id, shape.text_frame.text.replace("\n", " | "))
150
145
  ```
151
146
 
152
- Example: page 2 (slide 2), region over the second bullet, `text` "Renewal 92%",
153
- requirement "make it 94%". Two shapes overlap the region — the title placeholder
154
- and the body — and `text` picks the body.
147
+ Example: slide 2, region over the second bullet, `text` "Renewal 92%". Two
148
+ shapes overlap; `text` picks the body.
155
149
 
156
- Apply with the run-level replacement:
150
+ Apply with the pack's run-level replacement; `slide` scopes a rule:
157
151
 
158
152
  ```json
159
- { "replace": [{ "find": "Renewal 92%", "replace": "Renewal 94%" }] }
153
+ { "replacements": [{ "find": "Renewal 92%", "replace": "Renewal 94%", "slide": 2 }] }
160
154
  ```
161
155
 
162
156
  ```bash
163
- "<python>" "<cli>" pptx edit --input "deck.pptx" --spec "edit.json" --out "deck-edited.pptx"
157
+ "<python>" "<skill>/container_tools/replace_text.py" --input "deck.pptx" --spec "edit.json" --out "deck-edited.pptx"
164
158
  ```
165
159
 
166
- This replacement is not scoped to one slide. Check the returned count, and if
167
- the same string appeared on other slides, fix those with per-slide `slides[]`
168
- entries in the same spec. Re-render and `read_image` the page PNG for slide 2.
169
- If type overflows its shape after the change, shrink the run or shorten the text
170
- rather than leaving it clipped.
160
+ Without `slide`, a rule hits every slide: check `total` and `slides`, then scope
161
+ any unintended match. `unmatched` lists patterns that found nothing. Re-render
162
+ and `read_image` the page PNG for slide 2. If type overflows its shape, shrink
163
+ the run or shorten the text rather than leaving it clipped.
171
164
 
172
165
  ## xlsx — no cell reference, so resolve it yourself and say so
173
166
 
174
167
  xlsx items are always `unmapped: true` with `text: ""`: you get a region, a
175
168
  screenshot in `imageName`, and a requirement — no page, no sheet, no A1 range.
176
- The sheet and cells must be resolved from evidence, never invented.
177
-
178
- 1. **Look at the screenshot.** `read_image` the attachment named `imageName`.
179
- Note the visible column headers, row labels and values inside the marked
180
- region. If there is no screenshot, you only have `region` and
181
- `requirement` — say so instead of guessing.
182
- 2. **Dump the workbook.** `"<python>" "<cli>" xlsx read --input "book.xlsx"
183
- --json-out "book.json"`. Each sheet gives `name`, `dimensions`, `rows` (arrays
184
- without coordinates) and `formulas`.
185
- 3. **Resolve the cells.** Match the visible values and headers from step 1
186
- against the dumped rows, then derive the A1 address from the row/column
187
- index:
169
+ Resolve the sheet and cells from evidence, never invent them.
170
+
171
+ 1. **Look at the screenshot.** `read_image` the `imageName` attachment; note
172
+ headers, row labels, and values in the region. With no screenshot you have
173
+ only `region` and `requirement` — say so.
174
+ 2. **Dump the workbook.** `xlsx read --input "book.xlsx" --json-out "book.json"`.
175
+ Sheets give `name`, `dimensions`, `rows` (no coordinates), and `formulas`.
176
+ 3. **Resolve the cells.** Match the visible values and headers against the rows,
177
+ then derive A1 from the row/column index:
188
178
 
189
179
  ```python
190
180
  import json, sys
@@ -198,39 +188,27 @@ The sheet and cells must be resolved from evidence, never invented.
198
188
  print(sheet["name"], f"{get_column_letter(c)}{r}", value)
199
189
  ```
200
190
 
201
- Run it with the runtime python. If several sheets have the same label, or the
202
- screenshot does not pin the column, ask which one before editing.
203
-
204
- 4. **State the resolution before editing.** Tell the user which sheet and A1
205
- range you resolved, e.g. "Revenue is `Q4!B2:C2` (10, 12); formatting those
206
- cells as currency." Only edit after that, so a wrong guess is visible before
207
- it touches the file.
208
-
209
- 5. **Apply and verify.** Example: screenshot shows `Revenue 10 12` and the
210
- requirement is "show these as currency with 2 decimals"; `xlsx read` finds
211
- `Revenue` in `Q4!A2` with `10` and `12` beside it:
212
-
213
- ```json
214
- {
215
- "operations": [
216
- { "action": "style", "sheet": "Q4", "cells": { "B2:C2": { "number_format": "#,##0.00" } } }
217
- ]
218
- }
219
- ```
191
+ If several sheets share the label, or the screenshot does not pin the
192
+ column, ask which one before editing.
193
+ 4. **State the resolution before editing.** Say the sheet and A1 range, e.g.
194
+ "Revenue is `Q4!B2:C2` (10, 12); formatting those cells as currency." A wrong
195
+ guess is then visible before it touches the file.
196
+ 5. **Apply and verify.** Screenshot shows `Revenue 10 12`; requirement is
197
+ "currency with 2 decimals"; `xlsx read` finds `Revenue` in `Q4!A2` with `10`
198
+ and `12` beside it:
220
199
 
221
200
  ```bash
222
- "<python>" "<cli>" xlsx edit --input "book.xlsx" --spec "edit.json" --out "book-edited.xlsx"
201
+ "<python>" "<skill>/container_tools/style_cells.py" --input "book.xlsx" --out "book-edited.xlsx" \
202
+ --sheet "Q4" --range "B2:C2" --style '{"number_format":"#,##0.00"}'
223
203
  ```
224
204
 
225
- A `style` operation applies to the whole A1:B2 range in one go. Use
226
- `set_cells` to write values, `append_rows` to add rows, `add_sheet` /
227
- `delete_sheet` / `rename_sheet` for structure. Then read the edited workbook
228
- again to confirm the values.
205
+ One `--range` styles the whole range. `set_cells.py` sets values/formulas,
206
+ `append_rows.py` adds rows, `sheet_ops.py` edits sheet structure. Read the
207
+ workbook back to confirm values.
229
208
 
230
- Forbidden, because both produce plausible-looking edits in the wrong place:
209
+ Forbidden — both put a plausible edit in the wrong place:
231
210
 
232
- - converting `region` pixels into a cell address — the spreadsheet preview has
233
- its own zoom and scroll;
211
+ - converting `region` pixels to a cell address (the preview zooms and scrolls);
234
212
  - naming a sheet the screenshot does not show or `xlsx read` cannot match.
235
213
 
236
214
  Visual confirmation needs `xlsx render`, which requires the DSH LibreOffice kit;
@@ -242,10 +220,10 @@ platform); do not present openpyxl's empty cache as a computed value.
242
220
 
243
221
  Ask one short question, listing what you did find, when:
244
222
 
245
- - the region covers text that exists on several pages and `text` is empty;
223
+ - the region covers text on several pages and `text` is empty;
246
224
  - two shapes on a slide both match `text`;
247
225
  - the xlsx screenshot does not identify the sheet, or the visible values match
248
226
  more than one row or column;
249
- - the requirement needs an operation the helper does not support (page-content
250
- editing in PDF, animation, comments) — offer the rebuild path instead of a
251
- silent partial change.
227
+ - the requirement needs an operation the pack scripts do not support
228
+ (page-content editing in PDF, animation, comments) — offer the rebuild path
229
+ instead of a silent partial change.
@@ -1,139 +1,129 @@
1
1
  # Shared workflow
2
2
 
3
- Every format pack follows this loop. The helper CLI is an accelerator for common
4
- operations, not the boundary of what is possible: it shortens the common path,
5
- and the skill teaches the rest. The format router adds the spec keys and the
6
- commands; the order, the checks and the failure handling are the same.
3
+ Every format pack follows this loop. The helper CLI is an accelerator, not the
4
+ boundary: the format router adds spec keys and commands; the order, checks, and
5
+ failure handling below are the same.
7
6
 
8
7
  ## 0. Read the runtime block first
9
8
 
10
- Call the `office_status` tool, then read the `## Office runtime` section that is
11
- appended to the end of the active `SKILL.md`. It is a fenced JSON block:
9
+ Call `office_status`, then read the `## Office runtime` section appended to the
10
+ active `SKILL.md`. It is a fenced JSON block:
12
11
 
13
12
  ```json
14
13
  { "python": "...\\python.exe", "cli": "...\\python\\office_cli.py", "skill": "...\\assets\\skills\\office-documents", "shared": "...\\assets\\skills\\shared", "version": "3.13.12" }
15
14
  ```
16
15
 
17
16
  - `python` and `cli` are verified absolute paths. Use them verbatim and quote
18
- them in commands. Never call a bare `python`, `py`, or `pdftoppm`.
19
- - `skill` is the directory of the format pack; its scripts live under
20
- `<skill>/scripts/`.
21
- - `shared` is this directory (workflow and annotation recipes).
22
- - If the block is missing entirely and the skill body instead says *No working
23
- Python runtime is available*, stop: do not run any helper command and do not
24
- fabricate a document.
17
+ them. Never call a bare `python`, `py`, or `pdftoppm`.
18
+ - `skill` is the format pack; its scripts live under `<skill>/scripts/`,
19
+ `container_tools/` (pptx, xlsx), or the pack root.
20
+ - `shared` is this directory (cross-format workflow, recipes, shared scripts).
21
+ - A missing block plus *No working Python runtime is available* means stop: run
22
+ no helper command and fabricate no document.
25
23
 
26
- `office_status` returns the same facts as JSON with `runtime.state`, the package
27
- list and `renderers`. Use it when a command fails and you need to know why.
24
+ `office_status` reports `runtime.state`, `python`, `cli`, `packages`, and the
25
+ kit `renderers` facts (`available`, `backend`, `remedy`). Use it when a command
26
+ fails. `<cli> status` additionally reports `can_render` per format.
28
27
 
29
28
  ## 1. Decide: create or edit
30
29
 
31
- - No existing file → create. Write the output into the user's workspace, not a
32
- temp directory, unless the user names a location.
33
- - Existing file to change → edit. **Never write back over the original.** The
34
- output is `<original>-edited.<ext>` next to the original (or the name the user
35
- asked for): `report.docx` → `report-edited.docx`. State both paths in the
36
- final summary.
30
+ - No existing file → create, in the user's workspace unless they name a location.
31
+ - Existing file → edit. **Never write over the original.** Write
32
+ `<original>-edited.<ext>` beside it (or the name the user asked for):
33
+ `report.docx` → `report-edited.docx`.
37
34
  - "Improve this document" means read it first (`read` verb) and edit the real
38
- content; do not rebuild it from scratch and drop its formatting.
35
+ content; do not rebuild it and drop its formatting.
36
+ - Never delete the original or "clean up" a temp copy over it. State both paths
37
+ in the final summary, original first.
39
38
 
40
- ## 2. Understand the file, choose the route, then change it
39
+ ## 2. Read, choose a route, change
41
40
 
42
- Read or inspect the file before changing it (`read` verb first), then pick the
43
- cheapest route that actually does the job:
41
+ Read the file first (`read` verb), then take the cheapest route that works:
44
42
 
45
- 1. **CLI verb** — the common case. The helper CLI is spec-driven and prints
46
- exactly one JSON object on stdout. Failures print
47
- `{ "ok": false, "error": "..." }` on stderr and exit non-zero.
43
+ 1. **CLI verb** — the common case. Spec-driven; prints one JSON object on stdout.
44
+ Failures print `{ "ok": false, "error": "..." }` on stderr and exit non-zero.
48
45
 
49
46
  ```bash
50
47
  "<python>" "<cli>" docx|pptx|xlsx|pdf create --spec "<spec.json>" --out "<file>"
51
48
  "<python>" "<cli>" docx|pptx|xlsx|pdf read --input "<file>"
52
- "<python>" "<cli>" docx|pptx|xlsx|pdf edit --input "<file>" --spec "<spec.json>" --out "<file-edited>"
53
- "<python>" "<cli>" docx|pptx|xlsx|pdf render --input "<file>" --out-dir "<dir>"
49
+ "<python>" "<cli>" docx|pptx|xlsx|pdf render --input "<file>" --out-dir "<render-dir>"
54
50
  "<python>" "<cli>" xlsx recalc --input "<file>" --out "<file>"
55
51
  ```
56
52
 
53
+ There is no `edit` verb: mutation belongs to a pack script or a snippet.
57
54
  Write the spec as a JSON file in the workspace; quote absolute paths.
58
- `xlsx read` accepts `--data-only`; every command accepts `--json-out <path>`
59
- when you want the report on disk instead of stdout.
55
+ `xlsx read` accepts `--data-only`; `create` (except `pdf`), `read`, `render`,
56
+ and `xlsx recalc` accept `--json-out <path>`.
60
57
 
61
58
  2. **Pack script** — a specialised job the CLI has no verb for. Use the script
62
- listed in the format router (`<skill>/scripts/`, or `container_tools/` for
63
- pptx/xlsx). Scripts are argparse-based, support `--help`, and take absolute
64
- paths; run them with the same `"<python>"`.
59
+ named by the format router (`<skill>/scripts/`, `container_tools/` for
60
+ pptx/xlsx, or the pack root). Scripts are argparse-based, support `--help`,
61
+ take absolute paths, and run with the same `"<python>"`.
65
62
 
66
- 3. **Library snippet** — the long tail. When neither the CLI nor a pack script
67
- covers the change, write a short runnable script in the workspace using the
68
- installed libraries (`python-docx`, `openpyxl`, `python-pptx`, `pypdf`,
69
- `PyMuPDF`, `reportlab`, `pdfplumber`), run it with `"<python>"`, then verify
70
- the file through the same render step. Keep it small and keep the user's
71
- original untouched.
63
+ 3. **Library snippet** — the long tail. When neither covers the change, write a
64
+ small runnable script in the workspace with the installed libraries
65
+ (`python-docx`, `openpyxl`, `python-pptx`, `pypdf`, `PyMuPDF`, `reportlab`,
66
+ `pdfplumber`), run it with `"<python>"`, then verify through the render step.
67
+ Keep it small; keep the original untouched.
72
68
 
73
- Never answer "the CLI does not support this, so it cannot be done". A missing
74
- verb is a missing shortcut, not a missing capability: give the library-level
75
- route, run it, and verify the result like any other change.
69
+ Never say "the CLI does not support this, so it cannot be done". A missing verb
70
+ is a missing shortcut, not a missing capability: use the script or library
71
+ route, run it, and verify the result.
76
72
 
77
- `check_runtime.py` in this directory validates the interpreter: it imports every
78
- package from `python/requirements.txt` and runs `<cli> status`. A non-zero exit
79
- means the environment is broken, not the spec.
73
+ `check_runtime.py` validates the interpreter: it imports every package in
74
+ `python/requirements.txt` and runs `<cli> status`. Non-zero means a broken
75
+ environment, not a bad spec.
80
76
 
81
- ## 3. Render and look at the result
77
+ ## 3. Render and look
82
78
 
83
79
  ```bash
84
- "<python>" "<cli>" docx render --input "<file>" --out-dir "<dir>"
80
+ "<python>" "<cli>" docx render --input "<file>" --out-dir "<render-dir>"
85
81
  ```
86
82
 
87
- Output: `<dir>/page-1.png`, `page-2.png`, ... (rasterised at `--dpi`, default
88
- 110). For docx/pptx/xlsx the intermediate PDF is kept at `<dir>/<stem>.pdf` —
89
- use it for annotation geometry. `pdf render` rasterises with PyMuPDF and needs
90
- no external renderer.
83
+ Output is `<render-dir>/page-1.png`, `page-2.png`, ... at `--dpi` (default 110).
84
+ For docx/pptx/xlsx, add `--pdf` to also keep `<render-dir>/<stem>.pdf`; use that
85
+ PDF for annotation geometry. `pdf render` uses PyMuPDF and needs no external
86
+ renderer.
91
87
 
92
- Open each page with `read_image`. Look for: text overflowing or clipped at
88
+ Open every page with `read_image`. Check for: text overflowing or clipped at
93
89
  margins, overlapping shapes, unexplained blank pages, tables spilling past the
94
- page, fonts replaced by boxes, missing chart/table content, and numbers that do
95
- not match what the user asked for.
90
+ page, fonts replaced by boxes, missing chart/table content, numbers that do not
91
+ match the request.
96
92
 
97
- Only after the pages look right do you present the file.
93
+ Present only after the pages look right.
98
94
 
99
95
  ## 4. Fix, re-render, present
100
96
 
101
- Edit the spec (or the edit spec), re-run the same command, re-render, look
102
- again. Two or three passes is normal; if a problem survives three passes, say
103
- what still looks wrong instead of pretending it is fixed.
97
+ Change the spec or script, re-run the same command, re-render, look again. Two
98
+ or three passes is normal. If a problem survives three passes, say what still
99
+ looks wrong instead of pretending it is fixed.
104
100
 
105
101
  Then call `present` with the final file and reply with one sentence about what
106
- changed. For an edit, mention the original path and the edited path.
102
+ changed. For an edit, give the original path and the edited path.
107
103
 
108
104
  ## Rasterizing art
109
105
 
110
- Decks, workbooks and documents embed raster images only, so an SVG or a chosen
111
- PDF page has to become a PNG first; the same call downscales an oversized raster.
106
+ Documents, decks, and workbooks embed raster images only. Convert an SVG or one
107
+ PDF page to PNG first; the same call downscales an oversized raster.
112
108
 
113
109
  ```bash
114
110
  "<python>" "<shared>/scripts/ensure_raster_image.py" --input "<art.svg>" --dpi 300 --out "<art.png>"
115
111
  "<python>" "<shared>/scripts/ensure_raster_image.py" --input "<deck.pdf>" --page 2 --width 1600 --out "<slide-2.png>"
116
112
  ```
117
113
 
118
- It prints one JSON object with the PNG's `width`/`height` and never overwrites
119
- the input. Open that PNG with `read_image` and look at it before embedding it:
120
- a blank, clipped or miscoloured raster is invisible inside the host file.
114
+ It prints one JSON object with the PNG `width`/`height` and never overwrites the
115
+ input. Open the PNG with `read_image` before embedding: a blank, clipped, or
116
+ miscoloured raster is invisible inside the host file.
121
117
 
122
118
  ## Failure ladder
123
119
 
124
120
  | Symptom | Meaning | What you do and say |
125
121
  | --- | --- | --- |
126
- | No `## Office runtime` JSON; skill says *No working Python runtime is available* | The plugin cannot install or find a compatible interpreter | Do not run helper commands, do not write a document by hand. Give the user the remedy command from the skill body, ask them to run it, then call `office_status` again. |
127
- | `office_status` shows `cli.exists: false`, or `python`/`cli` file not found | The bundled helper or its install is incomplete | Report the missing path. Do not substitute another tool that produces a different file. |
128
- | `render` fails: `the LibreOffice renderer is not available` | The kit that DSH ships with was not found; create/read/edit still work but docx/pptx/xlsx cannot be rasterised | Do not claim you inspected it. Say the file was built and validated structurally (read verb), but the renderer is unavailable on this host so you could not check its appearance. The kit ships with DeepSeek Harness and the plugin records its path in `config.json` when it loads, so this means the harness install lacks the kit or `DSH_LIBREOFFICE_KIT_CLI` points nowhere; a standalone LibreOffice/Office/WPS install does not help. `pdf render` still works. If the user needs visual confidence now, offer to rebuild from a spec or to present the unverified file explicitly labelled as such. |
129
- | Annotation region does not land on anything, or the payload says `unmapped: true` | The drawn region cannot be tied to an object in the file | Do not silently shift the region or guess a cell/shape. Say what you could identify (preview page, text, resolved cells) and which item you could not locate, then ask the user for the text or cell. |
130
- | `{"ok": false, "error": "..."}` and non-zero exit | The spec was rejected (unknown key, bad type, missing file) | Read the error text, fix the spec key it names, re-run the same command. Never strip a failing block to make the command pass — that silently deletes user content. |
131
- | `xlsx recalc` fails because the LibreOffice kit is missing | openpyxl cannot evaluate formulas, and the kit is the only calculation engine this plugin uses | Report formula cells as uncalculated; do not invent computed values. Fix the renderer first (see the row above) and re-run. |
132
- | The helper has no verb for what the requirement needs | The common path is short; the capability is not limited to it | Take the pack script route, or write a short snippet against `python-docx` / `openpyxl` / `python-pptx` / `pypdf` / `PyMuPDF` / `reportlab`, run it with `<python>`, then `read` the file back and render. Never refuse a job because a verb is missing. |
133
- | `read` returns an empty structure for a file the user supplied | Unsupported or corrupt input | Say what failed and stop; do not overwrite the user's file with an empty one. |
134
-
135
- ## Naming rule for edits (restated)
136
-
137
- `<original>-edited.<ext>`, in the same directory as the original, unless the
138
- user asked for a different name. Never overwrite the original, never delete it,
139
- never "clean up" a temp copy over it. Deliver both paths, original first.
122
+ | No `## Office runtime` JSON; skill says *No working Python runtime is available* | No usable or installable interpreter | Do not run helper commands or write a document by hand. Give the user the remedy command from the skill body, ask them to run it, then call `office_status` again. |
123
+ | `office_status` shows `cli.exists: false`, or the `python`/`cli` path is missing | The bundled helper or its install is incomplete | Report the missing path. Do not substitute another tool that writes a different file. |
124
+ | `render` fails: `the LibreOffice renderer is not available` | The DSH LibreOffice kit was not found. create/read/scripts still work; docx/pptx/xlsx cannot rasterise | Do not claim you inspected it. Say the file was built and validated structurally, but could not be checked visually. The kit ships with DeepSeek Harness and the plugin records its path in `config.json`; a missing kit or a bad `DSH_LIBREOFFICE_KIT_CLI` causes this. A standalone LibreOffice/Office/WPS install does not help. `pdf render` still works. Offer a rebuild from spec or an unverified file labelled as such. |
125
+ | Region lands on nothing, or the payload says `unmapped: true` | The region cannot be tied to an object in the file | Do not shift the region or guess a cell/shape. Say what you identified and what you could not, then ask for the text or cell. |
126
+ | `{"ok": false, "error": "..."}` plus non-zero exit | The spec was rejected (unknown key, bad type, missing file) | Read the error, fix the named spec key, re-run. Never delete a failing block to make the command pass — that silently deletes user content. |
127
+ | `xlsx recalc` fails: kit missing | openpyxl cannot evaluate formulas; the kit is the only calculation engine | Report formula cells as uncalculated; never invent computed values. Fix the renderer and re-run. |
128
+ | No verb for the requirement | The common path is short, not the capability | Take the pack-script or library route, run it, `read` the file back, and render. Never refuse a job because a verb is missing. |
129
+ | `read` returns an empty structure for a supplied file | Unsupported or corrupt input | Say what failed and stop. Never overwrite the user's file with an empty one. |
package/dist/index.js CHANGED
@@ -610,7 +610,7 @@ function apply(ctx) {
610
610
  const parsed = parseSkill(raw, path);
611
611
  const runtime = await ensureRuntime(paths.configFile);
612
612
  const kitCli = resolveKitCli();
613
- const pythonLine = runtime.state === "ok" && runtime.python != null ? `\n\n## Office runtime\n\nUse these verified absolute paths for every helper command. Quote paths that contain spaces. \`skill\` is this skill's own directory: every script it names lives at \`<skill>/scripts/...\`; \`shared\` holds the cross-format workflow notes; the helper finds \`renderer\` on its own, so never pass it.\n\n\`\`\`json\n${JSON.stringify({
613
+ const pythonLine = runtime.state === "ok" && runtime.python != null ? `\n\n## Office runtime\n\nVerified absolute paths. Use them for every helper command; quote paths that contain spaces.\n\n- \`python\` runs all helper scripts, \`cli\` is the Office helper CLI.\n- \`skill\` is this skill's directory: its scripts live under \`<skill>/scripts/\`.\n- \`shared\` holds the cross-format notes and shared scripts.\n- \`renderer\` is the DSH LibreOffice kit; the helper finds it itself, so never pass it.\n\n\`\`\`json\n${JSON.stringify({
614
614
  python: runtime.python.path,
615
615
  cli: paths.cli,
616
616
  skill: dirname(path).split("\\").join("/"),