$npx -y skills add SpillwaveSolutions/automating-mac-apps-plugin --skill automating-numbersAutomates Apple Numbers via JXA with AppleScript dictionary discovery. Use when asked to "automate Numbers spreadsheets", "create spreadsheets programmatically", "JXA Numbers scripting", or "bulk data operations in Numbers". Focuses on sheets, tables, ranges, batch I/O, clipboard
| 1 | # Automating Numbers (JXA-first, AppleScript discovery) |
| 2 | |
| 3 | ## Relationship to the macOS automation skill |
| 4 | - Standalone for Numbers, aligned with `automating-mac-apps` patterns. |
| 5 | - Use `automating-mac-apps` for permissions, shell, and UI scripting guidance. |
| 6 | - **PyXA Installation:** To use PyXA examples in this skill, see the installation instructions in `automating-mac-apps` skill (PyXA Installation section). |
| 7 | |
| 8 | ## Core Framing |
| 9 | - Numbers AppleScript dictionary is AppleScript-first; discover there. |
| 10 | - JXA provides logic and data processing. |
| 11 | - Objects are specifiers; read via methods, write via assignments. |
| 12 | - Handle errors from Numbers operations using try/catch blocks and Application error checking. |
| 13 | |
| 14 | ## Workflow (default) |
| 15 | 1) [ ] Discover terms in Script Editor (Numbers dictionary). |
| 16 | 2) [ ] Prototype minimal AppleScript commands. |
| 17 | 3) [ ] Port to JXA and add defensive checks. |
| 18 | 4) [ ] Prefer batch reads and clipboard shim for writes. |
| 19 | 5) [ ] Use UI scripting only for dictionary gaps. |
| 20 | |
| 21 | ## Validation Checklist |
| 22 | - [ ] Empty document handling works without errors |
| 23 | - [ ] Data integrity verified after batch operations |
| 24 | - [ ] Numbers UI remains responsive after automation runs |
| 25 | - [ ] Errors logged with specific Numbers object paths |
| 26 | - [ ] Sheet/table indices validated before access |
| 27 | - [ ] Clipboard shim restores original clipboard contents |
| 28 | |
| 29 | ## Examples |
| 30 | |
| 31 | **Basic table read (JXA - Legacy):** |
| 32 | ```javascript |
| 33 | const numbers = Application('Numbers'); |
| 34 | const doc = numbers.documents[0]; |
| 35 | const sheet = doc.sheets[0]; |
| 36 | const table = sheet.tables[0]; |
| 37 | const data = table.rows.whose({_not: [{cells: []}]})().map(row => row.cells().map(c => c.value())); |
| 38 | ``` |
| 39 | |
| 40 | **Basic table read (PyXA - Recommended):** |
| 41 | ```python |
| 42 | import PyXA |
| 43 | |
| 44 | numbers = PyXA.Numbers() |
| 45 | |
| 46 | # Get first document, sheet, and table |
| 47 | doc = numbers.documents()[0] |
| 48 | sheet = doc.sheets()[0] |
| 49 | table = sheet.tables()[0] |
| 50 | |
| 51 | # Read all rows with data |
| 52 | rows = table.rows() |
| 53 | data = [] |
| 54 | |
| 55 | for row in rows: |
| 56 | cells = row.cells() |
| 57 | if cells: # Skip empty rows |
| 58 | row_data = [cell.value() for cell in cells] |
| 59 | data.append(row_data) |
| 60 | |
| 61 | print("Table data:", data) |
| 62 | ``` |
| 63 | |
| 64 | **PyObjC with Scripting Bridge:** |
| 65 | ```python |
| 66 | from ScriptingBridge import SBApplication |
| 67 | |
| 68 | numbers = SBApplication.applicationWithBundleIdentifier_("com.apple.Numbers") |
| 69 | |
| 70 | # Access document and table |
| 71 | doc = numbers.documents()[0] |
| 72 | sheet = doc.sheets()[0] |
| 73 | table = sheet.tables()[0] |
| 74 | |
| 75 | # Read table data |
| 76 | rows = table.rows() |
| 77 | data = [] |
| 78 | |
| 79 | for row in rows: |
| 80 | cells = row.cells() |
| 81 | if cells: |
| 82 | row_data = [cell.value() for cell in cells] |
| 83 | data.append(row_data) |
| 84 | |
| 85 | print("Table data:", data) |
| 86 | ``` |
| 87 | |
| 88 | **Batch write with clipboard shim (JXA - Legacy):** |
| 89 | ```javascript |
| 90 | const numbers = Application('Numbers'); |
| 91 | // Prepare data array |
| 92 | const data = [['Name', 'Age'], ['Alice', 25], ['Bob', 30]]; |
| 93 | // Use clipboard for bulk insertion |
| 94 | const app = Application.currentApplication(); |
| 95 | app.includeStandardAdditions = true; |
| 96 | app.setTheClipboardTo(data.map(row => row.join('\t')).join('\n')); |
| 97 | numbers.activate(); |
| 98 | delay(0.5); |
| 99 | // UI scripting to paste |
| 100 | SystemEvents = Application('System Events'); |
| 101 | SystemEvents.keystroke('v', {using: 'command down'}); |
| 102 | ``` |
| 103 | |
| 104 | **Batch write (PyXA - Modern):** |
| 105 | ```python |
| 106 | import PyXA |
| 107 | |
| 108 | numbers = PyXA.Numbers() |
| 109 | |
| 110 | # Prepare data |
| 111 | data = [ |
| 112 | ['Name', 'Age'], |
| 113 | ['Alice', 25], |
| 114 | ['Bob', 30] |
| 115 | ] |
| 116 | |
| 117 | # Get table to write to |
| 118 | doc = numbers.documents()[0] |
| 119 | sheet = doc.sheets()[0] |
| 120 | table = sheet.tables()[0] |
| 121 | |
| 122 | # Clear existing data and write new data |
| 123 | table.clear() # Clear table first |
| 124 | |
| 125 | for i, row_data in enumerate(data): |
| 126 | # Add row if needed |
| 127 | if i >= len(table.rows()): |
| 128 | table.rows().push({}) |
| 129 | |
| 130 | # Set cell values |
| 131 | row = table.rows()[i] |
| 132 | for j, value in enumerate(row_data): |
| 133 | if j >= len(row.cells()): |
| 134 | row.cells().push({}) |
| 135 | cell = row.cells()[j] |
| 136 | cell.value = value |
| 137 | ``` |
| 138 | |
| 139 | **PyObjC Batch Write:** |
| 140 | ```python |
| 141 | from ScriptingBridge import SBApplication |
| 142 | |
| 143 | numbers = SBApplication.applicationWithBundleIdentifier_("com.apple.Numbers") |
| 144 | |
| 145 | # Prepare data |
| 146 | data = [ |
| 147 | ['Name', 'Age'], |
| 148 | ['Alice', 25], |
| 149 | ['Bob', 30] |
| 150 | ] |
| 151 | |
| 152 | # Get table |
| 153 | doc = numbers.documents()[0] |
| 154 | sheet = doc.sheets()[0] |
| 155 | table = sheet.tables()[0] |
| 156 | |
| 157 | # Clear and write data |
| 158 | table.clear() |
| 159 | |
| 160 | for i, row_data in enumerate(data): |
| 161 | # Ensure row exists |
| 162 | while len(table.rows()) <= i: |
| 163 | table.rows().push({}) |
| 164 | |
| 165 | row = table.rows()[i] |
| 166 | |
| 167 | for j, value in enumerate(row_data): |
| 168 | # Ensure cell exists |
| 169 | while len(row.cells()) <= j: |
| 170 | row.cells().push({}) |
| 171 | |
| 172 | cell = row.cells()[j] |
| 173 | cell.value = value |
| 174 | ``` |
| 175 | |
| 176 | ## When Not to Use |
| 177 | - General macOS automation without |