@pixerate/editor 0.220.0 → 0.221.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 (29) hide show
  1. package/dist/image-editor/controller.d.cts +27 -4
  2. package/dist/image-editor/controller.d.ts +27 -4
  3. package/dist/image-editor/index.cjs +2276 -1864
  4. package/dist/image-editor/index.js +1240 -828
  5. package/dist/image-editor/layers/serialize.d.cts +2 -0
  6. package/dist/image-editor/layers/serialize.d.ts +2 -0
  7. package/dist/image-editor/selection/index.d.cts +1 -0
  8. package/dist/image-editor/selection/index.d.ts +1 -0
  9. package/dist/image-editor/selection/inspect.d.cts +1 -1
  10. package/dist/image-editor/selection/inspect.d.ts +1 -1
  11. package/dist/image-editor/selection/recipe.d.cts +89 -0
  12. package/dist/image-editor/selection/recipe.d.ts +89 -0
  13. package/dist/image-editor/types.d.cts +9 -0
  14. package/dist/image-editor/types.d.ts +9 -0
  15. package/package.json +1 -1
  16. package/skills/README.md +40 -9
  17. package/skills/WORKFLOW_TEMPLATE.md +50 -0
  18. package/skills/pixerate-image-editor/SKILL.md +14 -11
  19. package/skills/pixerate-image-editor/references/painting.md +17 -1
  20. package/skills/pixerate-image-selection-masking/SKILL.md +3 -3
  21. package/skills/pixerate-image-selection-masking/evals/local-adjustment.json +22 -0
  22. package/skills/pixerate-image-selection-masking/evals/mask-out-background.json +24 -0
  23. package/skills/pixerate-image-selection-masking/evals/select-one-of-two.json +22 -0
  24. package/skills/pixerate-spreadsheet/SKILL.md +7 -0
  25. package/skills/pixerate-spreadsheet-clean-import/SKILL.md +154 -0
  26. package/skills/pixerate-spreadsheet-clean-import/evals/clean-in-place.json +43 -0
  27. package/skills/pixerate-spreadsheet-clean-import/evals/dedupe-keep-latest.json +25 -0
  28. package/skills/pixerate-spreadsheet-clean-import/evals/fix-types-only.json +24 -0
  29. package/skills/pixerate-video-editor/SKILL.md +8 -0
