{"ok":true,"site":{"host":"floorplay-zeta.vercel.app","url":"https://floorplay-zeta.vercel.app/","type":"live","desc":"Floorplay","tools":[{"name":"get_room","kind":"answer","impl":"imperative","description":"Read the current room: shell, openings (with compass facing), brief, placed items with positions and light scores, the user's selection, open proposals, metrics and violations. Coordinates are integer centimeters. Origin is the top-left corner of the room; x grows to the right, y grows downward. Items are placed by their center. rotation is 0, 90, 180 or 270 degrees clockwise; at 0 the item front faces +y (the bottom wall). Call get_room first to learn dimensions, openings and current items.","annotations":{"readOnlyHint":true,"untrustedContentHint":true},"inputSchema":{"type":"object","properties":{}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"get_catalog","kind":"answer","impl":"imperative","description":"List furniture available to place, with dimensions in cm, price in USD, clearance rules, the room kinds each item suits, its alternative `colors` and, for wall-mounted things, the `mountHeight` it hangs at. Filter by category, room kind, maximum footprint, maximum price or a name query. Items added with add_catalog_item are included. Returns at most 60 items, cheapest first; narrow the filters if `truncated` is true.","annotations":{"readOnlyHint":true,"untrustedContentHint":true},"inputSchema":{"type":"object","properties":{"category":{"type":"string","description":"Furniture category","enum":["bed","sofa","armchair","desk","chair","table","wardrobe","shelf","dresser","nightstand","rug","lamp","plant","tv","kitchen","appliance","storage","decor","wall","other"]},"room":{"type":"string","description":"Kind of room the item suits","enum":["living","kitchen","bedroom","hall","office","dining","kids","studio"]},"maxWidth":{"type":"integer","description":"Maximum width (cm)"},"maxDepth":{"type":"integer","description":"Maximum depth (cm)"},"maxPrice":{"type":"number","description":"Maximum price in USD"},"query":{"type":"string","description":"Case-insensitive substring of the name or category"}}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"suggest_positions","kind":"act","impl":"imperative","description":"Best positions for a catalog item given the room, walls, door, window and daylight. Call this before place_item for beds, desks, sofas, wardrobes and shelves. Each suggestion is a valid placement with a reason and a score. Coordinates are integer centimeters. Origin is the top-left corner of the room; x grows to the right, y grows downward. Items are placed by their center. rotation is 0, 90, 180 or 270 degrees clockwise; at 0 the item front faces +y (the bottom wall). Call get_room first to learn dimensions, openings and current items.","annotations":{"readOnlyHint":true},"inputSchema":{"type":"object","properties":{"catalogId":{"type":"string","description":"Catalog id from get_catalog"},"near":{"type":"string","description":"Prefer positions close to this feature","enum":["window","door","corner","any"]},"count":{"type":"integer","description":"How many suggestions to return, default 5","minimum":1,"maximum":10},"hour":{"type":"integer","description":"Hour of day used for the light score, 6 to 20. Defaults to 9","minimum":6,"maximum":20}},"required":["catalogId"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"evaluate_layout","kind":"act","impl":"imperative","description":"Score a candidate set of changes WITHOUT applying them. Returns metrics, violations and the delta versus the current room. Use it to iterate privately before place_item or propose_layout. Coordinates are integer centimeters. Origin is the top-left corner of the room; x grows to the right, y grows downward. Items are placed by their center. rotation is 0, 90, 180 or 270 degrees clockwise; at 0 the item front faces +y (the bottom wall). Call get_room first to learn dimensions, openings and current items.","annotations":{"readOnlyHint":true},"inputSchema":{"type":"object","properties":{"placements":{"type":"array","description":"Changes to apply in order","items":{"type":"object","properties":{"action":{"type":"string","description":"place | move | remove | swap","enum":["place","move","remove","swap"]},"catalogId":{"type":"string","description":"Catalog id (place, swap)"},"id":{"type":"string","description":"Existing item id (move, remove, swap). For place, optional id to assign"},"x":{"type":"integer","description":"Center x (place, move) (cm)"},"y":{"type":"integer","description":"Center y (place, move) (cm)"},"rotation":{"type":"integer","description":"Rotation in degrees clockwise","enum":[0,90,180,270]}},"required":["action"]}}},"required":["placements"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"get_daylight","kind":"answer","impl":"imperative","description":"Approximate daylight at a given hour (6 to 20): light score 0..1 per item and the brightest free spots for morning (09:00) and afternoon (16:00). Use it to place a desk in morning light or a bed away from glare.","annotations":{"readOnlyHint":true},"inputSchema":{"type":"object","properties":{"hour":{"type":"integer","description":"Hour of day, 6 to 20. Defaults to the current slider value","minimum":6,"maximum":20}}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"list_templates","kind":"answer","impl":"imperative","description":"Ready-made rooms you can start from, with their key, dimensions, item count and budget. Call this before load_template to learn the keys.","annotations":{"readOnlyHint":true},"inputSchema":{"type":"object","properties":{}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"suggest_palette","kind":"act","impl":"imperative","description":"Three color schemes for the current room, warm, cool and neutral, derived from what is already in it. Each returns a wall color, a floor finish, three accent tones and the exact set_item_color changes that would carry it out. Use it when the user asks how the room could look, then apply one with set_finish and set_item_color.","annotations":{"readOnlyHint":true},"inputSchema":{"type":"object","properties":{}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"get_ledger","kind":"answer","impl":"imperative","description":"Recent actions by the user and by you, newest last, with the number of violations after each.","annotations":{"readOnlyHint":true},"inputSchema":{"type":"object","properties":{"limit":{"type":"integer","description":"How many entries, default 20","minimum":1,"maximum":200}}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"suggest_furniture","kind":"act","impl":"imperative","description":"Turn the brief into a priced shopping list that fits the room and the budget: which catalog items cover each need (sleep, work, host, storage, dine, read, kids, kitchen, hall), the total, what remains, needs that could not be met, and cheaper or pricier alternatives per pick. Call it before propose_layout so the budget shapes the layout. Defaults to the room brief; pass budget or needs to explore.","annotations":{"readOnlyHint":true},"inputSchema":{"type":"object","properties":{"budget":{"type":"number","description":"Budget in USD; defaults to the brief","minimum":0},"needs":{"type":"array","description":"Needs to cover; defaults to the brief","items":{"type":"string","description":"Need, e.g. \"work from home\""}}}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"list_rooms","kind":"answer","impl":"imperative","description":"Every room in this session, with its id, dimensions in cm and item count. The one flagged `current: true` is what get_room and every editing tool acts on. Use it before switch_room, delete_room or when the user names a room you have not seen.","annotations":{"readOnlyHint":true},"inputSchema":{"type":"object","properties":{}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"get_guide","kind":"answer","impl":"imperative","description":"How to design a room with these tools: the recommended order of calls, the coordinate conventions, and the habits that avoid rework. Call it once at the start of a design task.","annotations":{"readOnlyHint":true},"inputSchema":{"type":"object","properties":{}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"set_room_shell","kind":"act","impl":"imperative","description":"Set the room dimensions in cm and optionally which wall faces north. Existing items are kept and re-validated.","inputSchema":{"type":"object","properties":{"width":{"type":"integer","description":"Room width (x) (cm)"},"depth":{"type":"integer","description":"Room depth (y) (cm)"},"height":{"type":"integer","description":"Ceiling height (cm)"},"northWall":{"type":"string","description":"Which wall, named by where it sits on the plan: top, right, bottom or left. These are positions on the drawing the user is looking at, not compass directions — say \"the top wall\" back to them, not \"the north wall\"","enum":["top","right","bottom","left"]}},"required":["width","depth","height"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"add_opening","kind":"act","impl":"imperative","description":"Add a door or window to a wall. offset is measured from the left end of the top/bottom walls or the top end of the left/right walls. Doors default to 200 cm high swinging inward; windows default to 120 cm high with a 90 cm sill.","inputSchema":{"type":"object","properties":{"kind":{"type":"string","description":"door or window","enum":["door","window"]},"wall":{"type":"string","description":"Which wall, named by where it sits on the plan: top, right, bottom or left. These are positions on the drawing the user is looking at, not compass directions — say \"the top wall\" back to them, not \"the north wall\"","enum":["top","right","bottom","left"]},"offset":{"type":"integer","description":"Distance along the wall (cm)"},"width":{"type":"integer","description":"Opening width (cm)"},"height":{"type":"integer","description":"Opening height (cm)"},"sill":{"type":"integer","description":"Window sill height (cm)"},"swing":{"type":"string","description":"Door swing","enum":["in","out"]},"hinge":{"type":"string","description":"Hinge side","enum":["start","end"]}},"required":["kind","wall","offset","width"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"remove_opening","kind":"act","impl":"imperative","description":"Remove a door or window by id (ids come from get_room).","inputSchema":{"type":"object","properties":{"id":{"type":"string","description":"Opening id"}},"required":["id"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"move_opening","kind":"act","impl":"imperative","description":"Move a door or window along its wall, or to another wall, by id (ids come from get_room). offset is measured from the left end of the top/bottom walls or the top end of the left/right walls. It must stay within the wall and clear of other openings. A door that is a doorway between two rooms cannot be moved this way: remove_doorway and cut_doorway instead.","inputSchema":{"type":"object","properties":{"id":{"type":"string","description":"Opening id"},"offset":{"type":"integer","description":"New distance along the wall (cm)"},"wall":{"type":"string","description":"Wall to carry it to; defaults to the wall it is on","enum":["top","right","bottom","left"]}},"required":["id","offset"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"set_brief","kind":"act","impl":"imperative","description":"Update the design brief: budget in USD, needs (short phrases) and free-text notes. Omitted fields are kept.","inputSchema":{"type":"object","properties":{"budget":{"type":"number","description":"Budget in USD","minimum":0},"needs":{"type":"array","description":"What the room must support","items":{"type":"string","description":"Need"}},"notes":{"type":"string","description":"Free text"}}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"place_item","kind":"act","impl":"imperative","description":"Place one catalog item by its center. Applies immediately; undo_last_action takes it back. and reports any violations plus the nearest clear position. Prefer suggest_positions for beds, desks, sofas, wardrobes and shelves. Positions within 15 cm of a wall are snapped flush and wall furniture is turned to face the room; the result reports snapped: true. Coordinates are integer centimeters. Origin is the top-left corner of the room; x grows to the right, y grows downward. Items are placed by their center. rotation is 0, 90, 180 or 270 degrees clockwise; at 0 the item front faces +y (the bottom wall). Call get_room first to learn dimensions, openings and current items.","inputSchema":{"type":"object","properties":{"catalogId":{"type":"string","description":"Catalog id from get_catalog"},"x":{"type":"integer","description":"Center x (cm)"},"y":{"type":"integer","description":"Center y (cm)"},"rotation":{"type":"integer","description":"Rotation in degrees clockwise","enum":[0,90,180,270]}},"required":["catalogId","x","y"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"move_item","kind":"act","impl":"imperative","description":"Move an existing item to a new center, optionally rotating it. Locked items cannot be moved. Prefer suggest_positions for beds, desks, sofas, wardrobes and shelves. Positions within 15 cm of a wall are snapped flush and wall furniture is turned to face the room; the result reports snapped: true. Coordinates are integer centimeters. Origin is the top-left corner of the room; x grows to the right, y grows downward. Items are placed by their center. rotation is 0, 90, 180 or 270 degrees clockwise; at 0 the item front faces +y (the bottom wall). Call get_room first to learn dimensions, openings and current items.","inputSchema":{"type":"object","properties":{"id":{"type":"string","description":"Item id"},"x":{"type":"integer","description":"Center x (cm)"},"y":{"type":"integer","description":"Center y (cm)"},"rotation":{"type":"integer","description":"Rotation in degrees clockwise","enum":[0,90,180,270]}},"required":["id","x","y"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"rotate_item","kind":"act","impl":"imperative","description":"Rotate an existing item in place to 0, 90, 180 or 270 degrees clockwise. At 0 the front faces the bottom wall.","inputSchema":{"type":"object","properties":{"id":{"type":"string","description":"Item id"},"rotation":{"type":"integer","description":"Rotation in degrees clockwise","enum":[0,90,180,270]}},"required":["id","rotation"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"fix_item","kind":"act","impl":"imperative","description":"Move one item to the nearest position that clears its blocking violations, keeping its rotation. Use it when place_item or move_item reports an overlap, a blocked door or window, or too little clearance.","inputSchema":{"type":"object","properties":{"id":{"type":"string","description":"Item id"}},"required":["id"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"remove_item","kind":"act","impl":"imperative","description":"Remove an item from the room. Locked items cannot be removed.","inputSchema":{"type":"object","properties":{"id":{"type":"string","description":"Item id"}},"required":["id"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"clear_items","kind":"act","impl":"imperative","description":"Empty the room in one ledger entry, so a single undo puts everything back. Locked items are left where they are; the result lists what remains. Ask the user before calling it on a room they have worked on.","inputSchema":{"type":"object","properties":{}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"swap_item","kind":"act","impl":"imperative","description":"Replace an item with a different catalog item, keeping its position and rotation. Useful for cheaper or smaller alternatives.","inputSchema":{"type":"object","properties":{"id":{"type":"string","description":"Item id"},"catalogId":{"type":"string","description":"Replacement catalog id"}},"required":["id","catalogId"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"set_item_locked","kind":"act","impl":"imperative","description":"Lock an item so nobody can move, swap or remove it (for example when the user says \"keep the sofa\"), or unlock it.","inputSchema":{"type":"object","properties":{"id":{"type":"string","description":"Item id"},"locked":{"type":"boolean","description":"true to lock"}},"required":["id","locked"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"add_catalog_item","kind":"act","impl":"imperative","description":"Add a product you researched to the catalog so it can be placed: name, category, dimensions in cm, price in USD, optional color and URL. Clearance rules are inferred from the category.","inputSchema":{"type":"object","properties":{"name":{"type":"string","description":"Product name"},"category":{"type":"string","description":"Furniture category","enum":["bed","sofa","armchair","desk","chair","table","wardrobe","shelf","dresser","nightstand","rug","lamp","plant","tv","kitchen","appliance","storage","decor","wall","other"]},"width":{"type":"integer","description":"Width (cm)"},"depth":{"type":"integer","description":"Depth (cm)"},"height":{"type":"integer","description":"Height (cm)"},"price":{"type":"number","description":"Price in USD","minimum":0},"color":{"type":"string","description":"Hex color like #aabbcc"},"url":{"type":"string","description":"Product page URL"}},"required":["name","category","width","depth","height","price"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"load_template","kind":"act","impl":"imperative","description":"Start a new room from a ready-made layout; use list_templates for keys. The room is created, furnished, switched to and returned. It never replaces the current room, and it applies straight away rather than becoming a proposal. Coordinates are integer centimeters. Origin is the top-left corner of the room; x grows to the right, y grows downward. Items are placed by their center. rotation is 0, 90, 180 or 270 degrees clockwise; at 0 the item front faces +y (the bottom wall). Call get_room first to learn dimensions, openings and current items.","inputSchema":{"type":"object","properties":{"key":{"type":"string","description":"Template key from list_templates","enum":["living","kitchen","bedroom","hall","office","dining","kids","studio"]},"name":{"type":"string","description":"Name for the new room, defaults to the template name"}},"required":["key"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"create_room","kind":"act","impl":"imperative","description":"Create a new empty room and switch to it. The current room is kept and can be returned to with switch_room. Sides must be between 100 and 3000 cm. Creating a room writes no ledger entry, so the result reports status \"applied\" with the new room's own violations and metrics. Use load_template instead to start from a furnished layout.","inputSchema":{"type":"object","properties":{"name":{"type":"string","description":"Room name"},"width":{"type":"integer","description":"Room width (x) (cm)"},"depth":{"type":"integer","description":"Room depth (y) (cm)"},"height":{"type":"integer","description":"Ceiling height (cm)"},"northWall":{"type":"string","description":"Which wall, named by where it sits on the plan: top, right, bottom or left. These are positions on the drawing the user is looking at, not compass directions — say \"the top wall\" back to them, not \"the north wall\"","enum":["top","right","bottom","left"]}},"required":["name","width","depth","height"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"switch_room","kind":"act","impl":"imperative","description":"Make another room the current one, so get_room and every editing tool acts on it. Ids come from list_rooms. Nothing is lost: the room you leave keeps its items and its ledger. Writes no ledger entry, so the result reports status \"applied\" with the arriving room's violations and metrics.","inputSchema":{"type":"object","properties":{"id":{"type":"string","description":"Room id from list_rooms"}},"required":["id"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"rename_room","kind":"act","impl":"imperative","description":"Rename the current room. Writes no ledger entry, so the result reports status \"applied\" with the room's violations and metrics as they stand.","inputSchema":{"type":"object","properties":{"name":{"type":"string","description":"New room name"}},"required":["name"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"delete_room","kind":"transact","impl":"imperative","description":"Delete a room and everything in it, including its ledger. This cannot be undone, so confirm with the user first. The last remaining room cannot be deleted. Writes no ledger entry, so the result reports status \"applied\" with the room that is current afterwards.","inputSchema":{"type":"object","properties":{"id":{"type":"string","description":"Room id from list_rooms"}},"required":["id"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"set_item_color","kind":"act","impl":"imperative","description":"Repaint one placed item in one of the alternative finishes get_catalog lists under `colors`, or pass null (or omit color) to put it back to its catalog color. Use it to carry out a scheme from suggest_palette.","inputSchema":{"type":"object","properties":{"id":{"type":"string","description":"Item id"},"color":{"type":"string","description":"New color as hex, like #aabbcc","nullable":true}},"required":["id"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"set_finish","kind":"act","impl":"imperative","description":"Paint the walls and lay the floor: wall takes a hex color, floor one of oak, walnut, ash, grey or tile. Omitted fields keep what the room has. Applies straight away in both views.","inputSchema":{"type":"object","properties":{"wall":{"type":"string","description":"Wall color as hex, like #aabbcc"},"floor":{"type":"string","description":"Floor material","enum":["oak","walnut","ash","grey","tile"]}}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"apply_palette","kind":"act","impl":"imperative","description":"Carry out one of the schemes suggest_palette offers, in one ledger entry: the wall color, the floor finish and every item recolor together. Call suggest_palette first if the user should see the three options before you pick. Applies straight away rather than becoming a proposal, because color is easy to change back.","inputSchema":{"type":"object","properties":{"name":{"type":"string","description":"Which scheme from suggest_palette","enum":["warm","cool","neutral"]}},"required":["name"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"propose_layout","kind":"act","impl":"imperative","description":"Propose a batch of changes as ghosts on the plan with a label, without applying them. The user sees a card with the metrics delta and can accept or reject. Call it several times with different labels to offer layout variants. Coordinates are integer centimeters. Origin is the top-left corner of the room; x grows to the right, y grows downward. Items are placed by their center. rotation is 0, 90, 180 or 270 degrees clockwise; at 0 the item front faces +y (the bottom wall). Call get_room first to learn dimensions, openings and current items.","inputSchema":{"type":"object","properties":{"label":{"type":"string","description":"Short name for this option, e.g. \"Bed by the window\""},"placements":{"type":"array","description":"Changes to apply in order","items":{"type":"object","properties":{"action":{"type":"string","description":"place | move | remove | swap","enum":["place","move","remove","swap"]},"catalogId":{"type":"string","description":"Catalog id (place, swap)"},"id":{"type":"string","description":"Existing item id (move, remove, swap). For place, optional id to assign"},"x":{"type":"integer","description":"Center x (place, move) (cm)"},"y":{"type":"integer","description":"Center y (place, move) (cm)"},"rotation":{"type":"integer","description":"Rotation in degrees clockwise","enum":[0,90,180,270]}},"required":["action"]}}},"required":["label","placements"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"apply_layout","kind":"act","impl":"imperative","description":"Apply a whole layout at once: places, moves, removes and swaps in the order given, as a single ledger entry, so one undo takes the whole idea back. Use propose_layout instead when the user should choose between options, and evaluate_layout first to check the result before anyone sees it. Coordinates are integer centimeters. Origin is the top-left corner of the room; x grows to the right, y grows downward. Items are placed by their center. rotation is 0, 90, 180 or 270 degrees clockwise; at 0 the item front faces +y (the bottom wall). Call get_room first to learn dimensions, openings and current items.","inputSchema":{"type":"object","properties":{"placements":{"type":"array","description":"Changes to apply in order","items":{"type":"object","properties":{"action":{"type":"string","description":"place | move | remove | swap","enum":["place","move","remove","swap"]},"catalogId":{"type":"string","description":"Catalog id (place, swap)"},"id":{"type":"string","description":"Existing item id (move, remove, swap). For place, optional id to assign"},"x":{"type":"integer","description":"Center x (place, move) (cm)"},"y":{"type":"integer","description":"Center y (place, move) (cm)"},"rotation":{"type":"integer","description":"Rotation in degrees clockwise","enum":[0,90,180,270]}},"required":["action"]}}},"required":["placements"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"set_daylight_hour","kind":"act","impl":"imperative","description":"Move the daylight time slider (6 to 20) so the user sees light at that hour in both views. Returns light per item.","inputSchema":{"type":"object","properties":{"hour":{"type":"integer","description":"Hour of day","minimum":6,"maximum":20}},"required":["hour"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"set_camera","kind":"act","impl":"imperative","description":"Move the 3D camera. Use a preset (overview, from_door, at_desk, on_bed, at_window) or give x, y and yaw (degrees clockwise, 0 faces the top wall) for a walking viewpoint at eye height. Returns the items visible from there, nearest first.","inputSchema":{"type":"object","properties":{"preset":{"type":"string","description":"Named viewpoint","enum":["overview","from_door","at_desk","on_bed","at_window"]},"x":{"type":"integer","description":"Camera x (cm)"},"y":{"type":"integer","description":"Camera y (cm)"},"yaw":{"type":"integer","description":"Yaw in degrees","minimum":0,"maximum":359}}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"undo_last_action","kind":"act","impl":"imperative","description":"Revert the most recent action in the ledger (yours or the user's). The revert is itself recorded.","inputSchema":{"type":"object","properties":{}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"revert_to_entry","kind":"act","impl":"imperative","description":"Rewind the room to how it stood just after one ledger entry, undoing everything after it in one go. Ids come from get_ledger. The rewind is itself recorded, so it can be undone in turn.","inputSchema":{"type":"object","properties":{"ledgerId":{"type":"string","description":"Ledger entry id from get_ledger"}},"required":["ledgerId"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"select_item","kind":"act","impl":"imperative","description":"Select an item so the user sees it highlighted on the plan and in 3D with its properties open, or pass null to clear the selection. Use it to point at what you are talking about. Selecting also turns on the selection-scoped tools (move_selected, replace_selected, remove_selected, find_alternatives_for_selected). Writes no ledger entry, so the result reports status \"applied\" with the room's violations and metrics as they stand.","inputSchema":{"type":"object","properties":{"id":{"type":"string","description":"Item id, or null to clear the selection","nullable":true}}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"set_view","kind":"act","impl":"imperative","description":"Turn the daylight tint and the 3D shadows on or off. Switch the tint off when the user wants to read the plan itself, and the shadows off when the 3D view is slow. Omitted fields keep what is set. Writes no ledger entry, so the result reports status \"applied\" with the room's violations and metrics as they stand.","inputSchema":{"type":"object","properties":{"showDaylight":{"type":"boolean","description":"Draw the daylight tint over the plan"},"showShadows":{"type":"boolean","description":"Cast and catch shadows in the 3D view"}}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"get_elevation","kind":"answer","impl":"imperative","description":"One wall seen straight on: its paint, its length and height, the doors and windows in it, everything hanging on it with its offset and mount height, and the floor-standing furniture within 100 cm of it. Walls are named by where they sit on the plan — top, right, bottom, left — and that is how the user sees them on screen, so name them that way in your reply. `facing` gives the compass direction if you need it, but it moves whenever north moves and it is not what the user is looking at. `describe` is the label the app itself puts on this wall. Read this before hanging anything, so a picture goes above the sofa rather than through it. offset is the distance along the wall to the near edge of the thing, measured from the left end of the top and bottom walls and from the top end of the left and right walls — the same ruler add_opening uses.","annotations":{"readOnlyHint":true,"untrustedContentHint":true},"inputSchema":{"type":"object","properties":{"wall":{"type":"string","description":"Which wall, named by where it sits on the plan: top, right, bottom or left. These are positions on the drawing the user is looking at, not compass directions — say \"the top wall\" back to them, not \"the north wall\"","enum":["top","right","bottom","left"]}},"required":["wall"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"list_wall_palettes","kind":"answer","impl":"imperative","description":"Regional wall colors: eleven regions, six named paints each, every one chosen to read as a whole wall. Use it to answer \"paint this room like a Kyoto tea house\" with a real color rather than an invented hex, then pass the hex to set_wall_color.","annotations":{"readOnlyHint":true},"inputSchema":{"type":"object","properties":{}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"get_style","kind":"answer","impl":"imperative","description":"Every finish in the room at once: the room default and any per-wall overrides, what each of the four walls actually resolves to and which named paint that is, the floor materials on offer, and the eleven regional palettes. Read it before repainting, so \"make the other walls match\" and \"what colour is this\" have an answer.","annotations":{"readOnlyHint":true},"inputSchema":{"type":"object","properties":{}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"set_wall_color","kind":"act","impl":"imperative","description":"Paint the walls. Walls are named by where they sit on the plan — top, right, bottom, left — which is how the user sees them on screen, so ask for and confirm \"the top wall\", never \"the north wall\". With a wall named, only that wall changes and the other three keep whatever they had. Without one, every wall goes back to a single color and any per-wall overrides are cleared. Name the paint with swatch, as \"Region/Name\" from list_wall_palettes — \"Japan/Aizome indigo\" — or pass any hex as color.","inputSchema":{"type":"object","properties":{"wall":{"type":"string","description":"Which wall, named by where it sits on the plan: top, right, bottom or left. These are positions on the drawing the user is looking at, not compass directions — say \"the top wall\" back to them, not \"the north wall\"","enum":["top","right","bottom","left"]},"color":{"type":"string","description":"Wall paint as hex, like #aabbcc"},"swatch":{"type":"string","description":"A named paint as \"Region/Name\", from list_wall_palettes or get_style. An alternative to color"}}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"place_on_wall","kind":"act","impl":"imperative","description":"Hang a wall-mounted item — a print, a mirror, a wall shelf, hooks, a clock, curtains — flush on a wall at a given offset. Only items in the catalog's `wall` category can be hung; everything else stands on the floor and belongs to place_item. The x and y are worked out for you. offset is the distance along the wall to the near edge of the thing, measured from the left end of the top and bottom walls and from the top end of the left and right walls — the same ruler add_opening uses.","inputSchema":{"type":"object","properties":{"catalogId":{"type":"string","description":"Catalog id of a wall-category item, from get_catalog"},"wall":{"type":"string","description":"Which wall, named by where it sits on the plan: top, right, bottom or left. These are positions on the drawing the user is looking at, not compass directions — say \"the top wall\" back to them, not \"the north wall\"","enum":["top","right","bottom","left"]},"offset":{"type":"integer","description":"Distance along the wall to the item near edge (cm)"},"mountHeight":{"type":"integer","description":"Height from the floor to the bottom of the item. Defaults to the catalog mount height (cm)"}},"required":["catalogId","wall","offset"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"get_shopping_list","kind":"answer","impl":"imperative","description":"Everything placed in the room, grouped by catalog id into the list you would take shopping: quantity, unit price, line total, whether it is still to buy, and where it is coming from when someone has said. Sourcing is your job, not the user's: for every line without a source, search for the piece — online or near where the user lives, asking for their city if you need it — and record the shop and the listing with set_purchase_status, passing the catalogId. unsourced counts the lines still waiting for that. Every line carries a searchQuery you can hand straight to a shop or a search, and the room brief is included so you can respect the budget and the notes. status is one of to-buy, owned, ordered. Absent means to-buy: everything in a freshly drawn room is still to buy.","annotations":{"readOnlyHint":true},"inputSchema":{"type":"object","properties":{}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"set_purchase_status","kind":"answer","impl":"imperative","description":"Record where a piece is coming from and whether it still needs buying. Pass catalogId to mark every copy of that piece at once — the usual case, since two identical chairs are bought together — or id to mark one placement. source is the shop, url the listing; both are optional and both are kept when you only change the status. status is one of to-buy, owned, ordered. Absent means to-buy: everything in a freshly drawn room is still to buy.","inputSchema":{"type":"object","properties":{"catalogId":{"type":"string","description":"Catalog id, from get_shopping_list. Marks every placement of that piece"},"id":{"type":"string","description":"A single placed item id, when only one of several copies is meant"},"status":{"type":"string","description":"Where this piece stands","enum":["to-buy","owned","ordered"]},"source":{"type":"string","description":"The shop it is coming from, as a person would say it: \"IKEA Kungens Kurva\""},"url":{"type":"string","description":"A link to the listing"}},"required":["status"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"get_home","kind":"answer","impl":"imperative","description":"The floor plan the current room stands on: every room with its offset and size, the doorways between them, the bounding box, which room the front door is in, any room you cannot walk to from it, and totals for area, items and budget. Read it before moving a room or cutting a doorway, so the offsets you pass are relative to the same plan. `home` comes back null when the current room stands on no plan. Offsets are integer centimetres on the shared plan: x and y are the room's top-left corner, x growing right and y growing down. Rooms must touch edge to edge without overlapping — a placement within 20 cm of a neighbour's edge is pulled flush to it, which is how two rooms come to share a wall.","annotations":{"readOnlyHint":true},"inputSchema":{"type":"object","properties":{}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"list_home_templates","kind":"answer","impl":"imperative","description":"The ready-made homes create_home can build: a key, a name, a blurb, the rooms with their offsets and sizes on the shared plan, and how many doorways come already cut between them. Rooms come furnished from the room templates, so a template home is a flat you can walk through the moment it is built.","annotations":{"readOnlyHint":true},"inputSchema":{"type":"object","properties":{}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"create_home","kind":"act","impl":"imperative","description":"Start a floor plan. With a template key from list_home_templates the rooms are built furnished, stood on the plan and joined by their doorways, and the app switches to the room the front door is in. Without one you get an empty plan to put rooms on with add_room_to_home. Existing rooms are never disturbed: a room joins a home only when you add it.","inputSchema":{"type":"object","properties":{"name":{"type":"string","description":"Name for the home, as a person would say it: \"Flat 3\""},"template":{"type":"string","description":"Ready-made home key from list_home_templates","enum":["one-bedroom","studio-hall"]}}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"add_room_to_home","kind":"act","impl":"imperative","description":"Stand a room on the floor plan. Pass roomId for a room that already exists, from list_rooms, or templateKey to build a furnished one from a room template first. The room goes on the plan the current room stands on; when it stands on none, a plan named after it is started and it is placed at the origin, so \"put a kitchen to the right of this room\" works from a single room. Offsets are integer centimetres on the shared plan: x and y are the room's top-left corner, x growing right and y growing down. Rooms must touch edge to edge without overlapping — a placement within 20 cm of a neighbour's edge is pulled flush to it, which is how two rooms come to share a wall. An overlap is refused with the room it ran into named and an offset that would work.","inputSchema":{"type":"object","properties":{"roomId":{"type":"string","description":"An existing room to stand on the plan, from list_rooms"},"templateKey":{"type":"string","description":"Build a new furnished room from this room template instead of using roomId; keys from list_templates","enum":["living","kitchen","bedroom","hall","office","dining","kids","studio"]},"x":{"type":"integer","description":"The room's left edge on the plan (cm)"},"y":{"type":"integer","description":"The room's top edge on the plan (cm)"},"name":{"type":"string","description":"Name for the new room, when templateKey is used. Defaults to the template name"}},"required":["x","y"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"move_room","kind":"act","impl":"imperative","description":"Move a room to another offset on the floor plan it already stands on. Offsets are integer centimetres on the shared plan: x and y are the room's top-left corner, x growing right and y growing down. Rooms must touch edge to edge without overlapping — a placement within 20 cm of a neighbour's edge is pulled flush to it, which is how two rooms come to share a wall. An overlap is refused with the room it ran into named and an offset that would work. Doorways keep the offsets they were cut at, so any doorway the move pulls out of line is taken out of both rooms and named in removedDoorways; cut_doorway puts one back at the new offsets.","inputSchema":{"type":"object","properties":{"roomId":{"type":"string","description":"Room id, from get_home or list_rooms"},"x":{"type":"integer","description":"The room's left edge on the plan (cm)"},"y":{"type":"integer","description":"The room's top edge on the plan (cm)"}},"required":["roomId","x","y"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"remove_room_from_home","kind":"act","impl":"imperative","description":"Take a room off the floor plan. The room itself survives with everything in it and can be worked on alone or added to another plan; only its place on this one goes. Its doorways go with it, which removes the matching opening from the rooms on the other side, so each of those rooms gets a ledger entry.","inputSchema":{"type":"object","properties":{"roomId":{"type":"string","description":"Room id, from get_home or list_rooms"}},"required":["roomId"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"cut_doorway","kind":"act","impl":"imperative","description":"Open a doorway through the wall two rooms share, so you can walk from one into the other. A doorway needs a wall the two rooms actually share, and it is one hole in both of them: cutting it writes an opening and a ledger entry in each room, and remove_doorway takes both away again. offset is measured inside the room you name — from the left end of its top and bottom walls, from the top end of its left and right walls, the same ruler add_opening uses. Width defaults to 80 cm and kind to a door, which hangs a leaf swinging into the room you name; a passage is the same hole with no leaf. Name otherRoomId when more than one room lies behind that wall. A refusal lists the shared walls you can actually cut into, with the span of each in that room's own coordinates.","inputSchema":{"type":"object","properties":{"roomId":{"type":"string","description":"The room to cut from, from get_home. The door swings into this one"},"wall":{"type":"string","description":"Which wall, named by where it sits on the plan: top, right, bottom or left. These are positions on the drawing the user is looking at, not compass directions — say \"the top wall\" back to them, not \"the north wall\"","enum":["top","right","bottom","left"]},"offset":{"type":"integer","description":"Distance along the wall to the near edge of the doorway (cm)"},"width":{"type":"integer","description":"Doorway width, default 80 (cm)"},"kind":{"type":"string","description":"door hangs a leaf that swings into roomId; passage is an open hole","enum":["door","passage"]},"otherRoomId":{"type":"string","description":"Which neighbour to open into, when more than one room lies behind that wall"}},"required":["roomId","wall","offset"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"remove_doorway","kind":"act","impl":"imperative","description":"Close a doorway again, taking the opening out of both rooms at once and writing a ledger entry in each. Ids come from get_home. Undoing only one room's entry would leave a door onto a blank wall next door, so use this rather than undo when you want the wall back.","inputSchema":{"type":"object","properties":{"id":{"type":"string","description":"Doorway id, from get_home"}},"required":["id"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"apply_proposal","kind":"act","impl":"imperative","description":"Apply one open proposal by id, applying its ghosted changes to the room. Only call this when the user has explicitly chosen that option. Open proposals and their ids are listed by get_room.","inputSchema":{"type":"object","properties":{"proposalId":{"type":"string","description":"Proposal id"}},"required":["proposalId"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"withdraw_proposal","kind":"act","impl":"imperative","description":"Withdraw one of your open proposals by id.","inputSchema":{"type":"object","properties":{"proposalId":{"type":"string","description":"Proposal id"}},"required":["proposalId"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"apply_all_proposals","kind":"act","impl":"imperative","description":"Apply every open proposal atomically, in order, as one ledger entry. Only when the user asked for all of them.","inputSchema":{"type":"object","properties":{}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"},{"name":"run_layout_script","kind":"act","impl":"imperative","description":"Run a JavaScript function body you write, in a sandboxed worker with no DOM access, to search for a layout programmatically. The body receives `api` with: api.room (shell, openings, items, brief), api.catalog (array of items with dimensions and prices), api.evaluate(placements) → { metrics, violations }, api.nearestValid(catalogId, x, y, rotation) → {x,y} | null, api.bestSpots(hour, count). It must `return` an array of placements ({ action: \"place\"|\"move\"|\"remove\"|\"swap\", catalogId, id, x, y, rotation }). The result becomes a labeled proposal for the user to accept. 2 second time limit.","inputSchema":{"type":"object","properties":{"label":{"type":"string","description":"Name for the resulting proposal"},"code":{"type":"string","description":"JavaScript function body"}},"required":["label","code"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute"}],"category":"AI & Agents","apiSurface":"spec","toolCount":60}}