### Install Project Dependencies Source: https://github.com/pablodelucca/pixel-agents/blob/main/CONTRIBUTING.md Clone the repository, install dependencies for the root, webview, and server, then build the project. ```bash git clone https://github.com/pablodelucca/pixel-agents.git cd pixel-agents npm install cd webview-ui && npm install && cd .. cd server && npm install && cd .. npm run build ``` -------------------------------- ### Install Dependencies and Build Project Source: https://github.com/pablodelucca/pixel-agents/blob/main/CLAUDE.md Run this command to install all necessary npm dependencies for the project and its sub-modules, then build the extension. ```sh npm install && cd webview-ui && npm install && cd ../server && npm install && cd .. && npm run build ``` -------------------------------- ### Install Pixel Agents from Source Source: https://github.com/pablodelucca/pixel-agents/blob/main/README.md Follow these steps to clone the repository, install dependencies, build the project, and launch the extension in VS Code for development. ```bash git clone https://github.com/pablodelucca/pixel-agents.git cd pixel-agents npm install cd webview-ui && npm install && cd .. npm run build ``` -------------------------------- ### Start and Stop PixelAgentsServer Source: https://context7.com/pablodelucca/pixel-agents/llms.txt Initializes and starts the PixelAgentsServer to listen for hook events. Writes discovery configuration to ~/.pixel-agents/server.json. Stops the server and removes the configuration file if owned. ```typescript const server = new PixelAgentsServer(); // Register callback for incoming hook events server.onHookEvent((providerId, event) => { // providerId: 'claude' // event: { hook_event_name: 'PreToolUse', session_id: 'uuid', tool_name: 'Read', tool_input: {...} } hookEventHandler.handleEvent(providerId, event); }); await server.start(); // Writes: ~/.pixel-agents/server.json // { // "port": 54321, // "pid": 12345, // "token": "550e8400-e29b-41d4-a716-446655440000", // "startedAt": 1700000000000 // } // Server routes: // GET /api/health → { status: 'ok', uptime: 42, pid: 12345 } // POST /api/hooks/claude → receives hook event JSON (auth required) // Header: Authorization: Bearer // Body: { "hook_event_name": "Stop", "session_id": "uuid-here" } server.stop(); // removes server.json if we own it ``` -------------------------------- ### Run Mocked Pixel Agent Dev Server Source: https://github.com/pablodelucca/pixel-agents/blob/main/CONTRIBUTING.md Navigate to the webview-ui directory and run this command to start the mocked Pixel Agent web app using Vite. ```bash cd webview-ui npm run dev ``` -------------------------------- ### Auto-load from Local Storage on Window Load Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/asset-manager.html Listens for the window load event to restore project data from localStorage. If no data is found, it provides instructions for starting a new project. ```javascript window.addEventListener('load', () => { updateTilesetVisibility() try { const saved = localStorage.getItem('asset-manager-data') if (saved) { loadJsonData(JSON.parse(saved)) statusMsg.textContent = 'Restored from localStorage (' + data.assets.length + ' assets)' } else { statusMsg.textContent = 'Click "New" to start a new project, or "Load JSON" to open an existing one' } } catch {} }) ``` -------------------------------- ### Setup JSON Tree Toggles Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/jsonl-viewer.html Initializes click event listeners for elements with the class 'json-toggle' to expand/collapse JSON tree nodes. ```javascript function setupToggles() { detailContent.querySelectorAll('.json-toggle').forEach(t => { t.addEventListener('click', () => { const el = document.getElementById(t.dataset.target); if (el) { const c = el.classList.toggle('json-collapsed'); t.textContent = c ? '▶' : '▼'; } }); }); } ``` -------------------------------- ### Initialize UI Elements and Event Listeners Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/asset-manager.html Gets references to DOM elements used for status messages, asset lists, and the preview canvas. Sets up event listeners for UI interactions. ```javascript const statusMsg = document.getElementById('status-msg') const statusCounts = document.getElementById('status-counts') const assetListEl = document.getElementById('asset-list') const previewCanvas = document.getElementById('preview-canvas') const previewCtx = previewCanvas.getContext('2d') ``` -------------------------------- ### Enable React-Specific ESLint Rules Source: https://github.com/pablodelucca/pixel-agents/blob/main/webview-ui/README.md Integrate eslint-plugin-react-x and eslint-plugin-react-dom for enhanced React and React DOM linting. This requires installing these plugins. ```javascript // eslint.config.js import reactX from 'eslint-plugin-react-x' import reactDom from 'eslint-plugin-react-dom' export default defineConfig([ globalIgnores(['dist']), { files: ['**/*.{ts,tsx}'] extends: [ // Other configs... // Enable lint rules for React reactX.configs['recommended-typescript'], // Enable lint rules for React DOM reactDom.configs.recommended, ], languageOptions: { parserOptions: { project: ['./tsconfig.node.json', './tsconfig.app.json'], tsconfigRootDir: import.meta.dirname, }, // other options... }, }, ]) ``` -------------------------------- ### Initialize and Run Game Loop Source: https://context7.com/pablodelucca/pixel-agents/llms.txt Starts the game loop using requestAnimationFrame with a capped delta time. The loop drives character state, pathfinding, animations, and effects. Call the returned function to stop the loop. ```typescript import { startGameLoop } from './engine/gameLoop.js'; const stopLoop = startGameLoop(canvasElement, { update(dt: number) { // dt = seconds since last frame, capped at MAX_DELTA_TIME_SEC (0.1s) // Update character positions, animation frames, wander timers, // matrix effect timers, speech bubble countdowns updateOfficeState(dt, officeState); }, render(ctx: CanvasRenderingContext2D) { // ctx.imageSmoothingEnabled is always false (pixel-perfect rendering) // Draws: floor tiles, wall tiles, furniture (depth-sorted), characters, // tool activity labels, speech bubbles, matrix effects renderOffice(ctx, officeState); }, }); // Stop when component unmounts stopLoop(); ``` -------------------------------- ### Install and Uninstall Claude Hooks Source: https://context7.com/pablodelucca/pixel-agents/llms.txt Manages Pixel Agents hook entries in ~/.claude/settings.json. Installs hooks to trigger for various Claude events and can check if they are installed. Uninstalls all Pixel Agents entries. ```typescript import { installHooks, uninstallHooks, areHooksInstalled } from './claudeHookInstaller.js'; import { copyHookScript } from '../index.js'; // Copy the bundled hook script to ~/.pixel-agents/hooks/claude-hook.js copyHookScript('/path/to/extension'); // Add entries to ~/.claude/settings.json (idempotent) installHooks(); // Adds to each hook event key: // { "matcher": "", "hooks": [{ "type": "command", "command": "node \"~/.pixel-agents/hooks/claude-hook.js\"", "timeout": 5 }] // Check if hooks are present in settings.json const installed = areHooksInstalled(); // → true // Remove all Pixel Agents entries from settings.json uninstallHooks(); ``` -------------------------------- ### Claude Hook Installer API Source: https://context7.com/pablodelucca/pixel-agents/llms.txt Functions to manage the installation and uninstallation of Pixel Agents hooks in Claude's settings. ```APIDOC ## Claude Hook Installer ### Description Manages Pixel Agents hook entries in `~/.claude/settings.json`. Hooks trigger for various Claude events. ### Functions #### `installHooks()` ##### Description Adds Pixel Agents hook entries to `~/.claude/settings.json`. This operation is idempotent. ##### Details Adds entries for the following hook event types: `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `Stop`, `PermissionRequest`, `Notification`, `SessionStart`, `SessionEnd`, `SubagentStart`, `SubagentStop`, `UserPromptSubmit`, and Agent Teams events. Each entry typically includes a `command` to execute the Pixel Agents hook script and a `timeout`. #### `uninstallHooks()` ##### Description Removes all Pixel Agents hook entries from `~/.claude/settings.json`. #### `areHooksInstalled()` ##### Description Checks if Pixel Agents hooks are currently installed in `~/.claude/settings.json`. ##### Returns - `boolean`: `true` if hooks are installed, `false` otherwise. ``` -------------------------------- ### Initialize Live Preview Canvas Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/wall-tile-editor.html Sets up the canvas for the live preview, including event listeners for mouse interactions like painting and context menu disabling. ```javascript function initPreview() { const canvas = document.getElementById('preview-canvas'); const s = TILE * PREVIEW_ZOOM; canvas.width = PV_W * s; canvas.height = PV_H * s; canvas.addEventListener('mousedown', (e) => { e.preventDefault(); painting = true; paintValue = e.button === 2 ? 0 : -1; handlePreviewMouse(e); }); canvas.addEventListener('mousemove', (e) => { if (painting) handlePreviewMouse(e); }); canvas.addEventListener('mouseup', () => { painting = false; }); canvas.addEventListener('mouseleave', () => { painting = false; }); canvas.addEventListener('contextmenu', (e) => e.preventDefault()); } ``` -------------------------------- ### Initialize Wall Tile Editor Constants and Variables Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/wall-tile-editor.html Sets up constants for tile dimensions, grid layout, zoom levels, colors, and mask labels. Initializes variables for image source, extracted pieces, grid state, and painting mode. ```javascript const TILE = 16; const SPRITE_H = 32; const GRID_COLS = 4; const PREVIEW_ZOOM = 4; const PIECE_ZOOM = 4; const PV_W = 14, PV_H = 10; const WALL_COLOR = '#3A3A5C'; const MASK_LABELS = [ 'Isolated', 'N', 'E', 'N+E', 'S', 'N+S', 'E+S', 'N+E+S', 'W', 'N+W', 'E+W', 'N+E+W', 'S+W', 'N+S+W', 'E+S+W', 'N+E+S+W', ]; let sourceImg = null; let pieces = []; // 16 canvas elements, indexed by mask let grid = []; let painting = false; let paintValue = 1; for (let r = 0; r < PV_H; r++) grid.push(new Array(PV_W).fill(0)); ``` -------------------------------- ### Set Export Button Click Handler Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/asset-manager.html Assigns the exportAssets function to the click event of the export start button. ```javascript document.getElementById('export-start-btn').onclick = exportAssets ``` -------------------------------- ### Run All Tests Source: https://github.com/pablodelucca/pixel-agents/blob/main/CONTRIBUTING.md Execute all unit and integration tests for both the webview and server components. ```bash npm test ``` -------------------------------- ### Initialize Preview Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/wall-tile-editor.html Initializes the preview functionality by setting up the canvas and event listeners. ```javascript initPreview(); ``` -------------------------------- ### Get Record Type Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/jsonl-viewer.html Determines the type of a record, returning 'unknown' if there's a parse error or the type is not specified. ```javascript function getRecordType(r) { return r._parseError ? 'unknown' : (r.type || 'unknown'); } ``` -------------------------------- ### New Project Initialization Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/asset-manager.html Handles the 'New Project' action. It prompts the user to confirm if there are unsaved changes and then resets the project data to an empty state using `loadJsonData`. ```javascript document.getElementById('btn-new-project').onclick = () => { if (data && data.assets.length > 0) { if (!confirm('Start a new project? Unsaved changes will be lost.')) return } jsonFileHandle = null loadJsonData({ version: 1, timestamp: new Date().toISOString(), sourceFile: '', tileset: '', backgroundColor: null, assets: [] }) statusMsg.textContent = 'New project created — load a PNG and start adding assets' } ``` -------------------------------- ### Get Orientations for Rotation Scheme Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/asset-manager.html Returns an array of orientation names based on the selected rotation scheme. Defaults to '4-way' orientations. ```javascript function getOrientationsForScheme(scheme) { if (scheme === '3-way-mirror') return ['front', 'back', 'right'] if (scheme === '2-way') return ['front', 'right'] return ['front', 'right', 'back', 'left'] // 4-way } ``` -------------------------------- ### Initialize Windows-MCP Tools Source: https://github.com/pablodelucca/pixel-agents/blob/main/CLAUDE.md Command to initialize the Windows-MCP (Desktop Automation) tools with a specific Python version. Lists available tools. ```sh uvx --python 3.13 windows-mcp ``` -------------------------------- ### Enable Export Button Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/asset-manager.html Re-enables the export start button after the export process has completed or failed. This is typically called at the end of the export function. ```javascript document.getElementById('export-start-btn').disabled = false ``` -------------------------------- ### Load PNG Tileset Image Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/asset-manager.html Handles the selection of a PNG file via the file input. Loads the image, updates the `tilesetImg` variable, and triggers rendering of the tileset, list, and preview. Initializes project data if it's empty. ```javascript document.getElementById('png-file').onchange = (e) => { const file = e.target.files[0] if (!file) return const reader = new FileReader() reader.onload = (ev) => { const img = new Image() img.onload = () => { tilesetImg = img // Auto-initialize empty project if no data loaded yet if (!data) { loadJsonData({ version: 1, timestamp: new Date().toISOString(), sourceFile: file.name, tileset: file.name, backgroundColor: null, assets: [], }) } updateTilesetVisibility() renderTileset() renderList() renderPreview() statusMsg.textContent = '\u2713 Tileset loaded: ' + img.width + 'x' + img.height + 'px' + (!data || data.assets.length === 0 ? ' — click "+ Add Asset" to start defining assets' : '') } img.src = ev.target.result } reader.readAsDataURL(file) e.target.value = '' } ``` -------------------------------- ### Update Selection Rectangle Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/asset-manager.html Updates the visual selection rectangle based on drag start and end points, scaling them by 'tilesetZoom'. This is used to indicate the area being selected or drawn. ```javascript function updateSelectRect() { if (!dragStart || !dragEnd) return const x1 = Math.min(dragStart.x, dragEnd.x) * tilesetZoom const y1 = Math.min(dragStart.y, dragEnd.y) * tilesetZoom const x2 = Math.max(dragStart.x, dragEnd.x) * tilesetZoom const y2 = Math.max(dragStart.y, dragEnd.y) * tilesetZoom selectRect.style.left = x1 + 'px' selectRect.style.top = y1 + 'px' selectRect.style.width = (x2 - x1) + 'px' selectRect.style.height = (y2 - y1) + 'px' } ``` -------------------------------- ### Handle Image Loading for Wall Tiles Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/wall-tile-editor.html Listens for file input changes to load a user-provided image. Once loaded, it updates the status, renders the source image, extracts tile pieces, and initializes the preview. ```javascript document.getElementById('file-input').addEventListener('change', (e) => { const file = e.target.files[0]; if (!file) return; const img = new Image(); img.onload = () => { sourceImg = img; document.getElementById('load-status').textContent = `Loaded: ${img.width}×${img.height}`; onImageLoaded(); }; img.src = URL.createObjectURL(file); }); ``` -------------------------------- ### Development Watch Mode Source: https://github.com/pablodelucca/pixel-agents/blob/main/CONTRIBUTING.md Run this command for development with live rebuilds of the extension backend and TypeScript type-checking. ```bash npm run watch ``` -------------------------------- ### Get Preview Canvas Pixel Coordinates Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/asset-manager.html Calculates the integer pixel coordinates within the preview canvas based on mouse event coordinates and the current zoom level. ```javascript function previewPixelAt(e) { const rect = previewCanvas.getBoundingClientRect() return { x: Math.floor((e.clientX - rect.left) / previewZoom), y: Math.floor((e.clientY - rect.top) / previewZoom) } } ``` -------------------------------- ### Render Live Preview Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/wall-tile-editor.html Draws the current state of the wall grid onto the preview canvas. It first fills base colors and then overlays the appropriate wall sprites based on adjacent tiles. ```javascript function renderPreview() { const canvas = document.getElementById('preview-canvas'); const ctx = canvas.getContext('2d'); const z = PREVIEW_ZOOM; const s = TILE * z; ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.imageSmoothingEnabled = false; // Pass 1: base colors for (let r = 0; r < PV_H; r++) { for (let c = 0; c < PV_W; c++) { if (grid[r][c]) { ctx.fillStyle = WALL_COLOR; } else { ctx.fillStyle = (r + c) % 2 === 0 ? '#B8B4A8' : '#ACA89C'; } ctx.fillRect(c * s, r * s, s, s); } } // Pass 2: wall sprites, row order, bottom-anchored for (let r = 0; r < PV_H; r++) { for (let c = 0; c < PV_W; c++) { if (!grid[r][c]) continue; let mask = 0; if (r > 0 && grid[r - 1][c]) mask |= 1; if (c < PV_W - 1 && grid[r][c + 1]) mask |= 2; if (r < PV_H - 1 && grid[r + 1][c]) mask |= 4; if (c > 0 && grid[r][c - 1]) mask |= 8; const piece = pieces[mask]; if (!piece) continue; // Bottom-anchor: sprite bottom = tile bottom const yOff = (TILE - SPRITE_H) * z; ctx.drawImage(piece, 0, 0, TILE, SPRITE_H, c * s, r * s + yOff, TILE * z, SPRITE_H * z); } } // Grid lines ctx.strokeStyle = 'rgba(255,255,255,0.08)'; ctx.lineWidth = 1; for (let c = 0; c <= PV_W; c++) { ctx.beginPath(); ctx.moveTo(c * s + 0.5, 0); ctx.lineTo(c * s + 0.5, PV_H * s); ctx.stroke(); } for (let r = 0; r <= PV_H; r++) { ctx.beginPath(); ctx.moveTo(0, r * s + 0.5); ctx.lineTo(PV_W * s, r * s + 0.5); ctx.stroke(); } } ``` -------------------------------- ### Get Visible Records Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/jsonl-viewer.html Filters records based on current filter settings and search query. Only records that match the active filters and contain the search query are returned. ```javascript function getVisibleRecords() { return processedRecords.filter(pr => { const t = getRecordType(pr.record); const ft = (t==='assistant'||t==='user'||t==='system'||t==='progress') ? t : 'unknown'; if (!filters[ft]) return false; if (searchQuery && !JSON.stringify(pr.record).toLowerCase().includes(searchQuery.toLowerCase())) return false; return true; }); } ``` -------------------------------- ### Handle Tileset Mouse Down Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/asset-manager.html Initiates mouse event handling for the tileset. Records the starting position for click or drag detection and prepares for drawing a selection rectangle if in add mode. ```javascript tilesetWrap.onmousedown = (e) => { if (e.target !== tilesetCanvas) return const rect = tilesetCanvas.getBoundingClientRect() const px = (e.clientX - rect.left) / tilesetZoom const py = (e.clientY - rect.top) / tilesetZoom clickStartPos = { x: e.clientX, y: e.clientY } if (addMode) { dragStart = snapCoord(px, py) dragEnd = { ...dragStart } isDragging = true updateSelectRect() selectRect.style.display = 'block' } e.preventDefault() } ``` -------------------------------- ### `launchNewTerminal` — Spawn a New Agent Source: https://context7.com/pablodelucca/pixel-agents/llms.txt Creates a VS Code terminal, runs the `claude` command with a session ID, builds the JSONL path, pre-registers the agent, and begins polling for the JSONL file. It also handles resuming prior sessions. ```APIDOC ## `launchNewTerminal` — Spawn a New Agent ### Description Creates a VS Code terminal, runs `claude --session-id `, builds the expected JSONL path, pre-registers the agent, and starts polling for the JSONL file to appear. Handles `/resume` detection when the user resumes a prior session. ### Parameters - **nextAgentIdRef** ({ current: number }) - Reference to the next agent ID. - **nextTerminalIndexRef** ({ current: number }) - Reference to the next terminal index. - **agents** (Map) - A map storing the state of all agents. - **activeAgentIdRef** ({ current: number | null }) - Reference to the currently active agent ID. - **knownJsonlFiles** (Set) - A set of known JSONL files to prevent re-adoption. - **fileWatchers** (Map) - Map of file watchers for agents. - **pollingTimers** (Map) - Map of polling timers for agents. - **waitingTimers** - Timers for waiting states. - **permissionTimers** - Timers for permission checks. - **jsonlPollTimers** - Timers for JSONL file polling. - **projectScanTimerRef** - Reference to the project scan timer. - **webview** (vscode.Webview | undefined) - The VS Code webview instance. - **persistAgents** (() => void) - Function to save agent states. - **folderPath** (string, optional) - The path to the project folder. - **bypassPermissions** (boolean, optional) - Flag to bypass permission checks. ### Example ```typescript await launchNewTerminal( nextAgentIdRef, nextTerminalIndexRef, agents, activeAgentIdRef, knownJsonlFiles, fileWatchers, pollingTimers, waitingTimers, permissionTimers, jsonlPollTimers, projectScanTimerRef, webview, persistAgents, '/home/user/myproject', // folderPath (optional) false // bypassPermissions (--dangerously-skip-permissions) ); ``` ### Behavior - Creates terminal "Claude Code #1", sends `claude --session-id `. - Posts `{ type: 'agentCreated', id: 1, folderName: undefined }` to webview. - Polls for `~/.claude/projects//.jsonl` to appear (every 1s). ``` -------------------------------- ### Get Selected Member Units Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/asset-manager.html Constructs a list of member units for a group, including individual assets and nested groups. Resolves asset indices and handles covered indices. ```javascript function getSelectedMemberUnits() { const units = [] const coveredIndices = new Set() for (const gid of selectedGroupUnits) { const group = data.groups.find(g => g.id === gid) if (!group) continue const leafAssetIds = [] for (const mid of group.members) { leafAssetIds.push(...resolveGroupAssets(mid)) } const indices = leafAssetIds .map(aid => data.assets.findIndex(a => a.id === aid)) .filter(i => i >= 0 && selectedIndices.has(i)) if (indices.length === 0) continue indices.forEach(i => coveredIndices.add(i)) units.push({ type: 'group', id: '@' + gid, groupId: gid, indices, label: group.name || gid, groupType: group.type }) } for (const idx of selectedIndices) { if (coveredIndices.has(idx)) continue const asset = data.assets[idx] units.push({ type: 'asset', id: asset.id, indices: [idx], label: asset.label || asset.id }) } return units } ``` -------------------------------- ### Load JSON Project using Classic File Input Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/asset-manager.html Handles JSON file loading via a traditional file input element. Reads the file content, parses it as JSON, and loads the data using `loadJsonData`. Includes error handling for invalid JSON. ```javascript document.getElementById('json-file').onchange = (e) => { const file = e.target.files[0] if (!file) return jsonFileHandle = null // no handle from classic file input const reader = new FileReader() reader.onload = (ev) => { try { loadJsonData(JSON.parse(ev.target.result)) statusMsg.textContent = '\u2713 Loaded ' + data.assets.length + ' assets from ' + file.name } catch (err) { alert('Invalid JSON: ' + err.message) } } reader.readAsText(file) e.target.value = '' // Allow re-loading same file } ``` -------------------------------- ### Handle Preview Canvas Mouse Up/Leave for Eraser Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/asset-manager.html Resets the erasing state when the mouse button is released or leaves the preview canvas after erasing has started. It also schedules a save operation. ```javascript previewCanvas.onmouseup = () => { if (isErasing) { isErasing = false lastErasePx = null scheduleSave() } } previewCanvas.onmouseleave = () => { if (isErasing) { isErasing = false lastErasePx = null scheduleSave() } } ``` -------------------------------- ### Room Wall Preset Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/wall-tile-editor.html Applies a rectangular room shape to the preview grid. ```javascript function presetRoom() { presetClear(); for (let c = 2; c <= 11; c++) { grid[1][c] = 1; grid[8][c] = 1; } for (let r = 1; r <= 8; r++) { grid[r][2] = 1; grid[r][11] = 1; } renderPreview(); } ``` -------------------------------- ### Run Webview Tests Source: https://github.com/pablodelucca/pixel-agents/blob/main/CONTRIBUTING.md Execute only the webview-specific tests. ```bash npm run test:webview ``` -------------------------------- ### Handle Preview Canvas Mouse Down for Eraser Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/asset-manager.html Initializes the erasing process when the mouse button is pressed down on the preview canvas while the eraser is active. It records the starting point and performs the initial erase. ```javascript previewCanvas.onmousedown = (e) => { if (!eraserActive || selectedIdx < 0 || !data) return e.preventDefault() pushUndo() isErasing = true const p = previewPixelAt(e) lastErasePx = p erasePixels([p]) renderPreview() } ``` -------------------------------- ### Build Summary String Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/asset-manager.html Generates a summary string indicating the number of approved and discarded assets. Approved assets are those that do not have the 'discard' property set. ```javascript function buildSummary() { const approved = data.assets.filter(x => !x.discard).length return approved + ' approved + ' + (data.assets.length - approved) + ' discarded assets' } ``` -------------------------------- ### Build Output JSON Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/asset-manager.html Constructs a JSON string representing the current state of the tileset data, including version, timestamp, source file, tileset configuration, background color, assets, and groups. Formats the output with an indentation of 2 spaces. ```javascript function buildOutputJson() { return JSON.stringify({ version: data.version || 1, timestamp: new Date().toISOString(), sourceFile: data.sourceFile, tileset: data.tileset, backgroundColor: data.backgroundColor, assets: data.assets, groups: data.groups || [], }, null, 2) } ``` -------------------------------- ### Load JSON Project using File System Access API Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/asset-manager.html Attempts to load a JSON project file using the modern File System Access API, which provides a handle for direct saving. Falls back to a classic file input if the API is unavailable or fails. ```javascript document.getElementById('btn-load-json').onclick = async () => { // Try File System Access API first (Chromium) — gives us a handle for direct save-back if (window.showOpenFilePicker) { try { const [handle] = await window.showOpenFilePicker({ types: [ { description: 'JSON', accept: { 'application/json': ['.json'] }, }, ], multiple: false, }) const file = await handle.getFile() const text = await file.text() jsonFileHandle = handle loadJsonData(JSON.parse(text)) statusMsg.textContent = '\u2713 Loaded ' + data.assets.length + ' assets from ' + file.name return } catch (err) { if (err.name === 'AbortError') return // user cancelled console.warn('File System Access API failed, falling back:', err) } } // Fallback: classic file input document.getElementById('json-file').click() } ``` -------------------------------- ### JavaScript DOM Element References for Pixel Agents Viewer Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/jsonl-viewer.html Gets references to various DOM elements used in the application, such as buttons, headers, views, lists, and input fields. These references are used to manipulate the UI and handle user interactions. ```javascript const backBtn = document.getElementById('backBtn'); const hdrPath = document.getElementById('hdrPath'); const hdrStats = document.getElementById('hdrStats'); const viewEmpty = document.getElementById('viewEmpty'); const viewSessions = document.getElementById('viewSessions'); const viewDetail = document.getElementById('viewDetail'); const dropZone = document.getElementById('dropZone'); const sessionsList = document.getElementById('sessionsList'); const sessionCount = document.getElementById('sessionCount'); const sessionSearch = document.getElementById('sessionSearch'); const convoHeader = document.getElementById('convoHeader'); const convoName = document.getElementById('convoName'); const convoBody = document.getElementById('convoBody'); const recordList = document.getElementById('recordList'); const detailContent = document.getElementById('detailContent'); const timelineBar = document.getElementById('timelineBar'); const searchInput = document.getElementById('searchInput'); ``` -------------------------------- ### Open Folder Picker for JSONL Files Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/jsonl-viewer.html Uses the Directory Picker API to allow users to select a folder. It then reads all .jsonl files within that folder and loads their content. ```javascript async function openFolder() { if (window.showDirectoryPicker) { try { const handle = await window.showDirectoryPicker(); const files = []; for await (const entry of handle.values()) { if (entry.kind === 'file' && entry.name.endsWith('.jsonl')) { files.push(await entry.getFile()); } } if (files.length > 0) { await loadFileObjects(files, handle.name); } } catch (e) { if (e.name !== 'AbortError') console.error(e); } } else { document.getElementById('folderInput').click(); } } ``` -------------------------------- ### Handle Add Asset Button Click Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/asset-manager.html Handles the click event for the 'Add Asset' button. It prompts the user to load a tileset if none is loaded, initializes an empty project if necessary, and toggles the add mode. ```javascript document.getElementById('btn-add-asset').onclick = () => { if (!tilesetImg) { alert('Load a tileset PNG first'); return } // Auto-initialize empty project if needed if (!data) { loadJsonData({ version: 1, timestamp: new Date().toISOString(), sourceFile: '', tileset: '', backgroundColor: null, assets: [] }) } redrawMode = false toggleAddMode(!addMode) } ``` -------------------------------- ### Build Extension for E2E Tests Source: https://github.com/pablodelucca/pixel-agents/blob/main/CONTRIBUTING.md Build the extension before running end-to-end tests, as they load the compiled output. ```bash npm run build ``` -------------------------------- ### JavaScript File and Folder Loading Event Listeners for Pixel Agents Viewer Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/jsonl-viewer.html Sets up event listeners for opening folders and files via button clicks or drag-and-drop. It triggers functions to load files and folders into the application. ```javascript document.getElementById('openFolderBtn').addEventListener('click', () => openFolder()); document.getElementById('openFileBtn').addEventListener('click', () => document.getElementById('fileInput').click()); document.getElementById('viewJsonlBtn').addEventListener('click', () => openDetailForCurrentSession()); document.getElementById('fileInput').addEventListener('change', (e) => { if (e.target.files[0]) loadSingleFile(e.target.files[0]); e.target.value = ''; }); document.getElementById('folderInput').addEventListener('change', (e) => { loadFilesFromInput(e.target.files); e.target.value = ''; }); // Drag & drop (folder or file) document.body.addEventListener('dragover', (e) => { e.preventDefault(); dropZone?.classList.add('drag-over'); }); document.body.addEventListener('dragleave', (e) => { if (e.target === document.body || e.target === dropZone) dropZone?.classList.remove('drag-over'); }); document.body.addEventListener('drop', async (e) => { e.preventDefault(); dropZone?.classList.remove('drag-over'); const items = [...(e.dataTran ``` -------------------------------- ### Initialize Editor State After Image Load Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/wall-tile-editor.html Called after an image is successfully loaded. It makes the main content visible, renders the source image with grid overlays, extracts individual tile pieces, and sets up the initial room layout. ```javascript function onImageLoaded() { document.getElementById('content').classList.remove('hidden'); renderSourceImage(); extractPieces(); renderPieceGrid(); presetRoom(); } ``` -------------------------------- ### Initialize Group Creation Form Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/asset-manager.html Initializes the group creation form with default values from the first selected asset. Sets name, category, flags, and background tiles. ```javascript document.getElementById('group-name-input').value = '' document.getElementById('group-id-input').value = commonPrefix(ids) || '' const first = assets[0] document.getElementById('group-category').value = first.category || 'misc' document.getElementById('group-bgtiles').value = first.backgroundTiles || 0 document.getElementById('group-walls').checked = !!first.canPlaceOnWalls document.getElementById('group-ontop').checked = !!first.canPlaceOnSurfaces document.getElementById('btn-create-group').textContent = 'Create Group' renderGroupMembers() ``` -------------------------------- ### Clear Wall Preset Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/wall-tile-editor.html Resets the entire preview grid to an empty state (no walls). ```javascript function presetClear() { for (let r = 0; r < PV_H; r++) for (let c = 0; c < PV_W; c++) grid[r][c] = 0; renderPreview(); } ``` -------------------------------- ### Initialize and Use HookEventHandler Source: https://context7.com/pablodelucca/pixel-agents/llms.txt Sets up HookEventHandler to manage hook events, mapping session IDs to agents and buffering events. Registers lifecycle callbacks and handles incoming events. Unregisters agents and disposes of the handler when done. ```typescript const handler = new HookEventHandler( agents, waitingTimers, permissionTimers, () => webview, claudeProvider, watchAllSessionsRef, ); // Register lifecycle callbacks handler.setLifecycleCallbacks({ onExternalSessionDetected: (sessionId, transcriptPath, cwd) => { /* adopt external agent */ }, onSessionClear: (agentId, newSessionId, newPath) => { /* reassign after /clear */ }, onSessionResume: (transcriptPath) => { /* clear dismissals for --resume */ }, onSessionEnd: (agentId, reason) => { /* remove external agent */ }, onTeammateDetected:(parentId, sessionId, agentType) => { /* scan for teammate JSONL */ }, onTeammateRemoved: (teammateId) => { /* remove teammate from office */ }, }); // Register a known agent so hook events can be routed to it handler.registerAgent('550e8400-e29b-41d4-a716-446655440000', 1); // Handle an incoming hook event (called by PixelAgentsServer.onHookEvent) handler.handleEvent('claude', { hook_event_name: 'PreToolUse', session_id: '550e8400-e29b-41d4-a716-446655440000', tool_name: 'Bash', tool_input: { command: 'npm test' }, }); // → Posts to webview: { type: 'agentStatus', id: 1, status: 'active' } // { type: 'agentToolStart', id: 1, toolId: 'hook-1700000000', status: 'Running: npm test', toolName: 'Bash' } handler.unregisterAgent('550e8400-e29b-41d4-a716-446655440000'); handler.dispose(); // clears buffer timer, all maps ``` -------------------------------- ### Export Assets to Directory Source: https://github.com/pablodelucca/pixel-agents/blob/main/scripts/asset-manager.html Initiates the asset export process using the File System Access API. Handles directory selection, organization by category, and progress reporting. Falls back to an error message if the API is unsupported. ```javascript async function exportAssets() { if (!window.showDirectoryPicker) { document.getElementById('export-result').style.display = 'block' document.getElementById('export-result').style.background = '#4a1a1a' document.getElementById('export-result').style.border = '1px solid #6b2020' document.getElementById('export-result').textContent = 'Export requires a Chromium browser (Chrome/Edge) with File System Access API support.' return } let rootDir try { rootDir = await window.showDirectoryPicker({ mode: 'readwrite' }) } catch (err) { if (err.name === 'AbortError') return throw err } const useCategoryFolders = document.querySelector('input[name="export-org"]:checked').value === 'category' const items = collectExportItems() const totalPngs = items.reduce((sum, i) => sum + i.leafAssets.length, 0) let exportedPngs = 0 document.getElementById('export-progress').style.display = 'block' document.getElementById('export-result').style.display = 'none' document.getElementById('export-start-btn').disabled = true const furnitureDir = await rootDir.getDirectoryHandle('furniture', { create: true }) try { for (const item of items) { let parentDir = furnitureDir if (useCategoryFolders) { parentDir = await furnitureDir.getDirectoryHandle(item.category, { create: true }) } const groupDir = await parentDir.getDirectoryHandle(item.folderId, { create: true }) // Write manifest.j ``` -------------------------------- ### Simple Asset Manifest Source: https://github.com/pablodelucca/pixel-agents/blob/main/docs/external-assets.md Use this manifest for single-sprite assets without rotation. Ensure all fields are correctly populated, especially 'id' and 'file'. ```json { "id": "MY_ITEM", "name": "My Item", "category": "decor", "type": "asset", "file": "MY_ITEM.png", "width": 16, "height": 16, "footprintW": 1, "footprintH": 1, "canPlaceOnWalls": false, "canPlaceOnSurfaces": false, "backgroundTiles": 0 } ``` -------------------------------- ### Layout Persistence: Read, Write, and Watch Office Layouts Source: https://context7.com/pablodelucca/pixel-agents/llms.txt Manages the saving and loading of office layouts to a shared JSON file. `watchLayoutFile` detects external changes for cross-window synchronization. ```typescript // src/layoutPersistence.ts import { readLayoutFromFile, writeLayoutToFile, watchLayoutFile } from './layoutPersistence.js'; // Layout format (OfficeLayout from webview-ui/src/office/types.ts): const layout = { version: 1, cols: 20, rows: 15, tiles: [0, 1, 1, 0, /* ... TileType values ... */], // 0=WALL, 1-9=FLOOR, 255=VOID furniture: [ { uid: 'f1', type: 'DESK', col: 4, row: 3, color: { h: 210, s: 50, b: 80 } }, { uid: 'f2', type: 'CUSHIONED_CHAIR', col: 4, row: 5 }, ], tileColors: [null, { h: 30, s: 20, b: 95 }, { h: 30, s: 20, b: 95 }, null, /* ... */], layoutRevision: 2, }; writeLayoutToFile(layout); // atomic tmp+rename to ~/.pixel-agents/layout.json const saved = readLayoutFromFile(); // → layout object or null if file doesn't exist // Cross-window sync: watch for changes from other VS Code windows const watcher = watchLayoutFile((updatedLayout) => { webview?.postMessage({ type: 'layoutLoaded', layout: updatedLayout }); }); watcher.markOwnWrite(); // suppress own-write event before writing writeLayoutToFile(layout); watcher.dispose(); // stop polling + fs.watch ``` -------------------------------- ### Migrate and Load Layout with Revision Bumping Source: https://context7.com/pablodelucca/pixel-agents/llms.txt Loads saved office layouts, automatically migrating from legacy storage and resetting to defaults if the bundled revision is newer. ```typescript // src/layoutPersistence.ts import { migrateAndLoadLayout } from './layoutPersistence.js'; const bundledDefault = JSON.parse( fs.readFileSync('dist/assets/default-layout-2.json', 'utf-8') ); // has layoutRevision: 2 const result = migrateAndLoadLayout(context, bundledDefault); // Returns: { layout: OfficeLayout, wasReset: boolean } | null // wasReset=true → user's layout was replaced because bundled revision (2) > saved revision (1) // wasReset=false → layout loaded from file or migrated from workspaceState normally if (result) { webview?.postMessage({ type: 'layoutLoaded', layout: result.layout, wasReset: result.wasReset }); } ```