utopai-research/groups-compose
Designs and maintains semantic groupings and readable layouts on the filmmaking canvas — scenes, character-reference sets, act beats, and other titled visual frames. Use when nodes on the canvas cluster around a shared meaning and would read more clearly if arranged together and wrapped in a frame. Don't force it — groups are a view concern, not an organizing tax.
npx skills add https://github.com/Utopai-Research/pai-pro --skill groups-compose
Skip if fewer than 3 members or the tie is just generation order.
canvas_layout.js to write member positions and groupFrames in one atomic sidecar update.workflow.json; never put x / y into node data.x, y, width, height) is a bounding box computed from the final member positions.frameId: frame_<unix_ms>. Titles ≤30 chars recommended.Pick the one that fits. Grouping is current canvas state; read workflow.json per the project PROJECT_AGENT.md § "Choosing context" to verify ids.
Scene <N> — <location or beat>.A character card + its reference images.
<Character name> — references.image_result nodes depicting the same character.Act <N> or beat name../workflow.json + ./canvas_positions.json. workflow.json gives you node ids + labels + subtypes; canvas_positions.json gives you each node's x / y AND the existing groupFrames map. Reads are unrestricted; writes go through canvas_layout.js.nodes.groupFrames.upsert with memberIds minus the evictee.groupFrames.delete.note: 280 × 420 (width hardcoded; height = NOTE_CARD_FALLBACK_HEIGHT for first paint)image_result: 290 × 220 (16:9 default; if data.metadata.aspect_ratio is present, scale accordingly)video_result: 290 × 220 (same caveat; check data.aspect or data.metadata.aspect_ratio)audio_result: 240 × 64pending / pending_generation / pending_attachment: 260 × 200If measured heights appear in canvas_positions.json, prefer them.
minX = min(node.x for each member)
minY = min(node.y for each member)
maxX = max(node.x + node.w for each member)
maxY = max(node.y + node.h for each member)
x = minX - 24
y = minY - 24
width = (maxX - minX) + 48
height = (maxY - minY) + 48
node "$PAI_REPO_ROOT/server/cli/canvas_layout.js" \
--layout-json '{"positions":{"note_2":{"x":120,"y":80},"image_3":{"x":440,"y":80},"video_1":{"x":760,"y":80}},"groupFrames":{"upsert":{"frame_1716579123456":{"memberIds":["note_2","image_3","video_1"],"x":96,"y":56,"width":978,"height":468,"hue":200,"title":"Scene 1 — Causeway"}},"delete":[]}}'
memberIds, recomputed bbox.canvas_positions.json directly. Use canvas_layout.js so positions and frames apply together.addGroup, updateGroup, deleteGroup).memberIds).Take utopai-research/groups-compose from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.