iwork-studio 2.4.0__tar.gz → 2.4.1__tar.gz

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 (59) hide show
  1. {iwork_studio-2.4.0/src/iwork_studio.egg-info → iwork_studio-2.4.1}/PKG-INFO +2 -2
  2. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/README.md +1 -1
  3. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/pyproject.toml +1 -1
  4. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio/__init__.py +1 -1
  5. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio/mcp_server.py +9 -0
  6. iwork_studio-2.4.1/src/iwork_studio/tool_docs.py +294 -0
  7. {iwork_studio-2.4.0 → iwork_studio-2.4.1/src/iwork_studio.egg-info}/PKG-INFO +2 -2
  8. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio.egg-info/SOURCES.txt +1 -0
  9. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/tests/test_mcp_server.py +7 -0
  10. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/LICENSE +0 -0
  11. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/THIRD_PARTY_NOTICES.md +0 -0
  12. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/setup.cfg +0 -0
  13. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio/app_ops.py +0 -0
  14. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio/apps.py +0 -0
  15. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio/backups.py +0 -0
  16. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio/design.py +0 -0
  17. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio/exporter.py +0 -0
  18. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio/format_check.py +0 -0
  19. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio/helpers.py +0 -0
  20. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio/installer.py +0 -0
  21. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio/keynote_applescript.py +0 -0
  22. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio/keynote_deck.py +0 -0
  23. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio/keynote_io.py +0 -0
  24. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio/keynote_slides.py +0 -0
  25. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio/keynote_table.py +0 -0
  26. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio/keynote_theme.py +0 -0
  27. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio/numbers_format.py +0 -0
  28. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio/numbers_io.py +0 -0
  29. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio/numbers_structure.py +0 -0
  30. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio/pages_io.py +0 -0
  31. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio/pdf.py +0 -0
  32. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio/preview.py +0 -0
  33. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio/render_verify.py +0 -0
  34. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio/review.py +0 -0
  35. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio.egg-info/dependency_links.txt +0 -0
  36. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio.egg-info/entry_points.txt +0 -0
  37. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio.egg-info/requires.txt +0 -0
  38. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/src/iwork_studio.egg-info/top_level.txt +0 -0
  39. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/tests/test_app_ops.py +0 -0
  40. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/tests/test_apps.py +0 -0
  41. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/tests/test_backups.py +0 -0
  42. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/tests/test_design.py +0 -0
  43. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/tests/test_exporter.py +0 -0
  44. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/tests/test_format_check.py +0 -0
  45. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/tests/test_installer.py +0 -0
  46. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/tests/test_keynote_deck.py +0 -0
  47. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/tests/test_keynote_slides.py +0 -0
  48. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/tests/test_keynote_table.py +0 -0
  49. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/tests/test_keynote_theme.py +0 -0
  50. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/tests/test_keynote_writer.py +0 -0
  51. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/tests/test_numbers_format.py +0 -0
  52. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/tests/test_numbers_structure.py +0 -0
  53. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/tests/test_numbers_writer.py +0 -0
  54. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/tests/test_packaging.py +0 -0
  55. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/tests/test_pages_writer.py +0 -0
  56. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/tests/test_preview.py +0 -0
  57. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/tests/test_render_route.py +0 -0
  58. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/tests/test_review.py +0 -0
  59. {iwork_studio-2.4.0 → iwork_studio-2.4.1}/tests/test_value_fidelity.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iwork-studio
3
- Version: 2.4.0
3
+ Version: 2.4.1
4
4
  Summary: Create, edit, design and export Apple Numbers, Keynote and Pages files from any AI agent: verified, reversible, Arabic-safe. MCP server + Python library + agent skill.
5
5
  Author: iWork Studio contributors
6
6
  License-Expression: MIT
@@ -189,7 +189,7 @@ backup → change a scratch copy → re-open it and compare → atomic swap
189
189
 
190
190
  ## All 64 tools
191
191
 
