@pixerate/editor 0.221.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.
- package/dist/image-editor/index.cjs +17 -2
- package/dist/image-editor/index.js +17 -2
- package/dist/image-editor/selection/inspect.d.cts +1 -1
- package/dist/image-editor/selection/inspect.d.ts +1 -1
- package/package.json +1 -1
- package/skills/README.md +40 -9
- package/skills/WORKFLOW_TEMPLATE.md +50 -0
- package/skills/pixerate-image-editor/SKILL.md +7 -0
- package/skills/pixerate-image-selection-masking/evals/local-adjustment.json +22 -0
- package/skills/pixerate-image-selection-masking/evals/mask-out-background.json +24 -0
- package/skills/pixerate-image-selection-masking/evals/select-one-of-two.json +22 -0
- package/skills/pixerate-spreadsheet/SKILL.md +7 -0
- package/skills/pixerate-spreadsheet-clean-import/SKILL.md +154 -0
- package/skills/pixerate-spreadsheet-clean-import/evals/clean-in-place.json +43 -0
- package/skills/pixerate-spreadsheet-clean-import/evals/dedupe-keep-latest.json +25 -0
- package/skills/pixerate-spreadsheet-clean-import/evals/fix-types-only.json +24 -0
- package/skills/pixerate-video-editor/SKILL.md +8 -0
|
@@ -2807,6 +2807,21 @@ function inspectMaskEdge(mask, image, options = {}) {
|
|
|
2807
2807
|
blk.x1 = Math.max(blk.x1, x);
|
|
2808
2808
|
blk.y1 = Math.max(blk.y1, y);
|
|
2809
2809
|
};
|
|
2810
|
+
const antiAliased = (x, y) => {
|
|
2811
|
+
let full = false;
|
|
2812
|
+
let none = false;
|
|
2813
|
+
for (let dy = -2; dy <= 2; dy++) {
|
|
2814
|
+
for (let dx = -2; dx <= 2; dx++) {
|
|
2815
|
+
const nx = x + dx;
|
|
2816
|
+
const ny = y + dy;
|
|
2817
|
+
if (nx < 0 || ny < 0 || nx >= W || ny >= H) continue;
|
|
2818
|
+
const n = a[ny * W + nx];
|
|
2819
|
+
if (n === 255) full = true;
|
|
2820
|
+
else if (n === 0) none = true;
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
return full && none;
|
|
2824
|
+
};
|
|
2810
2825
|
const x0 = Math.max(0, b.x - 1);
|
|
2811
2826
|
const y0 = Math.max(0, b.y - 1);
|
|
2812
2827
|
const x1 = Math.min(W, b.x + b.width + 1);
|
|
@@ -2836,7 +2851,7 @@ function inspectMaskEdge(mask, image, options = {}) {
|
|
|
2836
2851
|
const partial = v > 0 && v < 255;
|
|
2837
2852
|
if (hi && lo || partial) {
|
|
2838
2853
|
band++;
|
|
2839
|
-
if (partial) soft++;
|
|
2854
|
+
if (partial && !antiAliased(x, y)) soft++;
|
|
2840
2855
|
}
|
|
2841
2856
|
if (v < 128) continue;
|
|
2842
2857
|
const inCanvasLow = (nx, ny) => nx >= 0 && ny >= 0 && nx < W && ny < H && a[ny * W + nx] < 128;
|
|
@@ -13784,7 +13799,7 @@ function createImageSelectionTools(controller) {
|
|
|
13784
13799
|
const inspectEdge = {
|
|
13785
13800
|
name: "inspect_mask_edge",
|
|
13786
13801
|
title: "Inspect a mask edge",
|
|
13787
|
-
description: "Measures the edge of the selection (default), a layer's painted mask (layer-mask) or its vector mask (vector-mask), so you can check it without looking. Returns haloScore 0-1 (background colour left just inside the edge; worse above 0.35), edgeAlignment 0-1 (share of the edge on strong image edges; worse below 0.5), jaggedness (outline turning in radians per 100 px beyond what closing it needs; smooth outlines score under 3, worse above 15), softFraction 0-1 (share of edge pixels
|
|
13802
|
+
description: "Measures the edge of the selection (default), a layer's painted mask (layer-mask) or its vector mask (vector-mask), so you can check it without looking. Returns haloScore 0-1 (background colour left just inside the edge; worse above 0.35), edgeAlignment 0-1 (share of the edge on strong image edges; worse below 0.5), jaggedness (outline turning in radians per 100 px beyond what closing it needs; smooth outlines score under 3, worse above 15), softFraction 0-1 (share of edge pixels in a transition wider than anti-aliasing), holes and islands (count, total area, the 10 largest with bounds), worst (up to 5 regions with a reason, ready for capture_view rect) and issues (findings with a suggested fix; empty when clean). Measures a pixel layer's masks against its own pixels, otherwise the composite. Read-only: inspect, fix the issues, inspect again.",
|
|
13788
13803
|
inputSchema: {
|
|
13789
13804
|
type: "object",
|
|
13790
13805
|
properties: {
|
|
@@ -2807,6 +2807,21 @@ function inspectMaskEdge(mask, image, options = {}) {
|
|
|
2807
2807
|
blk.x1 = Math.max(blk.x1, x);
|
|
2808
2808
|
blk.y1 = Math.max(blk.y1, y);
|
|
2809
2809
|
};
|
|
2810
|
+
const antiAliased = (x, y) => {
|
|
2811
|
+
let full = false;
|
|
2812
|
+
let none = false;
|
|
2813
|
+
for (let dy = -2; dy <= 2; dy++) {
|
|
2814
|
+
for (let dx = -2; dx <= 2; dx++) {
|
|
2815
|
+
const nx = x + dx;
|
|
2816
|
+
const ny = y + dy;
|
|
2817
|
+
if (nx < 0 || ny < 0 || nx >= W || ny >= H) continue;
|
|
2818
|
+
const n = a[ny * W + nx];
|
|
2819
|
+
if (n === 255) full = true;
|
|
2820
|
+
else if (n === 0) none = true;
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
return full && none;
|
|
2824
|
+
};
|
|
2810
2825
|
const x0 = Math.max(0, b.x - 1);
|
|
2811
2826
|
const y0 = Math.max(0, b.y - 1);
|
|
2812
2827
|
const x1 = Math.min(W, b.x + b.width + 1);
|
|
@@ -2836,7 +2851,7 @@ function inspectMaskEdge(mask, image, options = {}) {
|
|
|
2836
2851
|
const partial = v > 0 && v < 255;
|
|
2837
2852
|
if (hi && lo || partial) {
|
|
2838
2853
|
band++;
|
|
2839
|
-
if (partial) soft++;
|
|
2854
|
+
if (partial && !antiAliased(x, y)) soft++;
|
|
2840
2855
|
}
|
|
2841
2856
|
if (v < 128) continue;
|
|
2842
2857
|
const inCanvasLow = (nx, ny) => nx >= 0 && ny >= 0 && nx < W && ny < H && a[ny * W + nx] < 128;
|
|
@@ -13784,7 +13799,7 @@ function createImageSelectionTools(controller) {
|
|
|
13784
13799
|
const inspectEdge = {
|
|
13785
13800
|
name: "inspect_mask_edge",
|
|
13786
13801
|
title: "Inspect a mask edge",
|
|
13787
|
-
description: "Measures the edge of the selection (default), a layer's painted mask (layer-mask) or its vector mask (vector-mask), so you can check it without looking. Returns haloScore 0-1 (background colour left just inside the edge; worse above 0.35), edgeAlignment 0-1 (share of the edge on strong image edges; worse below 0.5), jaggedness (outline turning in radians per 100 px beyond what closing it needs; smooth outlines score under 3, worse above 15), softFraction 0-1 (share of edge pixels
|
|
13802
|
+
description: "Measures the edge of the selection (default), a layer's painted mask (layer-mask) or its vector mask (vector-mask), so you can check it without looking. Returns haloScore 0-1 (background colour left just inside the edge; worse above 0.35), edgeAlignment 0-1 (share of the edge on strong image edges; worse below 0.5), jaggedness (outline turning in radians per 100 px beyond what closing it needs; smooth outlines score under 3, worse above 15), softFraction 0-1 (share of edge pixels in a transition wider than anti-aliasing), holes and islands (count, total area, the 10 largest with bounds), worst (up to 5 regions with a reason, ready for capture_view rect) and issues (findings with a suggested fix; empty when clean). Measures a pixel layer's masks against its own pixels, otherwise the composite. Read-only: inspect, fix the issues, inspect again.",
|
|
13788
13803
|
inputSchema: {
|
|
13789
13804
|
type: "object",
|
|
13790
13805
|
properties: {
|
|
@@ -31,7 +31,7 @@ export interface MaskEdgeReport {
|
|
|
31
31
|
edgeAlignment: number;
|
|
32
32
|
/** Turning of the outline beyond what closing it needs, in radians per 100 px of outline (0: smooth). */
|
|
33
33
|
jaggedness: number;
|
|
34
|
-
/** 0–1: the share of edge pixels
|
|
34
|
+
/** 0–1: the share of edge pixels in a transition wider than anti-aliasing (partly covered, no fully selected and unselected pixel within 2 px). */
|
|
35
35
|
softFraction: number;
|
|
36
36
|
/** Unselected areas enclosed by the selection, largest first (at most 10 listed). */
|
|
37
37
|
holes: {
|
|
@@ -31,7 +31,7 @@ export interface MaskEdgeReport {
|
|
|
31
31
|
edgeAlignment: number;
|
|
32
32
|
/** Turning of the outline beyond what closing it needs, in radians per 100 px of outline (0: smooth). */
|
|
33
33
|
jaggedness: number;
|
|
34
|
-
/** 0–1: the share of edge pixels
|
|
34
|
+
/** 0–1: the share of edge pixels in a transition wider than anti-aliasing (partly covered, no fully selected and unselected pixel within 2 px). */
|
|
35
35
|
softFraction: number;
|
|
36
36
|
/** Unselected areas enclosed by the selection, largest first (at most 10 listed). */
|
|
37
37
|
holes: {
|
package/package.json
CHANGED
package/skills/README.md
CHANGED
|
@@ -5,6 +5,11 @@ Each editor domain in `@pixerate/editor` ships an [Agent Skill](https://agentski
|
|
|
5
5
|
expert in the domain would follow. They are published with the package, so an app or agent
|
|
6
6
|
that uses the tools can load the matching skill.
|
|
7
7
|
|
|
8
|
+
## Reference skills
|
|
9
|
+
|
|
10
|
+
One per editor domain. A reference skill owns its domain's tool and command tables, its mental
|
|
11
|
+
model and its general practice.
|
|
12
|
+
|
|
8
13
|
| Skill | Covers |
|
|
9
14
|
| --- | --- |
|
|
10
15
|
| [`pixerate-agent-tools`](pixerate-agent-tools/SKILL.md) | What every editor has in common: the MCP bridge, toolsets and tool naming, finding tools with `list_toolsets` / `search_tools` / `enable_tools` / `get_skill`, `list_commands` / `run_command` / `run_commands`, errors, undo. Load it with any domain skill. |
|
|
@@ -15,12 +20,9 @@ that uses the tools can load the matching skill.
|
|
|
15
20
|
| [`pixerate-chart`](pixerate-chart/SKILL.md) | Chart specs, data, series, presets, trendlines, Office and Mermaid charts. |
|
|
16
21
|
| [`pixerate-diagram`](pixerate-diagram/SKILL.md) | Mermaid diagrams: nodes, edges, groups, layout. |
|
|
17
22
|
| [`pixerate-image-editor`](pixerate-image-editor/SKILL.md) | Adjustments, adjustment layers, selections and masks. |
|
|
18
|
-
| [`pixerate-image-selection-masking`](pixerate-image-selection-masking/SKILL.md) | Workflow: cutouts, background and sky replacement, local adjustments through masks; choosing a selection method, checking and refining edges, and outputting an editable mask. Built on `pixerate-image-editor`'s tools. |
|
|
19
23
|
| [`pixerate-paint-editor`](pixerate-paint-editor/SKILL.md) | Natural-media painting: paper and grain, dry-media brushes, the brush library, strokes as data, pigment mixing. |
|
|
20
24
|
| [`pixerate-animation-editor`](pixerate-animation-editor/SKILL.md) | Vector animation: shots, commands, timing, Flair and Lottie. |
|
|
21
25
|
| [`pixerate-video-editor`](pixerate-video-editor/SKILL.md) | Non-linear video editing: tracks, clips, transitions, export. |
|
|
22
|
-
| [`pixerate-talking-head-edit`](pixerate-talking-head-edit/SKILL.md) | Talking-head cleanup by transcript: fillers, long pauses and stutters found in one pass and cut as one plan reviewed before it applies. |
|
|
23
|
-
| [`pixerate-bgm-alignment`](pixerate-bgm-alignment/SKILL.md) | Background music: beats and cuts on the beat, ducking under speech, loudness in LUFS. |
|
|
24
26
|
| [`pixerate-generation`](pixerate-generation/SKILL.md) | Planning, estimating and running paid media generation. |
|
|
25
27
|
| [`pixerate-parameters`](pixerate-parameters/SKILL.md) | Effect parameters, presets and keyframes. |
|
|
26
28
|
| [`pixerate-tokens`](pixerate-tokens/SKILL.md) | Design tokens: collections, modes, variables and aliases, through the whole-set document tools. |
|
|
@@ -28,6 +30,20 @@ that uses the tools can load the matching skill.
|
|
|
28
30
|
| [`pixerate-color`](pixerate-color/SKILL.md) | Colour inspection, WCAG accessibility and contrast optimization, CVD simulation, palette audits, harmonies, and OKLab/OKLCH blends. |
|
|
29
31
|
| [`pixerate-typography`](pixerate-typography/SKILL.md) | Fonts and fallback stacks, font loading, modular type scales and hierarchies, fluid sizes, readability and type-aware contrast audits, text segmentation, the font picker. |
|
|
30
32
|
|
|
33
|
+
## Workflow skills
|
|
34
|
+
|
|
35
|
+
A workflow skill covers one job from start to finish: what to confirm first, the steps in
|
|
36
|
+
order with a check after each, a decision table, recipes and the job's pitfalls. It is built on
|
|
37
|
+
its reference skill's tools (load both), and its description is written about the job, so a
|
|
38
|
+
request like "clean up this export" finds it.
|
|
39
|
+
|
|
40
|
+
| Skill | Editor | Covers |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| [`pixerate-image-selection-masking`](pixerate-image-selection-masking/SKILL.md) | Image (`pixerate-image-editor`) | Cutouts, background and sky replacement, local adjustments through masks; choosing a selection method, checking and refining edges, and outputting an editable mask. |
|
|
43
|
+
| [`pixerate-spreadsheet-clean-import`](pixerate-spreadsheet-clean-import/SKILL.md) | Spreadsheet (`pixerate-spreadsheet`) | Imported or pasted data made tidy: structure, headers, types (currency, comma decimals, day-first dates, leading zeros, missing values), text, duplicates and validation. |
|
|
44
|
+
| [`pixerate-talking-head-edit`](pixerate-talking-head-edit/SKILL.md) | Video (`pixerate-video-editor`) | Talking-head cleanup by transcript: fillers, long pauses and stutters found in one pass and cut as one plan reviewed before it applies. Owns the dialogue tools. |
|
|
45
|
+
| [`pixerate-bgm-alignment`](pixerate-bgm-alignment/SKILL.md) | Video (`pixerate-video-editor`) | Background music: beats and cuts on the beat, ducking under speech, loudness in LUFS. Owns the music tools. |
|
|
46
|
+
|
|
31
47
|
## Using them
|
|
32
48
|
|
|
33
49
|
Copy or symlink the directories into your agent's skills folder, for example for Claude Code:
|
|
@@ -75,12 +91,27 @@ same change.
|
|
|
75
91
|
`create*ToolProvider` / `create*Commands` factory is not mapped to a skill. A new domain
|
|
76
92
|
gets a new skill directory, an entry in `expectedBySkill()` and `FACTORIES`, and a row in
|
|
77
93
|
the table above.
|
|
78
|
-
- **Workflow skills.** A skill that covers one job end to end on another skill's tools
|
|
79
|
-
|
|
80
|
-
Its frontmatter adds `metadata` with
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
reference skill
|
|
94
|
+
- **Workflow skills.** A skill that covers one job end to end on another skill's tools has no
|
|
95
|
+
tool table (unless the job has its own provider, as the two video ones do). Start from
|
|
96
|
+
[`WORKFLOW_TEMPLATE.md`](WORKFLOW_TEMPLATE.md). Its frontmatter adds `metadata` with
|
|
97
|
+
`kind: workflow` and `reference: <skill>`; it lists the tools it uses as bullets under
|
|
98
|
+
`## Tools used`; and it is mapped in `WORKFLOWS` in the test (and to `[]` in
|
|
99
|
+
`expectedBySkill()`). The test checks that every tool it uses exists in the reference skill,
|
|
100
|
+
that every backticked tool or command name in its prose is real, that the reference skill
|
|
101
|
+
names it under `## Workflow skills`, that it stays under 250 body lines (aim for 200) and that
|
|
102
|
+
it ships evals. Add its row to the workflow table above.
|
|
103
|
+
- **Evals.** Each workflow skill ships at least three scenarios in `evals/*.json`, run by
|
|
104
|
+
`packages/core/tests/agent-skill-evals.test.ts`. An eval has a `prompt` (the task, in the
|
|
105
|
+
user's words), a `fixture` (the starting document: `{ "editor": "spreadsheet", "rows" }` or
|
|
106
|
+
`{ "editor": "image", "width", "height", "background", "shapes" }`), `checks` (a read-only
|
|
107
|
+
tool, its `args`, what its result must match in `expect`, and `why`) and a `solution` (the
|
|
108
|
+
tool calls an expert would make). The test proves each eval: some check fails on the fixture,
|
|
109
|
+
and all pass after the solution. A model harness runs the same file with the model in place
|
|
110
|
+
of the solution. In `expect`, objects match key by key and arrays element by element, with
|
|
111
|
+
operators for ranges and lists (`$min`, `$max`, `$match`, `$every`, `$some`, `$length`,
|
|
112
|
+
`$unique`, `$distinct`, `$sum`, `$column`, `$nonEmpty`, `$not`, `$in`, `$type`); a check's
|
|
113
|
+
argument can read a value the job created with `{ "$read": "list_layers", "path":
|
|
114
|
+
"layers.-1.id" }`. Checks test the outcome the user asked for, not one way of getting it.
|
|
84
115
|
- **Workflows and practice.** The test cannot check advice. When a feature changes the best
|
|
85
116
|
way to do something (a new batch tool, a new check, a better default), update the working
|
|
86
117
|
method and best practices too.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pixerate-<editor>-<job>
|
|
3
|
+
description: The <job> workflow in the Pixerate <editor> - <what it produces, in the user's words, and the main steps>. Use when asked to <trigger phrases: the verbs and nouns people use for this job>, or when <a tool or two that signal the job> are available. Load with pixerate-<editor>, which lists every tool.
|
|
4
|
+
metadata:
|
|
5
|
+
kind: workflow
|
|
6
|
+
reference: pixerate-<editor>
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# <The job, as a short title>
|
|
10
|
+
|
|
11
|
+
<Two to four sentences: the model of the job a person needs before starting. What the
|
|
12
|
+
document is made of here, and the one or two facts that make this job go right or wrong.>
|
|
13
|
+
|
|
14
|
+
This skill is the method. `pixerate-<editor>` has the full tool reference.
|
|
15
|
+
|
|
16
|
+
## Goal and quality bar
|
|
17
|
+
|
|
18
|
+
- <What the finished result is, stated so it can be checked: numbers, ranges, absences.>
|
|
19
|
+
- <What must stay editable, reversible or untouched.>
|
|
20
|
+
|
|
21
|
+
## Inputs to confirm
|
|
22
|
+
|
|
23
|
+
Ask when you cannot tell from the document; say what you assumed when you can.
|
|
24
|
+
|
|
25
|
+
- **<Decision>:** <the options, and the default when the user does not say>.
|
|
26
|
+
|
|
27
|
+
## Working method
|
|
28
|
+
|
|
29
|
+
1. **<Step>.** <What to do, with which tool and which arguments matter (units, defaults).>
|
|
30
|
+
**Check:** <a read tool and what its result must show before going on>.
|
|
31
|
+
2. **<Step>.** ...
|
|
32
|
+
**Check:** ...
|
|
33
|
+
|
|
34
|
+
## Decision table
|
|
35
|
+
|
|
36
|
+
| Situation | Approach |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| <case> | <method, tool, setting> |
|
|
39
|
+
|
|
40
|
+
## Recipes
|
|
41
|
+
|
|
42
|
+
**<Common request>.** <The tool calls in order, with arguments, as short prose.>
|
|
43
|
+
|
|
44
|
+
## Pitfalls
|
|
45
|
+
|
|
46
|
+
- **<Failure>.** <Why it happens and how to avoid or detect it.>
|
|
47
|
+
|
|
48
|
+
## Tools used
|
|
49
|
+
|
|
50
|
+
- `<tool_name>`, `<tool_name>`: <one-line purpose in this job>
|
|
@@ -33,6 +33,13 @@ Also an optional selection and crop/rotate/flip. See `pixerate-agent-tools` for
|
|
|
33
33
|
- **Coordinates** for selections are natural pixels of the source image (before crop, rotate
|
|
34
34
|
or flip). Read `get_selection` bounds or the image size, never screen pixels.
|
|
35
35
|
|
|
36
|
+
## Workflow skills
|
|
37
|
+
|
|
38
|
+
For these jobs, load the workflow skill too: it has the method, the checks between steps and
|
|
39
|
+
the pitfalls, and uses the tools below.
|
|
40
|
+
|
|
41
|
+
- `pixerate-image-selection-masking`: cutouts, background and sky replacement, local adjustments through masks.
|
|
42
|
+
|
|
36
43
|
## Working method
|
|
37
44
|
|
|
38
45
|
1. **Read.** `get_adjustments` for the current values, process version and layers;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "Make just the red ball black and white, and keep it adjustable so I can tweak it later. The sign in the corner should stay red.",
|
|
3
|
+
"fixture": {
|
|
4
|
+
"editor": "image",
|
|
5
|
+
"width": 120,
|
|
6
|
+
"height": 80,
|
|
7
|
+
"background": [40, 90, 200],
|
|
8
|
+
"shapes": [
|
|
9
|
+
{ "shape": "ellipse", "x": 40, "y": 20, "width": 40, "height": 40, "color": [210, 40, 40] },
|
|
10
|
+
{ "shape": "rect", "x": 100, "y": 6, "width": 12, "height": 8, "color": [210, 40, 40] }
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"checks": [
|
|
14
|
+
{ "why": "a Black & White adjustment layer, masked to the ball and not the sign", "tool": "list_layers", "expect": { "layers": { "$some": { "type": "adjustment", "kind": "blackWhite", "mask": { "kind": "raster", "defaultValue": 0, "paintedBounds": { "x": { "$min": 38, "$max": 41 }, "y": { "$min": 18, "$max": 21 }, "width": { "$min": 39, "$max": 44 }, "height": { "$min": 39, "$max": 44 } } } } } } },
|
|
15
|
+
{ "why": "the photo's own pixels are not changed", "tool": "list_layers", "expect": { "layers": { "$some": { "id": "background", "type": "pixel", "mask": null } } } }
|
|
16
|
+
],
|
|
17
|
+
"solution": [
|
|
18
|
+
{ "tool": "magic_wand", "args": { "x": 60, "y": 40, "tolerance": 20, "contiguous": true } },
|
|
19
|
+
{ "tool": "get_selection" },
|
|
20
|
+
{ "tool": "add_adjustment_layer", "args": { "kind": "blackWhite" } }
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "Hide the blue background so only the red ball shows. Don't delete any pixels; I may want the background back later.",
|
|
3
|
+
"fixture": {
|
|
4
|
+
"editor": "image",
|
|
5
|
+
"width": 120,
|
|
6
|
+
"height": 80,
|
|
7
|
+
"background": [40, 90, 200],
|
|
8
|
+
"shapes": [
|
|
9
|
+
{ "shape": "ellipse", "x": 40, "y": 20, "width": 40, "height": 40, "color": [210, 40, 40] },
|
|
10
|
+
{ "shape": "rect", "x": 100, "y": 6, "width": 12, "height": 8, "color": [210, 40, 40] }
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"checks": [
|
|
14
|
+
{ "why": "the top layer has a layer mask that reveals only the ball", "tool": "list_layers", "expect": { "layers": { "$some": { "type": "pixel", "mask": { "kind": "raster", "enabled": true, "defaultValue": 0, "paintedBounds": { "x": { "$min": 38, "$max": 41 }, "y": { "$min": 18, "$max": 21 }, "width": { "$min": 39, "$max": 44 }, "height": { "$min": 39, "$max": 44 } } } } } } },
|
|
15
|
+
{ "why": "the mask edge is clean", "tool": "inspect_mask_edge", "args": { "target": "layer-mask", "layerId": { "$read": "list_layers", "path": "layers.-1.id" } }, "expect": { "issues": [], "haloScore": { "$max": 0.35 } } }
|
|
16
|
+
],
|
|
17
|
+
"solution": [
|
|
18
|
+
{ "tool": "list_layers" },
|
|
19
|
+
{ "tool": "magic_wand", "args": { "x": 60, "y": 40, "tolerance": 20, "contiguous": true } },
|
|
20
|
+
{ "tool": "inspect_mask_edge" },
|
|
21
|
+
{ "tool": "add_layer_mask", "args": { "layerId": "background", "from": "selection" } },
|
|
22
|
+
{ "tool": "inspect_mask_edge", "args": { "target": "layer-mask", "layerId": "background" } }
|
|
23
|
+
]
|
|
24
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "Select the red ball in the middle of the photo, not the red sign in the top-right corner. Just the selection for now; I'll decide what to do with it.",
|
|
3
|
+
"fixture": {
|
|
4
|
+
"editor": "image",
|
|
5
|
+
"width": 120,
|
|
6
|
+
"height": 80,
|
|
7
|
+
"background": [40, 90, 200],
|
|
8
|
+
"shapes": [
|
|
9
|
+
{ "shape": "ellipse", "x": 40, "y": 20, "width": 40, "height": 40, "color": [210, 40, 40] },
|
|
10
|
+
{ "shape": "rect", "x": 100, "y": 6, "width": 12, "height": 8, "color": [210, 40, 40] }
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"checks": [
|
|
14
|
+
{ "why": "the selection hugs the ball and leaves the sign out", "tool": "get_selection", "expect": { "empty": false, "bounds": { "x": { "$min": 38, "$max": 41 }, "y": { "$min": 18, "$max": 21 }, "width": { "$min": 39, "$max": 44 }, "height": { "$min": 39, "$max": 44 } }, "percent": { "$min": 12, "$max": 14.5 } } },
|
|
15
|
+
{ "why": "a clean edge: no halo, holes or islands", "tool": "inspect_mask_edge", "expect": { "issues": [], "holes": { "count": 0 }, "islands": { "count": 0 } } }
|
|
16
|
+
],
|
|
17
|
+
"solution": [
|
|
18
|
+
{ "tool": "magic_wand", "args": { "x": 60, "y": 40, "tolerance": 20, "contiguous": true } },
|
|
19
|
+
{ "tool": "get_selection" },
|
|
20
|
+
{ "tool": "inspect_mask_edge" }
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -15,6 +15,13 @@ leading `'` forces text.
|
|
|
15
15
|
|
|
16
16
|
Errors, namespacing and `run_commands` batching are in the `pixerate-agent-tools` skill.
|
|
17
17
|
|
|
18
|
+
## Workflow skills
|
|
19
|
+
|
|
20
|
+
For these jobs, load the workflow skill too: it has the method, the checks between steps and
|
|
21
|
+
the pitfalls, and uses the tools below.
|
|
22
|
+
|
|
23
|
+
- `pixerate-spreadsheet-clean-import`: tidying imported or pasted data: structure, headers, types, dates, duplicates.
|
|
24
|
+
|
|
18
25
|
## Working method
|
|
19
26
|
|
|
20
27
|
1. **Read.** `get_range` on the area you will touch (and its header row). The result gives
|
|
@@ -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
|