@@ -0,0 +1,154 @@
1
+ ---
2
+ name: pixerate-spreadsheet-clean-import
3
+ description: The clean-up workflow for imported data in a Pixerate spreadsheet - a CSV export, a pasted table or a report dump turned into tidy data. Covers finding the data region, profiling each column, removing title, blank, total and repeated header rows, normalising headers, fixing types (numbers stored as text, currency and European decimals, day-first dates read month-first, lost leading zeros, missing-value markers), trimming and unifying text, removing duplicates by a key, and validating the result without damaging the source. Use when asked to clean, tidy, fix, normalise, deduplicate or prepare imported or pasted data for analysis, or when a column will not sum or sort. Load with pixerate-spreadsheet, which lists every tool.
4
+ metadata:
5
+ kind: workflow
6
+ reference: pixerate-spreadsheet
7
+ ---
8
+
9
+ # Cleaning imported data
10
+
11
+ Imported data arrives as typed text: each cell is parsed the way a person's typing is (en-US),
12
+ so `$1,200.00` and `(45)` become numbers, `3/4/2026` becomes the ISO date `2026-03-04`, and
13
+ anything the parser does not recognise (`USD 300`, `1.250,50`, `31/03/2026`, `n/a`) stays text.
14
+ `get_range` shows both sides: `raw` is what was typed, `values` what it became. Cleaning is
15
+ reading both, deciding what each column should be, and writing it back in blocks.
16
+
17
+ This skill is the method. `pixerate-spreadsheet` has the full tool reference and the formula list.
18
+
19
+ ## Goal and quality bar
20
+
21
+ - One header row of short, unique, trimmed names, with units in the header (`Amount (USD)`).
22
+ - One record per row: no title, notes, blank, subtotal, total or repeated header rows inside the data.
23
+ - One type per column: numbers are numbers (they sum), dates are ISO dates, codes that must keep
24
+ leading zeros are text, missing values are empty cells (not 0, not `n/a`).
25
+ - Text is trimmed and each category is spelled one way (`North`, not `north` and `NORTH `).
26
+ - No duplicates by the key the user chose; the right row of each set is kept.
27
+ - Nothing is lost by accident: the user can see what changed, and undo it.
28
+
29
+ ## Inputs to confirm
30
+
31
+ Ask when you cannot tell from the data; say what you assumed when you can.
32
+
33
+ - **In place or beside?** Clean the cells in place (the default for a sheet that is only an
34
+ import), or keep the raw columns and add clean ones next to them (when the raw data is the
35
+ only copy, or must be audited).
36
+ - **Date order** of the source: month-first (US) or day-first (most other places). A raw date
37
+ whose first part is above 12 (`31/03/2026`) settles it.
38
+ - **The duplicate key** (which columns make two rows the same record, and whether case and
39
+ spaces count) and **which row wins** (first, last, most recently updated, most complete).
40
+ - **Missing values:** empty (the default), or a value the user names. Never 0 unless told.
41
+ - **Category spelling:** the canonical form when variants disagree (`North` or `north`).
42
+
43
+ ## Working method
44
+
45
+ 1. **Find the data and keep a way back.** `get_range` a generous area (`A1:Z60`) and find the
46
+ header row, the first and last data row and any title, notes or total rows. If rows are
47
+ hidden or filtered, run `spreadsheet.unhideAll` or `spreadsheet.clearFilters` first so the
48
+ user sees what you clean. Before a large rewrite, `get_spreadsheet_document` keeps a copy
49
+ that `load_spreadsheet_document` can restore; small steps are each one `undo_spreadsheet`.
50
+ **Check:** you can name the data range and each column's intended type.
51
+ 2. **Profile each column** from `raw` and `values` together: numbers stored as text (a string in
52
+ `values` where the rest are numbers), currency codes, comma decimals, slash dates and their
53
+ order, codes that lost leading zeros (a number where text is expected: ZIP, ID, phone),
54
+ missing-value markers (`n/a`, `-`, `NULL`, `?`), stray spaces and case variants.
55
+ **Check:** a list of problems per column, with counts. Report anything you will not fix.
56
+ 3. **Fix the structure.** `unmerge_cells` over the region first. Delete title, notes, blank,
57
+ total and repeated header rows with `delete_rows`, from the bottom up so earlier row numbers
58
+ stay valid, or rewrite the whole block and delete the leftover tail once.
59
+ **Check:** `get_range` shows the header in row 1 of the region, data directly below, and
60
+ `sheet.rows` is the count you expect.
61
+ 4. **Fix the headers** in one `set_range` over the header row: trimmed, unique, plain names,
62
+ units in the header and not in the cells.
63
+ **Check:** no duplicates, no leading or trailing spaces.
64
+ 5. **Fix the values, a column (or a block) at a time** with `set_range`: numbers as JSON
65
+ numbers, dates as ISO text, text codes with a leading `'` (`'02134`), missing values as
66
+ `null`. For a large table use formula columns (see the recipes) rather than copying values
67
+ through the conversation.
68
+ **Check:** the returned `errors` is empty, and a `get_range` of the column shows one type.
69
+ 6. **Remove duplicates** by the confirmed key, keeping the winning row of each set. Rewrite the
70
+ kept rows as one block and delete the rest, or delete the losing rows from the bottom up.
71
+ **Check:** the row count fell by the number of duplicates you found in step 2.
72
+ 7. **Validate the whole region.** `get_range` it once more: `errors` is empty, every column has
73
+ one type, totals you can compute (a sum of amounts, a row count) match what you expect from
74
+ the source. For a big sheet, count in scratch cells instead of reading every value (see the
75
+ recipes), then clear them.
76
+ **Check:** every problem from step 2 is fixed or reported.
77
+ 8. **Report** what changed: rows removed (blank, total, duplicate) and why, columns converted,
78
+ assumptions (date order, the key, the canonical spelling) and anything left for the user.
79
+
80
+ ## Decision table
81
+
82
+ | Situation | Approach |
83
+ | --- | --- |
84
+ | Small table (a few hundred cells to change), sheet is only an import | Read, then rewrite the cleaned block in place with `set_range`; delete leftover rows |
85
+ | Large table, or the raw data must stay | Formula columns beside the raw ones (`=TRIM(C2:C5000)`, `=MAP(...)`); the raw columns stay as they are |
86
+ | One or two broken columns, the rest fine | `set_range` on those columns only; leave every other cell untouched |
87
+ | Numbers with a currency code or unit (`USD 300`, `12 kg`) | Strip it, put the unit in the header; ask when one column mixes currencies |
88
+ | Comma decimals (`1.250,50`) | Convert to `1250.5`; then check that cells the parser already turned into numbers (`1,250` read as 1250) were not meant as `1.25` |
89
+ | Slash dates, source is day-first | Rewrite every slash date in the column from `raw` as ISO, including the ones that already parsed (they parsed month-first and are wrong) |
90
+ | Codes that must keep zeros (ZIP, ID, phone) | Text with a leading `'`, padded to the code's length |
91
+ | Missing markers (`n/a`, `-`, `NULL`) | Empty cell (`null`), unless the user names a value |
92
+ | Exact duplicate rows | Keep the first |
93
+ | Same key, different details | Keep the row the user chose (latest, most complete); say which you dropped |
94
+ | Merged title cells above the header | Unmerge, then delete the title rows or leave them above the data with a blank row between |
95
+
96
+ ## Recipes
97
+
98
+ **Rewrite in place (small table).** `get_range` the region; build the clean block (header plus
99
+ kept rows) in one `set_range` from the top-left cell; `delete_rows` the rows left below it.
100
+ That is two undo steps, and every cell the user had is accounted for in your report.
101
+
102
+ **Formula columns (large table).** Insert clean columns with `insert_columns` to the right of
103
+ the raw ones and write one spilling formula at the top of each, leaving the cells below empty:
104
+
105
+ - Trim text: `=TRIM(C2:C5000)`; one spelling: `=MAP(E2:E5000,LAMBDA(v,PROPER(TRIM(v))))`.
106
+ - Currency text to numbers: `=MAP(D2:D5000,LAMBDA(v,IF(ISNUMBER(v),v,VALUE(TRIM(SUBSTITUTE(v,"USD",""))))))`.
107
+ - Comma decimals: `VALUE(SUBSTITUTE(SUBSTITUTE(v,".",""),",","."))` inside the same `MAP`.
108
+ - Day-first text dates: `DATE(RIGHT(v,4),MID(v,4,2),LEFT(v,2))`, which gives an ISO date; take
109
+ `v` from the raw text, since a date the parser already read month-first is wrong.
110
+
111
+ Tell the user the clean columns are formulas over the raw ones (sorting or deleting the raw
112
+ columns breaks them), and name the headers you gave them.
113
+
114
+ **Count instead of reading (big sheets).** Write checks into a spare cell outside the data, read
115
+ them, then clear the cell with `set_range` and `null`:
116
+
117
+ - Duplicates by key: `=COUNTA(A2:A5000)-COUNTA(UNIQUE(A2:A5000))`.
118
+ - Text in a number column: `=COUNTA(D2:D5000)-COUNT(D2:D5000)`.
119
+ - Stray spaces: `=COUNTIF(C2:C5000," *")+COUNTIF(C2:C5000,"* ")`.
120
+
121
+ **Keep the latest of each duplicate.** Read the key and date columns, pick the winner of each
122
+ set by date, then rewrite the kept rows as one block (or delete the losers bottom up). Normalise
123
+ the key (trim, lower case) only for comparing; write the kept row's own key back cleaned.
124
+
125
+ ## Pitfalls
126
+
127
+ - **Month-first parsing.** `02/04/2026` from a day-first export is stored as 4 February, with no
128
+ error. Only `raw` shows the original; check it whenever a column has slash dates.
129
+ - **Silent number parsing.** `1,250` becomes 1250 and `007` becomes 7 on input. Write codes with
130
+ a leading `'`, and check comma-decimal sources for values that parsed as thousands.
131
+ - **Totals inside the data** double every sum. A total row is usually last, has an empty key and
132
+ a `SUM` in `raw`; delete it, and compute totals elsewhere if the user wants them.
133
+ - **Deleting top-down** shifts the rows below, so the next row number you delete is wrong.
134
+ Delete from the bottom, or rewrite the block and delete the tail.
135
+ - **Formulas that point into the data** become `#REF!` when rows they use are deleted. Read
136
+ `raw` for formulas before deleting and check `errors` after.
137
+ - **Spills need room.** A spilling clean column turns into `#SPILL!` if anything sits below it.
138
+ - **0 is not missing.** Writing 0 for `n/a` changes averages and counts; write `null`.
139
+ - **Hidden rows are still data.** `get_range` returns them; unhide before you show the user a
140
+ result, so what they see matches what you counted.
141
+ - **Copying values through the conversation** is slow and error-prone for thousands of cells;
142
+ use formula columns, and keep the values you write to the cells that change.
143
+
144
+ ## Tools used
145
+
146
+ - `get_range`: read `raw` and `values`, the sheet size, errors and merges, before and after every step
147
+ - `set_range`: write headers, cleaned blocks, formula columns and scratch checks, one undo step each
148
+ - `set_cell`: fix one cell
149
+ - `delete_rows`, `insert_rows`: remove title, blank, total and duplicate rows; make room
150
+ - `insert_columns`, `delete_columns`: add clean formula columns; drop empty or unwanted ones
151
+ - `unmerge_cells`: split merged cells before restructuring
152
+ - `get_spreadsheet_document`, `load_spreadsheet_document`: keep a copy before a large rewrite, and restore it
153
+ - `undo_spreadsheet`: take back a step
154
+ - `spreadsheet.unhideAll`, `spreadsheet.clearFilters`: see every row before cleaning
@@ -0,0 +1,43 @@
1
+ {
2
+ "prompt": "This sheet is a raw CSV export of orders from our European shop. Clean it up in place so it can be analysed: tidy headers, one type per column, and no blank, duplicate or total rows.",
3
+ "fixture": {
4
+ "editor": "spreadsheet",
5
+ "rows": [
6
+ [" Order ID", "date", "Customer name ", "Amount", "region"],
7
+ ["1001", "2026-03-04", "acme corp", "$1,200.00", "north"],
8
+ ["1002", "31/03/2026", "Globex ", "950", "South"],
9
+ ["", "", "", "", ""],
10
+ ["1003", "4-Mar-2026", "Initech", "USD 300", "NORTH"],
11
+ ["1002", "31/03/2026", "Globex ", "950", "South"],
12
+ ["1004", "02/04/2026", "Umbrella", "1.250,50", " south"],
13
+ ["", "", "Total", "=SUM(D2:D7)", ""]
14
+ ]
15
+ },
16
+ "checks": [
17
+ { "why": "headers are trimmed and unique", "tool": "get_range", "args": { "range": "A1:E1" }, "expect": { "values": [{ "$every": { "$match": "^\\S(.*\\S)?$" }, "$unique": true }] } },
18
+ { "why": "one header row and the four distinct orders, with no blank, duplicate or total rows", "tool": "get_range", "args": { "range": "A1:H40" }, "expect": { "values": { "$nonEmpty": { "$length": 5 } } } },
19
+ { "why": "order ids are unique", "tool": "get_range", "args": { "range": "A2:A40" }, "expect": { "values": { "$nonEmpty": { "$column": [0, { "$unique": true }] } } } },
20
+ { "why": "amounts are numbers, European decimals and currency text included", "tool": "get_range", "args": { "range": "D2:D40" }, "expect": { "values": { "$nonEmpty": { "$column": [0, { "$sum": 3700.5 }] } } } },
21
+ { "why": "dates are real dates read day-first, as the export writes them (02/04 is 2 April)", "tool": "get_range", "args": { "range": "B2:B40" }, "expect": { "values": { "$nonEmpty": { "$column": [0, { "$every": { "$match": "^\\d{4}-\\d{2}-\\d{2}$" }, "$some": "2026-04-02", "$not": { "$some": "2026-02-04" } }] } } } },
22
+ { "why": "customer names are trimmed", "tool": "get_range", "args": { "range": "C2:C40" }, "expect": { "values": { "$nonEmpty": { "$column": [0, { "$every": { "$match": "^\\S(.*\\S)?$" } }] } } } },
23
+ { "why": "regions are spelled one way", "tool": "get_range", "args": { "range": "E2:E40" }, "expect": { "values": { "$nonEmpty": { "$column": [0, { "$distinct": { "$length": 2 } }] } } } },
24
+ { "why": "no formula errors", "tool": "get_range", "args": { "range": "A1:H40" }, "expect": { "errors": [] } }
25
+ ],
26
+ "solution": [
27
+ { "tool": "get_range", "args": { "range": "A1:Z50" } },
28
+ {
29
+ "tool": "set_range",
30
+ "args": {
31
+ "start": "A1",
32
+ "values": [
33
+ ["Order ID", "Date", "Customer", "Amount", "Region"],
34
+ [1001, "2026-03-04", "Acme Corp", 1200, "North"],
35
+ [1002, "2026-03-31", "Globex", 950, "South"],
36
+ [1003, "2026-03-04", "Initech", 300, "North"],
37
+ [1004, "2026-04-02", "Umbrella", 1250.5, "South"]
38
+ ]
39
+ }
40
+ },
41
+ { "tool": "delete_rows", "args": { "start": 6, "count": 3 } }
42
+ ]
43
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "prompt": "Remove duplicate customers from this list. Two rows are the same customer when the email matches, ignoring case and spaces; keep each customer's most recently updated row.",
3
+ "fixture": {
4
+ "editor": "spreadsheet",
5
+ "rows": [
6
+ ["Email", "Name", "Plan", "Updated"],
7
+ ["ann@x.com", "Ann Lee", "Free", "2026-01-10"],
8
+ ["bob@y.com", "Bob Ray", "Pro", "2026-02-01"],
9
+ ["Ann@X.com ", "Ann Lee", "Pro", "2026-05-01"],
10
+ ["cat@z.com", "Cat Sun", "Free", "2026-03-15"],
11
+ ["bob@y.com", "Bob Ray", "Pro", "2026-02-01"]
12
+ ]
13
+ },
14
+ "checks": [
15
+ { "why": "one row per customer under the header", "tool": "get_range", "args": { "range": "A1:F40" }, "expect": { "values": { "$nonEmpty": { "$length": 4 } } } },
16
+ { "why": "Ann's latest row (Pro, updated 1 May) is the one kept", "tool": "get_range", "args": { "range": "A2:D40" }, "expect": { "values": { "$some": [{ "$match": ["^\\s*ann@x\\.com\\s*$", "i"] }, "Ann Lee", "Pro", "2026-05-01"] } } },
17
+ { "why": "Bob and Cat are still there", "tool": "get_range", "args": { "range": "A2:A40" }, "expect": { "values": { "$some": ["bob@y.com"], "$nonEmpty": { "$some": ["cat@z.com"] } } } },
18
+ { "why": "the header row is unchanged", "tool": "get_range", "args": { "range": "A1:D1" }, "expect": { "values": [["Email", "Name", "Plan", "Updated"]] } }
19
+ ],
20
+ "solution": [
21
+ { "tool": "get_range", "args": { "range": "A1:D20" } },
22
+ { "tool": "set_range", "args": { "start": "A2", "values": [["ann@x.com", "Ann Lee", "Pro", "2026-05-01"], ["bob@y.com", "Bob Ray", "Pro", "2026-02-01"], ["cat@z.com", "Cat Sun", "Free", "2026-03-15"]] } },
23
+ { "tool": "delete_rows", "args": { "start": 5, "count": 2 } }
24
+ ]
25
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "prompt": "The ZIP column lost its leading zeros on import and Amount has some text in it. Fix those two columns so ZIPs are five-digit codes and amounts are numbers (leave missing amounts empty). Don't change anything else.",
3
+ "fixture": {
4
+ "editor": "spreadsheet",
5
+ "rows": [
6
+ ["ZIP", "Amount", "Date", "Note"],
7
+ ["2134", "300 USD", "2026-03-04", "rush"],
8
+ ["90210", "1 200", "2026-03-05", ""],
9
+ ["601", "(45)", "2026-03-05", "refund"],
10
+ ["10001", "n/a", "2026-03-06", "check with sales"]
11
+ ]
12
+ },
13
+ "checks": [
14
+ { "why": "ZIPs are five-digit text, leading zeros restored", "tool": "get_range", "args": { "range": "A2:A5" }, "expect": { "values": [["02134"], ["90210"], ["00601"], ["10001"]] } },
15
+ { "why": "amounts are numbers; a missing amount is empty, not 0", "tool": "get_range", "args": { "range": "B2:B5" }, "expect": { "values": [[300], [1200], [-45], [""]] } },
16
+ { "why": "the other columns and the headers are untouched", "tool": "get_range", "args": { "range": "A1:D5" }, "expect": { "raw": [["ZIP", "Amount", "Date", "Note"], { "$length": 4 }, { "$length": 4 }, { "$length": 4 }, { "$length": 4 }] } },
17
+ { "why": "dates and notes keep what was typed", "tool": "get_range", "args": { "range": "C2:D5" }, "expect": { "raw": [["2026-03-04", "rush"], ["2026-03-05", ""], ["2026-03-05", "refund"], ["2026-03-06", "check with sales"]] } },
18
+ { "why": "the sheet keeps its size", "tool": "get_range", "args": { "range": "A1" }, "expect": { "sheet": { "rows": 5, "columns": 4 } } }
19
+ ],
20
+ "solution": [
21
+ { "tool": "get_range", "args": { "range": "A1:D5" } },
22
+ { "tool": "set_range", "args": { "start": "A2", "values": [["'02134", 300], ["'90210", 1200], ["'00601", -45], ["'10001", null]] } }
23
+ ]
24
+ }
@@ -26,6 +26,14 @@ them from `get_video_project`; never invent them.
26
26
 
27
27
  For errors, namespaced tool names and undo conventions, see `pixerate-agent-tools`.
28
28
 
29
+ ## Workflow skills
30
+
31
+ For these jobs, load the workflow skill too: it has the method, the checks between steps and
32
+ the pitfalls, and uses the tools below.
33
+
34
+ - `pixerate-talking-head-edit`: dialogue cleanup by transcript: fillers, pauses, stutters.
35
+ - `pixerate-bgm-alignment`: cutting to the beat, ducking music under speech, loudness.
36
+
29
37
  ## Working method
30
38
 
31
39
  1. **Read.** `get_video_project` for settings, assets, tracks, clips (with end and source