192
- Writes are marked destructive and reads read-only, so clients can ask before writing. Every write that changes an existing file takes `dry_run=true` for a preview.
192
+ Writes are marked destructive and reads read-only, so clients can ask before writing. Every write that changes an existing file takes `dry_run=true` for a preview. Every tool has a title, every parameter a description, and every tool says when to use it instead of its siblings.
193
193
 
194
194
  <details open>
195
195
  <summary><b>Any file</b> (17)</summary>
@@ -152,7 +152,7 @@ backup → change a scratch copy → re-open it and compare → atomic swap
152
152
 
153
153
  ## All 64 tools
154
154
 
155
- Writes are marked destructive and reads read-only, so clients can ask before writing. Every write that changes an existing file takes `dry_run=true` for a preview.
155
+ Writes are marked destructive and reads read-only, so clients can ask before writing. Every write that changes an existing file takes `dry_run=true` for a preview. Every tool has a title, every parameter a description, and every tool says when to use it instead of its siblings.
156
156
 
157
157
  <details open>
158
158
  <summary><b>Any file</b> (17)</summary>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "iwork-studio"
7
- version = "2.4.0"
7
+ version = "2.4.1"
8
8
  description = "Create, edit, design and export Apple Numbers, Keynote and Pages files from any AI agent: verified, reversible, Arabic-safe. MCP server + Python library + agent skill."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,3 +1,3 @@
1
1
  """iWork Studio — verified read/write for Apple Numbers, Keynote and Pages."""
2
2
 
3
- __version__ = "2.4.0"
3
+ __version__ = "2.4.1"
@@ -973,11 +973,20 @@ def _title(name: str) -> str:
973
973
 
974
974
 
975
975
  def _apply_titles() -> None:
976
+ """Title, parameter descriptions and when-to-use guidance on every tool (tool_docs)."""
977
+ from iwork_studio.tool_docs import USAGE, param_doc
978
+
976
979
  for tool in mcp._tool_manager.list_tools():
977
980
  title = _title(tool.name)
978
981
  tool.title = title
979
982
  if tool.annotations is not None:
980
983
  tool.annotations = tool.annotations.model_copy(update={"title": title})
984
+ for name, prop in (tool.parameters.get("properties") or {}).items():
985
+ doc = param_doc(tool.name, name)
986
+ if doc and not prop.get("description"):
987
+ prop["description"] = doc
988
+ if tool.name in USAGE and USAGE[tool.name] not in (tool.description or ""):
989
+ tool.description = f"{tool.description} {USAGE[tool.name]}".strip()
981
990
 
982
991
 
983
992
  _apply_titles()
