You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: add support for external asset packs 🎨 (#169)
* feat: add support for external asset packs
Persist and load furniture assets from user-defined directories outside
the extension, enabling third-party asset packs to be used alongside
built-in furniture.
- Add configPersistence.ts to read/write ~/.pixel-agents/config.json
- Load external asset dirs on boot and merge with bundled assets
- Add/remove directories via Settings modal with live palette refresh
- Add docs/external-assets.md covering the manifest format and usage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: Windows path display, asset ID dedup, path traversal defense in external assets
---------
Co-authored-by: Marc teBoekhorst <marctebo@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Florin Timbuc <florin@sowild.design>
**One-agent-per-terminal**: Each "+ Agent" click → new terminal (`claude --session-id <uuid>`) → immediate agent creation → 1s poll for `<uuid>.jsonl` → file watching starts.
79
80
@@ -89,7 +90,7 @@ JSONL transcripts at `~/.claude/projects/<project-hash>/<session-id>.jsonl`. Pro
89
90
90
91
**Extension state per agent**: `id, terminalRef, projectDir, jsonlFile, fileOffset, lineBuffer, activeToolIds, activeToolStatuses, activeSubagentToolNames, isWaiting`.
91
92
92
-
**Persistence**: Agents persisted to `workspaceState` key `'pixel-agents.agents'` (includes palette/hueShift/seatId). **Layout persisted to `~/.pixel-agents/layout.json`** (user-level, shared across all VS Code windows/workspaces). `layoutPersistence.ts` handles all file I/O: `readLayoutFromFile()`, `writeLayoutToFile()` (atomic via `.tmp` + rename), `migrateAndLoadLayout()` (checks file → migrates old workspace state → falls back to bundled default), `watchLayoutFile()` (hybrid `fs.watch` + 2s polling for cross-window sync). On save, `markOwnWrite()` prevents the watcher from re-reading our own write. External changes push `layoutLoaded` to the webview; skipped if the editor has unsaved changes (last-save-wins). On webview ready: `restoreAgents()` matches persisted entries to live terminals. `nextAgentId`/`nextTerminalIndex` advanced past restored values. **Default layout**: When no saved layout file exists and no workspace state to migrate, a bundled `default-layout.json` is loaded from `assets/` and written to the file. If that also doesn't exist, `createDefaultLayout()` generates a basic office. To update the default: run "Pixel Agents: Export Layout as Default" from the command palette (writes current layout to `webview-ui/public/assets/default-layout.json`), then rebuild. **Export/Import**: Settings modal offers Export Layout (save dialog → JSON file) and Import Layout (open dialog → validates `version: 1` + `tiles` array → writes to layout file + pushes `layoutLoaded` to webview).
93
+
**Persistence**: Agents persisted to `workspaceState` key `'pixel-agents.agents'` (includes palette/hueShift/seatId). **Layout persisted to `~/.pixel-agents/layout.json`** (user-level, shared across all VS Code windows/workspaces). `layoutPersistence.ts` handles all file I/O: `readLayoutFromFile()`, `writeLayoutToFile()` (atomic via `.tmp` + rename), `migrateAndLoadLayout()` (checks file → migrates old workspace state → falls back to bundled default), `watchLayoutFile()` (hybrid `fs.watch` + 2s polling for cross-window sync). On save, `markOwnWrite()` prevents the watcher from re-reading our own write. External changes push `layoutLoaded` to the webview; skipped if the editor has unsaved changes (last-save-wins). On webview ready: `restoreAgents()` matches persisted entries to live terminals. `nextAgentId`/`nextTerminalIndex` advanced past restored values. **Default layout**: When no saved layout file exists and no workspace state to migrate, a bundled `default-layout.json` is loaded from `assets/` and written to the file. If that also doesn't exist, `createDefaultLayout()` generates a basic office. To update the default: run "Pixel Agents: Export Layout as Default" from the command palette (writes current layout to `webview-ui/public/assets/default-layout.json`), then rebuild. **Export/Import**: Settings modal offers Export Layout (save dialog → JSON file) and Import Layout (open dialog → validates `version: 1` + `tiles` array → writes to layout file + pushes `layoutLoaded` to webview). **Config persisted to `~/.pixel-agents/config.json`** (user-level, shared across windows). `configPersistence.ts` handles read/write with atomic tmp+rename. Currently stores `externalAssetDirectories: string[]` for external asset pack paths. **External asset directories**: Settings modal offers Add/Remove Asset Directory. External furniture merged with bundled assets on boot and on add/remove via `mergeLoadedAssets()` (external IDs override bundled on collision).
Copy file name to clipboardExpand all lines: README.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -41,6 +41,7 @@ This is the source code for the free Pixel Agents extension for VS Code — inst
41
41
-**Sound notifications** — optional chime when an agent finishes its turn
42
42
-**Sub-agent visualization** — Task tool sub-agents spawn as separate characters linked to their parent
43
43
-**Persistent layouts** — your office design is saved and shared across VS Code windows
44
+
-**External asset directories** — load custom or third-party furniture packs from any folder on your machine
44
45
-**Diverse characters** — 6 diverse characters. These are based on the amazing work of [JIK-A-4, Metro City](https://jik-a-4.itch.io/metrocity-free-topdown-character-pack).
45
46
46
47
<palign="center">
@@ -96,7 +97,7 @@ Each furniture item lives in its own folder under `assets/furniture/` with a `ma
96
97
97
98
To add a new furniture item, create a folder in `webview-ui/public/assets/furniture/` with your PNG sprite(s) and a `manifest.json`, then rebuild. The asset manager (`scripts/asset-manager.html`) provides a visual editor for creating and editing manifests.
98
99
99
-
Detailed documentation on the manifest format and asset pipeline is coming soon.
100
+
To use furniture from an external directory, open Settings → **Add Asset Directory**. See [docs/external-assets.md](docs/external-assets.md) for the full manifest format and how to use third-party asset packs.
100
101
101
102
Characters are based on the amazing work of [JIK-A-4, Metro City](https://jik-a-4.itch.io/metrocity-free-topdown-character-pack).
Pixel Agents supports loading furniture assets from directories outside the extension. This lets you use custom or third-party pixel art asset packs alongside the built-in furniture.
4
+
5
+
## Adding an External Directory
6
+
7
+
1. Open the Pixel Agents panel and click **Settings**
8
+
2. Click **Add Asset Directory** and pick a folder
9
+
3. Your custom assets will appear in the furniture palette immediately, merged with the built-ins
10
+
4. The directory path is saved to `~/.pixel-agents/config.json` and reloaded automatically on restart
11
+
12
+
To remove a directory, open Settings and click the **X** next to it.
13
+
14
+
## Directory Structure
15
+
16
+
Your asset directory must follow this structure:
17
+
18
+
```
19
+
my-assets/
20
+
assets/
21
+
furniture/
22
+
MY_CHAIR/
23
+
manifest.json
24
+
MY_CHAIR.png
25
+
MY_DESK/
26
+
manifest.json
27
+
MY_DESK_FRONT.png
28
+
MY_DESK_SIDE.png
29
+
```
30
+
31
+
Each furniture item gets its own subfolder containing a `manifest.json` and one or more PNG sprite files. The folder name doesn't matter — the `id` field in the manifest is what identifies the item.
32
+
33
+
## Manifest Format
34
+
35
+
### Simple asset (single sprite, no rotation)
36
+
37
+
```json
38
+
{
39
+
"id": "MY_ITEM",
40
+
"name": "My Item",
41
+
"category": "decor",
42
+
"type": "asset",
43
+
"file": "MY_ITEM.png",
44
+
"width": 16,
45
+
"height": 16,
46
+
"footprintW": 1,
47
+
"footprintH": 1,
48
+
"canPlaceOnWalls": false,
49
+
"canPlaceOnSurfaces": false,
50
+
"backgroundTiles": 0
51
+
}
52
+
```
53
+
54
+
### Rotation group (2-way)
55
+
56
+
```json
57
+
{
58
+
"id": "MY_DESK",
59
+
"name": "My Desk",
60
+
"category": "desks",
61
+
"type": "group",
62
+
"groupType": "rotation",
63
+
"rotationScheme": "2-way",
64
+
"canPlaceOnWalls": false,
65
+
"canPlaceOnSurfaces": false,
66
+
"backgroundTiles": 1,
67
+
"members": [
68
+
{
69
+
"type": "asset",
70
+
"id": "MY_DESK_FRONT",
71
+
"file": "MY_DESK_FRONT.png",
72
+
"width": 32,
73
+
"height": 32,
74
+
"footprintW": 2,
75
+
"footprintH": 2,
76
+
"orientation": "front"
77
+
},
78
+
{
79
+
"type": "asset",
80
+
"id": "MY_DESK_SIDE",
81
+
"file": "MY_DESK_SIDE.png",
82
+
"width": 16,
83
+
"height": 32,
84
+
"footprintW": 1,
85
+
"footprintH": 2,
86
+
"orientation": "side"
87
+
}
88
+
]
89
+
}
90
+
```
91
+
92
+
### Rotation group (3-way with mirrored side)
93
+
94
+
Use `"rotationScheme": "3-way-mirror"` and add `"mirrorSide": true` to the side member — the engine auto-generates the mirrored left variant so you only need one side sprite.
95
+
96
+
```json
97
+
{
98
+
"id": "MY_CHAIR",
99
+
"name": "My Chair",
100
+
"category": "chairs",
101
+
"type": "group",
102
+
"groupType": "rotation",
103
+
"rotationScheme": "3-way-mirror",
104
+
"canPlaceOnWalls": false,
105
+
"canPlaceOnSurfaces": false,
106
+
"backgroundTiles": 0,
107
+
"members": [
108
+
{
109
+
"type": "asset",
110
+
"id": "MY_CHAIR_FRONT",
111
+
"file": "MY_CHAIR_FRONT.png",
112
+
"width": 16,
113
+
"height": 16,
114
+
"footprintW": 1,
115
+
"footprintH": 1,
116
+
"orientation": "front"
117
+
},
118
+
{
119
+
"type": "asset",
120
+
"id": "MY_CHAIR_BACK",
121
+
"file": "MY_CHAIR_BACK.png",
122
+
"width": 16,
123
+
"height": 16,
124
+
"footprintW": 1,
125
+
"footprintH": 1,
126
+
"orientation": "back"
127
+
},
128
+
{
129
+
"type": "asset",
130
+
"id": "MY_CHAIR_SIDE",
131
+
"file": "MY_CHAIR_SIDE.png",
132
+
"width": 16,
133
+
"height": 16,
134
+
"footprintW": 1,
135
+
"footprintH": 1,
136
+
"orientation": "side",
137
+
"mirrorSide": true
138
+
}
139
+
]
140
+
}
141
+
```
142
+
143
+
## Field Reference
144
+
145
+
### Root fields (all manifests)
146
+
147
+
| Field | Type | Description |
148
+
|---|---|---|
149
+
|`id`| string | Unique identifier. Must be unique across all loaded assets |
150
+
|`name`| string | Display name shown in the palette |
If you have a pixel art asset pack (such as **[Office Interior Tileset (16x16)](https://donarg.itch.io/officetileset)** by [Donarg](https://donarg.itch.io/) — highly recommended), you'll need to slice the tileset into individual PNGs and create a `manifest.json` for each item.
182
+
183
+
The manifest format is simple enough that an AI assistant like Claude Code can generate them for you — just describe your sprites or share the PNGs and ask it to write the manifests.
184
+
185
+
The `scripts/asset-manager.html` in this repo also provides a visual editor for creating and editing manifests.
0 commit comments