- name
- pixel-agents-vscode
- description
- Visualize and manage Claude Code AI agents as pixel art characters in a VS Code extension office interface
- triggers
- ["set up pixel agents for claude code","visualize my AI agents in VS Code","create a pixel office for agents","customize agent characters and office layout","manage multiple claude code agents visually","track agent activity with pixel agents","add furniture to my agent office","debug agent connections in pixel agents"]
# Pixel Agents VSCode Extension
> Skill by [ara.so](https://ara.so) — AI Agent Skills collection.
Pixel Agents is a VS Code extension that visualizes multi-agent AI systems (currently Claude Code) as pixel art characters in a customizable office environment. Each agent becomes an animated character that reflects its real-time activity — typing when writing code, reading when searching files, and displaying speech bubbles when waiting for input.
## Installation
### From VS Code Marketplace
1. Open VS Code
2. Go to Extensions (Ctrl+Shift+X / Cmd+Shift+X)
3. Search for "Pixel Agents"
4. Click Install
Or install via command line:
```bash
code --install-extension pablodelucca.pixel-agents
```
### From Source
```bash
git clone https://github.com/pablodelucca/pixel-agents.git
cd pixel-agents
npm install
cd webview-ui && npm install && cd ..
npm run build
```
Then press **F5** in VS Code to launch the Extension Development Host.
## Prerequisites
- **VS Code**: Version 1.105.0 or later
- **Claude Code CLI**: Must be installed and configured
```bash
# Install Claude Code (if not already installed)
npm install -g @anthropic-ai/claude-code
```
## Key Features and Usage
### Spawning Agents
Open the **Pixel Agents** panel (appears in bottom panel area alongside terminal):
**Create normal agent:**
```typescript
// Click "+ Agent" button in Pixel Agents panel
// This spawns a new Claude Code terminal with a character
```
**Create agent with skip permissions:**
```typescript
// Right-click "+ Agent" button
// Select "Launch with --dangerously-skip-permissions"
// Agent will bypass all tool approval prompts
```
### Managing Characters
**Assign character to a seat:**
```typescript
// 1. Click a character to select it
// 2. Click an empty seat to reassign the character
```
**View agent status:**
- **Walking**: Agent is navigating to desk/idle
- **Typing**: Writing code, creating files, making changes
- **Reading**: Searching files, analyzing code
- **Speech bubble**: Waiting for user input or permission
- **Idle**: No current activity
### Office Layout Editor
**Open editor:**
```typescript
// Click "Layout" button in Pixel Agents panel
```
**Editor tools:**
- **Select** (S): Select and move furniture
- **Paint** (P): Paint floor tiles
- **Erase** (E): Remove items
- **Place** (F): Add furniture
- **Eyedropper** (I): Pick colors/tiles
- **Pick** (K): Select furniture type
**Keyboard shortcuts:**
- `Ctrl+Z` / `Cmd+Z`: Undo (50 levels)
- `Ctrl+Y` / `Cmd+Y`: Redo
- `Delete`: Remove selected item
- Arrow keys: Fine-tune placement
**Expand grid:**
```typescript
// Click the ghost border outside current grid
// Grid expands up to 64×64 tiles
```
### Configuration
Access settings via gear icon in Pixel Agents panel:
**Sound notifications:**
```typescript
// Toggle sound when agent finishes turn
// Settings → Enable Sound Notifications
```
**Debug view:**
```typescript
// Settings → Debug View
// Shows per-agent diagnostics:
// - JSONL file status
// - Lines parsed
// - Last data timestamp
// - File path
```
**Export/Import layouts:**
```typescript
// Settings → Export Layout (saves as JSON)
// Settings → Import Layout (load from JSON file)
```
**Add external assets:**
```typescript
// Settings → Add Asset Directory
// Point to folder with custom furniture packs
```
## Working with Assets
### Asset Structure
All assets are in `webview-ui/public/assets/`:
```
assets/
├── furniture/
│ ├── desk-01/
│ │ ├── manifest.json
│ │ └── sprite.png
│ └── chair-01/
│ ├── manifest.json
│ └── sprite.png
├── floors/
│ └── tile-wood.png
└── walls/
└── brick/
├── manifest.json
└── tileset.png
```
### Creating Custom Furniture
**Manifest structure:**
```json
{
"id": "desk-modern",
"name": "Modern Desk",
"category": "desks",
"width": 2,
"height": 1,
"isSeat": true,
"seatOffset": { "x": 0, "y": -16 },
"rotationGroups": [
{
"rotations": ["north", "east", "south", "west"],
"sprite": "desk-modern.png"
}
],
"stateGroups": [
{
"state": "off",
"sprite": "desk-modern-off.png"
},
{
"state": "on",
"sprite": "desk-modern-on.png"
}
]
}
```
**Add to project:**
```bash
# 1. Create folder in assets/furniture/
mkdir webview-ui/public/assets/furniture/desk-modern
# 2. Add sprite PNG and manifest.json
# 3. Rebuild extension
npm run build
```
### Using External Asset Directories
```typescript
// 1. Create asset directory structure:
// /my-assets/
// furniture/
// custom-desk/
// manifest.json
// sprite.png
// 2. In Pixel Agents Settings → Add Asset Directory
// 3. Select /my-assets/
// 4. Assets appear in furniture picker
```
**External manifest.json:**
```json
{
"id": "custom-plant",
"name": "Custom Plant",
"category": "decorations",
"width": 1,
"height": 1,
"isSeat": false,
"rotationGroups": [
{
"rotations": ["north"],
"sprite": "plant.png"
}
]
}
```
## Code Examples
### Monitoring Agent Activity (Extension Side)
```typescript
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
// Watch Claude Code JSONL transcript for agent activity
function watchAgentTranscript(projectPath: string, agentId: string): vscode.Disposable {
const jsonlPath = path.join(
projectPath,
'.claude',
'projects',
agentId,
'transcript.jsonl'
);
let lastPosition = 0;
const interval = setInterval(() => {
if (!fs.existsSync(jsonlPath)) {
return;
}
const stats = fs.statSync(jsonlPath);
if (stats.size <= lastPosition) {
return;
}
const buffer = Buffer.alloc(stats.size - lastPosition);
const fd = fs.openSync(jsonlPath, 'r');
fs.readSync(fd, buffer, 0, buffer.length, lastPosition);
fs.closeSync(fd);
const lines = buffer.toString('utf-8').split('\n').filter(l => l.trim());
lines.forEach(line => {
try {
const record = JSON.parse(line);
handleAgentRecord(agentId, record);
} catch (err) {
console.error('[Pixel Agents] Failed to parse JSONL:', err);
}
});
lastPosition = stats.size;
}, 500);
return new vscode.Disposable(() => clearInterval(interval));
}
function handleAgentRecord(agentId: string, record: any): void {
// Detect agent activity from JSONL record type
if (record.type === 'tool_use') {
const toolName = record.content?.name;
if (toolName === 'write_file' || toolName === 'edit_file') {
updateAgentState(agentId, 'typing');
} else if (toolName === 'search_files' || toolName === 'read_file') {
updateAgentState(agentId, 'reading');
} else if (toolName === 'run_command') {
updateAgentState(agentId, 'typing');
}
} else if (record.type === 'user_message') {
updateAgentState(agentId, 'idle');
}
}
function updateAgentState(agentId: string, state: string): void {
// Send message to webview
webviewPanel.webview.postMessage({
command: 'updateAgentState',
agentId,
state
});
}
```
### Character Animation (Webview Side)
```typescript
interface Character {
id: string;
x: number;
y: number;
targetX: number;
targetY: number;
state: 'idle' | 'walking' | 'typing' | 'reading';
direction: 'north' | 'south' | 'east' | 'west';
frame: number;
spriteSheet: HTMLImageElement;
}
class CharacterRenderer {
private characters: Map<string, Character> = new Map();
private readonly TILE_SIZE = 16;
private readonly FRAME_RATE = 8; // frames per second
private frameCounter = 0;
update(deltaTime: number): void {
this.frameCounter += deltaTime;
const frameInterval = 1000 / this.FRAME_RATE;
this.characters.forEach(char => {
// Update position if walking
if (char.state === 'walking') {
const dx = char.targetX - char.x;
const dy = char.targetY - char.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 1) {
char.x = char.targetX;
char.y = char.targetY;
char.state = 'idle';
} else {
const speed = 2; // pixels per frame
char.x += (dx / distance) * speed;
char.y += (dy / distance) * speed;
// Update direction based on movement
if (Math.abs(dx) > Math.abs(dy)) {
char.direction = dx > 0 ? 'east' : 'west';
} else {
char.direction = dy > 0 ? 'south' : 'north';
}
}
}
// Advance animation frame
if (this.frameCounter >= frameInterval) {
char.frame = (char.frame + 1) % this.getFrameCount(char.state);
}
});
if (this.frameCounter >= frameInterval) {
this.frameCounter = 0;
}
}
render(ctx: CanvasRenderingContext2D): void {
this.characters.forEach(char => {
const spriteX = this.getSpriteX(char);
const spriteY = this.getSpriteY(char);
ctx.drawImage(
char.spriteSheet,
spriteX, spriteY,
this.TILE_SIZE, this.TILE_SIZE * 2, // source
Math.floor(char.x), Math.floor(char.y),
this.TILE_SIZE, this.TILE_SIZE * 2 // destination
);
});
}
private getSpriteX(char: Character): number {
return char.frame * this.TILE_SIZE;
}
private getSpriteY(char: Character): number {
const directionOffsets = {
'south': 0,
'west': 1,
'east': 2,
'north': 3
};
const stateOffsets = {
'idle': 0,
'walking': 0,
'typing': 4,
'reading': 8
};
return (stateOffsets[char.state] + directionOffsets[char.direction])
* this.TILE_SIZE * 2;
}
private getFrameCount(state: string): number {
if (state === 'walking') return 4;
if (state === 'typing' || state === 'reading') return 3;
return 1; // idle
}
moveCharacter(id: string, targetX: number, targetY: number): void {
const char = this.characters.get(id);
if (char) {
char.targetX = targetX;
char.targetY = targetY;
char.state = 'walking';
}
}
setCharacterState(id: string, state: Character['state']): void {
const char = this.characters.get(id);
if (char) {
char.state = state;
char.frame = 0;
}
}
}
```
### Pathfinding (BFS for Agent Movement)
```typescript
interface Point {
x: number;
y: number;
}
class Pathfinder {
private grid: boolean[][]; // true = walkable
private width: number;
private height: number;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
عرض على GitHub