@@ -0,0 +1,294 @@
1
+ """iWork Studio — parameter descriptions and "when to use it" guidance for every MCP tool.
2
+
3
+ Clients and directories read a tool's parameter descriptions and decide between
4
+ sibling tools from its description, so both live here in one place:
5
+
6
+ param_doc(tool, name) the description of one parameter
7
+ USAGE[tool] one or two sentences: when to use this tool, and which to use instead
8
+
9
+ mcp_server applies them to every registered tool; a test checks nothing is missing.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ _FILE = {"numbers": "the .numbers file", "keynote": "the .key deck", "pages": "the .pages document",
15
+ "iwork": "the .numbers, .key or .pages file"}
16
+
17
+ # Parameters that mean the same thing everywhere.
18
+ COMMON = {
19
+ "dry_run": "true = make the change on a throwaway copy, run every check, and return what would change; "
20
+ "the file itself is not touched. Use it before broad or risky edits and show the user.",
21
+ "sheet": "Sheet name. Omit when the file has one sheet (iwork_read lists sheets and tables).",
22
+ "table": "Table name on that sheet. Omit when the sheet has one table.",
23
+ "slide": "Slide number, 1-based (keynote_list_slides lists them).",
24
+ "cells": 'A cell or range in A1 notation, e.g. "B2" or "A1:D1".',
25
+ "kit": 'A design kit: a name from iwork_list_design_kits (preset or saved, e.g. "executive", "Resal") or '
26
+ 'your own {"fonts": {...}, "colors": {"title": "#RRGGBB", ...}}. Contrast is checked.',
27
+ "ref": 'One cell in A1 notation, e.g. "B2".',
28
+ "overwrite": "true = replace an existing output (the old one is kept as a backup or returned). Default false.",
29
+ "header_rows": "How many rows at the top are headers (styled and kept on top).",
30
+ "header_columns": "How many columns on the left are headers.",
31
+ "color": 'Colour as "#RRGGBB".',
32
+ "width": "Width in points.",
33
+ "x": "Left edge in points from the slide's top-left corner (give with y).",
34
+ "y": "Top edge in points from the slide's top-left corner (give with x).",
35
+ }
36
+
37
+ # Tool-specific parameters (and overrides of COMMON).
38
+ PARAMS: dict[str, dict[str, str]] = {
39
+ "iwork_verify_format": {
40
+ "text": "Text that must appear in the rendered PDF; for Arabic, one word (multi-word RTL text is reordered).",
41
+ "font": 'Expected font; matches when the drawn font name contains it, e.g. "Avenir".',
42
+ "size": "Expected size in points (±0.6).",
43
+ "color": 'Expected text colour "#RRGGBB".',
44
+ "bold": "Expected bold (true) or not bold (false).",
45
+ "page_width": "Expected page width in points.",
46
+ "page_height": "Expected page height in points.",
47
+ },
48
+ "iwork_verify_render": {
49
+ "assert_text": "Text that must be visibly rendered; for Arabic, one word.",
50
+ "expected_pages": "Expected page (or slide) count; omit to skip that check.",
51
+ },
52
+ "numbers_edit_cell": {"value": "New value: a number stays a number, text stays text exactly (Arabic too); null clears. "
53
+ "For a formula use numbers_set_formula."},
54
+ "numbers_set_dimensions": {
55
+ "columns": 'Column widths in points by letter, e.g. {"A": 160, "C": 90}.',
56
+ "rows": 'Row heights in points by 1-based row number, e.g. {"1": 32}.',
57
+ },
58
+ "numbers_set_number_format": {
59
+ "format": "number | currency | percentage | scientific | fraction | datetime | text.",
60
+ "decimal_places": "Digits after the decimal point.",
61
+ "thousands_separator": "true = group thousands (1,234).",
62
+ "negative_style": "minus | red | parentheses | red_parentheses.",
63
+ "currency_code": 'ISO currency code for format=currency, e.g. "SAR" or "USD".',
64
+ "accounting": "true = accounting style (symbol aligned left) for currency.",
65
+ "date_format": 'Date pattern for format=datetime, e.g. "d MMM yyyy".',
66
+ },
67
+ "numbers_set_cell_style": {
68
+ "font_name": 'Font family, e.g. "Helvetica Neue" or "Geeza Pro".',
69
+ "font_size": "Font size in points.",
70
+ "bold": "true/false; omit to leave as is.",
71
+ "italic": "true/false; omit to leave as is.",
72
+ "underline": "true/false; omit to leave as is.",
73
+ "strikethrough": "true/false; omit to leave as is.",
74
+ "font_color": 'Text colour "#RRGGBB".',
75
+ "fill_color": 'Cell fill "#RRGGBB".',
76
+ "align": "Horizontal: left | center | right | justify | auto.",
77
+ "valign": "Vertical: top | middle | bottom.",
78
+ "wrap": "true = wrap text in the cell.",
79
+ },
80
+ "numbers_set_borders": {
81
+ "sides": "all | outline | inner | top | right | bottom | left.",
82
+ "width": "Line width in points.",
83
+ "color": 'Line colour "#RRGGBB".',
84
+ "style": "solid | dashes | dots | none (none removes the border).",
85
+ },
86
+ "numbers_set_headers": {"header_rows": "Number of header rows (0 for none).",
87
+ "header_columns": "Number of header columns (0 for none)."},
88
+ "numbers_merge_cells": {"cells": 'The range to merge, e.g. "A1:C1". Refused if it would hide values.'},
89
+ "numbers_insert": {
90
+ "what": "rows | columns.",
91
+ "count": "How many to insert.",
92
+ "at": "1-based position to insert before; omit to append at the end.",
93
+ "values": "Optional contents: one list per new row (or column), e.g. [[\"Riyadh\", 1200]].",
94
+ },
95
+ "numbers_delete": {"what": "rows | columns.", "at": "1-based position of the first one to delete.",
96
+ "count": "How many to delete."},
97
+ "numbers_add_table": {
98
+ "table_name": "Name of the new table (must not exist on that sheet).",
99
+ "rows": 'The data, header first: [["Region", "Revenue"], ["Riyadh", 1200]].',
100
+ "sheet": "Existing sheet to add it to; omit for the first sheet.",
101
+ "new_sheet": "Name of a new sheet to create for the table instead.",
102
+ },
103
+ "numbers_create": {
104
+ "path": "Where to create the new .numbers file; it must not exist yet.",
105
+ "sheets": 'Sheets with their tables: [{"name": "Sales", "tables": [{"name": "Q1", "rows": [["Region", "Revenue"], '
106
+ '["Riyadh", 1200]], "header_rows": 1}]}].',
107
+ },
108
+ "numbers_import_csv": {
109
+ "csv_path": "The CSV or TSV file to read (UTF-8).",
110
+ "path": "Where to create the new .numbers file; it must not exist yet.",
111
+ "delimiter": 'Column separator, e.g. "," or "\\t"; omit to detect it.',
112
+ "sheet": "Name for the new sheet.",
113
+ "table": "Name for the new table.",
114
+ "numbers": "true = plain numbers become numbers; false = keep every cell as text.",
115
+ },
116
+ "numbers_set_formula": {"formula": 'The formula, starting with "=", e.g. "=SUM(B2:B9)".'},
117
+ "numbers_sort": {"column": 'Column letter to sort by, e.g. "C".', "descending": "true = largest/last first."},
118
+ "keynote_replace_text": {"find": "Text to find (literal unless regex=true).", "replace": "Replacement text.",
119
+ "regex": "true = treat find as a regular expression."},
120
+ "pages_replace_all": {"find": "Text to find, exactly.", "replace": "Replacement text."},
121
+ "pages_set_body": {"body": "The new body text; paragraphs separated by newlines. Resets body formatting."},
122
+ "iwork_restore_backup": {"backup": "Backup name from iwork_list_backups."},
123
+ "keynote_add_slide": {"after": "Insert after this slide number; 0 = make it the first slide; omit = at the end."},
124
+ "keynote_move_slide": {"slide": "The slide to move, 1-based.", "to": "Where it should end up, 1-based."},
125
+ "keynote_skip_slide": {"skipped": "true = hide the slide in the slideshow; false = show it again."},
126
+ "keynote_set_presenter_notes": {"notes": "The presenter notes text (replaces existing notes)."},
127
+ "iwork_export": {
128
+ "format": "Numbers: pdf | xlsx | csv. Pages: pdf | docx | epub | txt | rtf. Keynote: pdf | pptx | images | movie.",
129
+ "out": "Output path (a folder for images); omit to write next to the source.",
130
+ "password": "Optional password for pdf, xlsx, docx or pptx.",
131
+ "password_hint": "Optional hint shown with the password prompt.",
132
+ "image_quality": "good | better | best.",
133
+ "image_format": "Slide images only: jpeg | png | tiff.",
134
+ },
135
+ "iwork_thumbnail": {"out_dir": "Folder for the extracted JPEG; omit for a temporary folder."},
136
+ "iwork_find": {
137
+ "folder": "Folder to search; omit to search the allowed folders, else Documents, Desktop and Downloads.",
138
+ "kind": "numbers | keynote | pages; omit for all three.",
139
+ "name": "Part of the file name to match (case-insensitive).",
140
+ "limit": "Maximum results (1–500).",
141
+ },
142
+ "iwork_extract_design_kit": {
143
+ "path": "A .key deck or .numbers table that already has the look to capture.",
144
+ "name": 'Name for the kit, e.g. "Resal" (required with save=true).',
145
+ "save": "true = save it under name for reuse; contrast must pass.",
146
+ "overwrite": "true = replace a saved kit with the same name.",
147
+ "sheet": "For .numbers: the sheet holding the styled table.",
148
+ "table": "For .numbers: the styled table.",
149
+ },
150
+ "iwork_save_design_kit": {
151
+ "name": 'Name to save under, e.g. "Resal" (letters, digits, spaces, - or _; not a preset name).',
152
+ "kit": '{"fonts": {...}, "colors": {...}, "theme": "...", "background": "#RRGGBB", "base": "<preset>"} '
153
+ "or a preset name to copy.",
154
+ "overwrite": "true = replace a saved kit with the same name.",
155
+ },
156
+ "iwork_delete_design_kit": {"name": "Name of the saved kit to delete (presets can't be deleted)."},
157
+ "numbers_apply_design": {"banding": "true = tint alternate body rows."},
158
+ "iwork_list_templates": {"app": "numbers | pages | keynote."},
159
+ "iwork_create_from_template": {"template": "The user's own .numbers, .key or .pages file to copy.",
160
+ "path": "Where to create the new file; it must not exist yet."},
161
+ "iwork_create": {"path": "Where to create the new file (.numbers, .key or .pages); it must not exist yet.",
162
+ "template": "Template or theme name from iwork_list_templates; omit for Blank / Basic White."},
163
+ "pages_fill_placeholders": {"values": 'Placeholder tag → text, e.g. {"Name": "Sara", "Date": "3 October"} '
164
+ "(tags from pages_list_placeholders)."},
165
+ "pages_set_table_cells": {
166
+ "table": "The table's name or number (from pages_read_tables).",
167
+ "cells": '{"B2": 1200, "C3": "تم", "D9": "=SUM(D2:D8)"}: numbers stay numbers, "=…" is a formula, null clears.',
168
+ },
169
+ "keynote_slideshow": {"action": "start | stop | next | previous.",
170
+ "path": "The deck to present (needed for start).",
171
+ "from_slide": "Slide to start from, 1-based."},
172
+ "keynote_set_theme": {"theme": "Theme name from keynote_list_themes."},
173
+ "keynote_set_slide_layout": {"layout": "Layout (master slide) name from keynote_inspect_style."},
174
+ "keynote_format_text": {
175
+ "item": "Text item index on the slide (from keynote_inspect_style); or use match.",
176
+ "match": "A unique piece of the item's text, to pick it instead of item.",
177
+ "font": 'PostScript font name, e.g. "HelveticaNeue-Bold".',
178
+ "size": "Font size in points.",
179
+ "color": 'Text colour "#RRGGBB".',
180
+ },
181
+ "keynote_set_transition": {
182
+ "effect": 'Transition effect, e.g. "dissolve", "push", "magic move", or "none".',
183
+ "duration": "Duration in seconds.",
184
+ "delay": "Delay before it starts, in seconds.",
185
+ "automatic": "true = advance to the next slide on its own.",
186
+ },
187
+ "keynote_build_deck": {
188
+ "path": "Where to create the new .key deck; it must not exist yet.",
189
+ "slides": 'The outline: [{"title": "…", "body": ["bullet", …], "notes": "…", "layout": "…", "image": "/path.png", '
190
+ '"chart": {…}, "table": {…}}, …]; see the tool description for chart and table.',
191
+ "theme": "Theme name from keynote_list_themes; the kit's theme is used when omitted.",
192
+ "transition": 'Transition for every slide, e.g. "dissolve".',
193
+ },
194
+ "keynote_add_table": {
195
+ "rows": 'The table, header first: [["Region", "Q1"], ["Riyadh", 1200]]; text, numbers or null; "=…" is a formula.',
196
+ "x": "Left edge in points (give with y); omit to let Keynote place it.",
197
+ "y": "Top edge in points (give with x).",
198
+ "width": "Table width in points; omit for Keynote's default.",
199
+ },
200
+ "keynote_slide_image": {"width": "Image width in pixels (320–2560)."},
201
+ "keynote_apply_design": {"set_theme": "true = also switch to the kit's theme first."},
202
+ "keynote_set_slide_text": {"title": "New title text; omit to leave it.",
203
+ "body": "New body: a string, or a list of bullets; omit to leave it."},
204
+ "keynote_add_chart": {
205
+ "rows": 'Row names, e.g. ["2025", "2026"] (series when group_by="row").',
206
+ "columns": 'Column names, e.g. ["Q1", "Q2", "Q3"].',
207
+ "data": "One list of numbers per row name, each as long as columns.",
208
+ "type": "bar | stacked_bar | horizontal_bar | stacked_horizontal_bar | line | area | stacked_area | pie | "
209
+ "scatter (or a *_3d variant).",
210
+ "group_by": 'row (each row is a series) | column.',
211
+ },
212
+ "keynote_add_image": {"image": "Image file to place (png, jpg, heic, pdf…).",
213
+ "x": "Left edge in points (give with y); omit to let Keynote place it.",
214
+ "width": "Image width in points; height keeps the aspect ratio."},
215
+ }
216
+
217
+
218
+ def param_doc(tool: str, name: str) -> str | None:
219
+ if name in PARAMS.get(tool, {}):
220
+ return PARAMS[tool][name]
221
+ if name == "path":
222
+ return f"Path to {_FILE.get(tool.split('_', 1)[0], 'the file')} (absolute, or starting with ~)."
223
+ return COMMON.get(name)
224
+
225
+
226
+ # When to use each tool, and what to use instead.
227
+ USAGE = {
228
+ "iwork_capabilities": "Call first when unsure what this machine can do (Mac apps, GUI session, which routes work).",
229
+ "iwork_read": "Use to see a file's content before editing it. For Numbers formatting use numbers_inspect_format; "
230
+ "for Keynote styling, keynote_inspect_style.",
231
+ "iwork_find": "Use when the user names a file loosely (\"my sales deck\"); then read it with iwork_read.",
232
+ "iwork_metadata": "Use for file facts (template, app build, slide count) without reading content; use iwork_read for content.",
233
+ "iwork_thumbnail": "Use for a quick look at the stored preview with no app; for the real rendering of a slide use keynote_slide_image.",
234
+ "iwork_list_templates": "Use before iwork_create to pick a template or theme name.",
235
+ "iwork_create": "Use for a new file from Apple's templates; for a copy of the user's own file use iwork_create_from_template; "
236
+ "for a Numbers file from data use numbers_create; for a designed deck use keynote_build_deck.",
237
+ "iwork_create_from_template": "Use when the user has their own template file; use iwork_create for Apple's built-in ones.",
238
+ "iwork_export": "Use to hand the result over as PDF, Excel, Word or PowerPoint; never convert back into iWork.",
239
+ "iwork_verify_render": "Use after an important write to confirm the text really shows; use iwork_verify_format to check font, size or colour.",
240
+ "iwork_verify_format": "Use to confirm how text is drawn (font, size, colour); use iwork_verify_render to check text is present.",
241
+ "iwork_list_backups": "Use to find a version to undo to, then restore it with iwork_restore_backup.",
242
+ "iwork_restore_backup": "Use to undo a change: pick the backup with iwork_list_backups first.",
243
+ "iwork_list_design_kits": "Use before styling to pick a kit (presets and the user's saved kits).",
244
+ "iwork_extract_design_kit": "Use when the user has a deck or table with their brand look; use iwork_save_design_kit when they give colours and fonts directly.",
245
+ "iwork_save_design_kit": "Use when the user gives brand colours and fonts; use iwork_extract_design_kit to take them from a file.",
246
+ "iwork_delete_design_kit": "Use only when the user asks to remove a saved kit.",
247
+ "numbers_create": "Use to make a new Numbers file from data; use numbers_import_csv for a CSV file.",
248
+ "numbers_import_csv": "Use for a CSV/TSV file; use numbers_create when you already have the rows.",
249
+ "numbers_edit_cell": "Use for one value; for a formula use numbers_set_formula, for many rows numbers_insert with values.",
250
+ "numbers_set_formula": "Use for a formula cell (Numbers computes it); use numbers_edit_cell for plain values.",
251
+ "numbers_recalculate": "Use when a no-app edit returned formulas_need_recalc, so totals are current.",
252
+ "numbers_insert": "Use to add rows or columns (optionally with values); use numbers_add_table for a separate table.",
253
+ "numbers_delete": "Use to remove rows or columns; undo with iwork_restore_backup.",
254
+ "numbers_add_table": "Use for a new, separate table; use numbers_insert to grow an existing one.",
255
+ "numbers_sort": "Use to reorder body rows by a column.",
256
+ "numbers_inspect_format": "Use before formatting to see current styles, formats, sizes and merges.",
257
+ "numbers_set_cell_style": "Use for fonts, colours, fill and alignment of a range; for a whole designed table use numbers_apply_design.",
258
+ "numbers_set_number_format": "Use for how numbers display (currency, %, dates); values don't change.",
259
+ "numbers_set_borders": "Use for cell borders on a range.",
260
+ "numbers_set_dimensions": "Use for column widths and row heights.",
261
+ "numbers_set_headers": "Use to change how many header rows/columns a table has.",
262
+ "numbers_merge_cells": "Use to merge a range, e.g. a title across columns.",
263
+ "numbers_apply_design": "Use to make a whole table look designed in one call; use numbers_set_cell_style for a few cells.",
264
+ "keynote_build_deck": "Use for a new deck from an outline; then run keynote_review_deck. To change an existing deck use the slide tools.",
265
+ "keynote_set_slide_text": "Use to fill or rewrite a slide's title and body; use keynote_replace_text for find/replace across the deck.",
266
+ "keynote_replace_text": "Use to change the same text everywhere; use keynote_set_slide_text for one slide's title or body.",
267
+ "keynote_list_slides": "Use to see slides, notes and hidden state before slide operations.",
268
+ "keynote_add_slide": "Use for a blank slide; use keynote_duplicate_slide to copy one; use keynote_build_deck for a whole deck.",
269
+ "keynote_duplicate_slide": "Use to copy an existing slide; use keynote_add_slide for a blank one.",
270
+ "keynote_delete_slide": "Use to remove a slide (the last one can't be deleted); to hide it instead use keynote_skip_slide.",
271
+ "keynote_move_slide": "Use to reorder slides.",
272
+ "keynote_skip_slide": "Use to hide or show a slide in the slideshow without deleting it.",
273
+ "keynote_set_presenter_notes": "Use for speaker notes on an existing slide (keynote_build_deck sets notes for new decks).",
274
+ "keynote_list_themes": "Use before keynote_set_theme or keynote_build_deck to pick a theme name.",
275
+ "keynote_inspect_style": "Use before formatting: theme, layouts, and each text item's font, size and colour.",
276
+ "keynote_set_theme": "Use to switch the deck's theme; for a full designed look use keynote_apply_design.",
277
+ "keynote_set_slide_layout": "Use to change one slide's layout (names from keynote_inspect_style).",
278
+ "keynote_format_text": "Use for one text item; to restyle the whole deck use keynote_apply_design.",
279
+ "keynote_set_transition": "Use for one slide's transition (keynote_build_deck can set one for every slide).",
280
+ "keynote_add_image": "Use to place a picture or logo on a slide.",
281
+ "keynote_add_chart": "Use for a chart on an existing slide; in a new deck put it in keynote_build_deck's outline.",
282
+ "keynote_add_table": "Use for a table on an existing slide; in a new deck put it in keynote_build_deck's outline.",
283
+ "keynote_apply_design": "Use to restyle a whole deck from a kit; preview with dry_run, then run keynote_review_deck.",
284
+ "keynote_review_deck": "Use after building or restyling a deck, before saying it's done; fix errors, then look with keynote_slide_image.",
285
+ "keynote_slide_image": "Use to look at a slide's real design (e.g. one keynote_review_deck flagged).",
286
+ "keynote_slideshow": "Use to present the deck; it doesn't change the file.",
287
+ "pages_preflight": "Call once before other Pages tools; a -1712 error means a dialog needs a human.",
288
+ "pages_replace_all": "Use to change text everywhere and keep formatting; pages_set_body replaces the whole body.",
289
+ "pages_set_body": "Use only to replace the whole body (resets its formatting); prefer pages_replace_all or pages_fill_placeholders.",
290
+ "pages_list_placeholders": "Use before pages_fill_placeholders to see the template's fields.",
291
+ "pages_fill_placeholders": "Use for template fields (Name, Date); the only write for page-layout documents like letters.",
292
+ "pages_read_tables": "Use before pages_set_table_cells to see tables and cell references.",
293
+ "pages_set_table_cells": "Use to write into an existing Pages table (new tables can't be created).",
294
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iwork-studio
3
- Version: 2.4.0
3
+ Version: 2.4.1
4
4
  Summary: Create, edit, design and export Apple Numbers, Keynote and Pages files from any AI agent: verified, reversible, Arabic-safe. MCP server + Python library + agent skill.
5
5
  Author: iWork Studio contributors
6
6
  License-Expression: MIT
@@ -189,7 +189,7 @@ backup → change a scratch copy → re-open it and compare → atomic swap
189
189
 
190
190
  ## All 64 tools
191
191
 
192
- Writes are marked destructive and reads read-only, so clients can ask before writing. Every write that changes an existing file takes `dry_run=true` for a preview.
192
+ Writes are marked destructive and reads read-only, so clients can ask before writing. Every write that changes an existing file takes `dry_run=true` for a preview. Every tool has a title, every parameter a description, and every tool says when to use it instead of its siblings.
193
193
 
194
194
  <details open>
195
195
  <summary><b>Any file</b> (17)</summary>
@@ -26,6 +26,7 @@ src/iwork_studio/pdf.py
26
26
  src/iwork_studio/preview.py
27
27
  src/iwork_studio/render_verify.py
28
28
  src/iwork_studio/review.py
29
+ src/iwork_studio/tool_docs.py
29
30
  src/iwork_studio.egg-info/PKG-INFO
30
31
  src/iwork_studio.egg-info/SOURCES.txt
31
32
  src/iwork_studio.egg-info/dependency_links.txt
@@ -122,6 +122,13 @@ def test_tools_listed_with_safety_annotations():
122
122
  assert tools["iwork_read"].annotations.read_only_hint is True
123
123
  assert all(t.title and t.annotations and t.annotations.title == t.title for t in tools.values()) # directories require titles
124
124
  assert tools["keynote_add_table"].title == "Keynote: add table"
125
+ # every parameter is described, and every tool says when to use it (directories grade both)
126
+ from iwork_studio.tool_docs import USAGE
127
+
128
+ undocumented = [(n, p) for n, t in tools.items() for p, v in t.input_schema["properties"].items()
129
+ if not v.get("description")]
130
+ assert undocumented == []
131
+ assert all(USAGE[n] in t.description for n, t in tools.items())
125
132
  assert tools["numbers_edit_cell"].annotations.destructive_hint is True
126
133
 
127
134
 
File without changes
File without changes