@neolio42/pixel-office 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +136 -0
  3. package/bin.sh +16 -0
  4. package/bin.ts +162 -0
  5. package/next-env.d.ts +6 -0
  6. package/next.config.ts +7 -0
  7. package/package.json +51 -0
  8. package/postcss.config.mjs +7 -0
  9. package/public/assets/characters/char_0.png +0 -0
  10. package/public/assets/characters/char_1.png +0 -0
  11. package/public/assets/characters/char_2.png +0 -0
  12. package/public/assets/characters/char_3.png +0 -0
  13. package/public/assets/characters/char_4.png +0 -0
  14. package/public/assets/characters/char_5.png +0 -0
  15. package/public/assets/characters.png +0 -0
  16. package/public/assets/default-layout-1.json +92 -0
  17. package/public/assets/floors/floor_0.png +0 -0
  18. package/public/assets/floors/floor_1.png +0 -0
  19. package/public/assets/floors/floor_2.png +0 -0
  20. package/public/assets/floors/floor_3.png +0 -0
  21. package/public/assets/floors/floor_4.png +0 -0
  22. package/public/assets/floors/floor_5.png +0 -0
  23. package/public/assets/floors/floor_6.png +0 -0
  24. package/public/assets/floors/floor_7.png +0 -0
  25. package/public/assets/floors/floor_8.png +0 -0
  26. package/public/assets/furniture/BIN/BIN.png +0 -0
  27. package/public/assets/furniture/BIN/manifest.json +13 -0
  28. package/public/assets/furniture/BOOKSHELF/BOOKSHELF.png +0 -0
  29. package/public/assets/furniture/BOOKSHELF/manifest.json +13 -0
  30. package/public/assets/furniture/CACTUS/CACTUS.png +0 -0
  31. package/public/assets/furniture/CACTUS/manifest.json +13 -0
  32. package/public/assets/furniture/CLOCK/CLOCK.png +0 -0
  33. package/public/assets/furniture/CLOCK/manifest.json +13 -0
  34. package/public/assets/furniture/COFFEE/COFFEE.png +0 -0
  35. package/public/assets/furniture/COFFEE/manifest.json +13 -0
  36. package/public/assets/furniture/COFFEE_TABLE/COFFEE_TABLE.png +0 -0
  37. package/public/assets/furniture/COFFEE_TABLE/manifest.json +13 -0
  38. package/public/assets/furniture/CUSHIONED_BENCH/CUSHIONED_BENCH.png +0 -0
  39. package/public/assets/furniture/CUSHIONED_BENCH/manifest.json +13 -0
  40. package/public/assets/furniture/CUSHIONED_CHAIR/CUSHIONED_CHAIR_BACK.png +0 -0
  41. package/public/assets/furniture/CUSHIONED_CHAIR/CUSHIONED_CHAIR_FRONT.png +0 -0
  42. package/public/assets/furniture/CUSHIONED_CHAIR/CUSHIONED_CHAIR_SIDE.png +0 -0
  43. package/public/assets/furniture/CUSHIONED_CHAIR/manifest.json +44 -0
  44. package/public/assets/furniture/DESK/DESK_FRONT.png +0 -0
  45. package/public/assets/furniture/DESK/DESK_SIDE.png +0 -0
  46. package/public/assets/furniture/DESK/manifest.json +33 -0
  47. package/public/assets/furniture/DOUBLE_BOOKSHELF/DOUBLE_BOOKSHELF.png +0 -0
  48. package/public/assets/furniture/DOUBLE_BOOKSHELF/manifest.json +13 -0
  49. package/public/assets/furniture/HANGING_PLANT/HANGING_PLANT.png +0 -0
  50. package/public/assets/furniture/HANGING_PLANT/manifest.json +13 -0
  51. package/public/assets/furniture/LARGE_PAINTING/LARGE_PAINTING.png +0 -0
  52. package/public/assets/furniture/LARGE_PAINTING/manifest.json +13 -0
  53. package/public/assets/furniture/LARGE_PLANT/LARGE_PLANT.png +0 -0
  54. package/public/assets/furniture/LARGE_PLANT/manifest.json +13 -0
  55. package/public/assets/furniture/PC/PC_BACK.png +0 -0
  56. package/public/assets/furniture/PC/PC_FRONT_OFF.png +0 -0
  57. package/public/assets/furniture/PC/PC_FRONT_ON_1.png +0 -0
  58. package/public/assets/furniture/PC/PC_FRONT_ON_2.png +0 -0
  59. package/public/assets/furniture/PC/PC_FRONT_ON_3.png +0 -0
  60. package/public/assets/furniture/PC/PC_SIDE.png +0 -0
  61. package/public/assets/furniture/PC/manifest.json +88 -0
  62. package/public/assets/furniture/PLANT/PLANT.png +0 -0
  63. package/public/assets/furniture/PLANT/manifest.json +13 -0
  64. package/public/assets/furniture/PLANT_2/PLANT_2.png +0 -0
  65. package/public/assets/furniture/PLANT_2/manifest.json +13 -0
  66. package/public/assets/furniture/POT/POT.png +0 -0
  67. package/public/assets/furniture/POT/manifest.json +13 -0
  68. package/public/assets/furniture/SMALL_PAINTING/SMALL_PAINTING.png +0 -0
  69. package/public/assets/furniture/SMALL_PAINTING/manifest.json +13 -0
  70. package/public/assets/furniture/SMALL_PAINTING_2/SMALL_PAINTING_2.png +0 -0
  71. package/public/assets/furniture/SMALL_PAINTING_2/manifest.json +13 -0
  72. package/public/assets/furniture/SMALL_TABLE/SMALL_TABLE_FRONT.png +0 -0
  73. package/public/assets/furniture/SMALL_TABLE/SMALL_TABLE_SIDE.png +0 -0
  74. package/public/assets/furniture/SMALL_TABLE/manifest.json +33 -0
  75. package/public/assets/furniture/SOFA/SOFA_BACK.png +0 -0
  76. package/public/assets/furniture/SOFA/SOFA_FRONT.png +0 -0
  77. package/public/assets/furniture/SOFA/SOFA_SIDE.png +0 -0
  78. package/public/assets/furniture/SOFA/manifest.json +44 -0
  79. package/public/assets/furniture/TABLE_FRONT/TABLE_FRONT.png +0 -0
  80. package/public/assets/furniture/TABLE_FRONT/manifest.json +13 -0
  81. package/public/assets/furniture/WHITEBOARD/WHITEBOARD.png +0 -0
  82. package/public/assets/furniture/WHITEBOARD/manifest.json +13 -0
  83. package/public/assets/furniture/WOODEN_BENCH/WOODEN_BENCH.png +0 -0
  84. package/public/assets/furniture/WOODEN_BENCH/manifest.json +13 -0
  85. package/public/assets/furniture/WOODEN_CHAIR/WOODEN_CHAIR_BACK.png +0 -0
  86. package/public/assets/furniture/WOODEN_CHAIR/WOODEN_CHAIR_FRONT.png +0 -0
  87. package/public/assets/furniture/WOODEN_CHAIR/WOODEN_CHAIR_SIDE.png +0 -0
  88. package/public/assets/furniture/WOODEN_CHAIR/manifest.json +44 -0
  89. package/public/assets/walls/wall_0.png +0 -0
  90. package/scripts/setup.ts +158 -0
  91. package/server.ts +53 -0
  92. package/src/app/api/focus-terminal/route.ts +65 -0
  93. package/src/app/api/hooks/notification/route.ts +19 -0
  94. package/src/app/api/hooks/post-tool-use/route.ts +26 -0
  95. package/src/app/api/hooks/pre-tool-use/route.ts +189 -0
  96. package/src/app/api/hooks/session-end/route.ts +31 -0
  97. package/src/app/api/hooks/session-start/route.ts +47 -0
  98. package/src/app/api/hooks/stop/route.ts +19 -0
  99. package/src/app/api/hooks/user-prompt/route.ts +92 -0
  100. package/src/app/favicon.ico +0 -0
  101. package/src/app/globals.css +14 -0
  102. package/src/app/layout.tsx +21 -0
  103. package/src/app/page.tsx +5 -0
  104. package/src/components/ApprovalToast.tsx +132 -0
  105. package/src/components/OfficeCanvas.tsx +311 -0
  106. package/src/components/Terminal.tsx +177 -0
  107. package/src/components/TerminalTile.tsx +181 -0
  108. package/src/components/WorkerPanel.tsx +261 -0
  109. package/src/components/WorkerPopup.tsx +116 -0
  110. package/src/game/asset-loader.ts +172 -0
  111. package/src/game/office-layout.ts +287 -0
  112. package/src/game/renderer.ts +369 -0
  113. package/src/game/sprites.ts +133 -0
  114. package/src/game/worker-entity.ts +219 -0
  115. package/src/hooks/usePixelOffice.ts +318 -0
  116. package/src/hooks/useRecentCwds.ts +27 -0
  117. package/src/lib/approval-queue.ts +67 -0
  118. package/src/lib/pty-manager.ts +267 -0
  119. package/src/lib/store.ts +181 -0
  120. package/src/lib/tool-classifier.ts +224 -0
  121. package/src/lib/transcript.ts +109 -0
  122. package/src/lib/types.ts +58 -0
  123. package/src/lib/ws-server.ts +270 -0
  124. package/tsconfig.json +34 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nedas Mackevičius
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,136 @@
1
+ # Pixel Office
2
+
3
+ A real-time pixel art visualization of your [Claude Code](https://docs.anthropic.com/en/docs/claude-code) sessions. Each terminal running Claude Code appears as a worker sitting at a desk in a tiny office. You're the boss — watch them work, approve risky operations, and click workers to see what they're doing.
4
+
5
+ ![Pixel Office](https://img.shields.io/badge/status-alpha-orange) ![License](https://img.shields.io/badge/license-MIT-blue)
6
+
7
+ ## Quick start
8
+
9
+ ```bash
10
+ npx pixel-office
11
+ ```
12
+
13
+ That's it. First run automatically registers Claude Code hooks, starts the server, and opens your browser. Start a Claude Code session in any terminal — a worker appears at a desk.
14
+
15
+ ## How it works
16
+
17
+ ```
18
+ Claude Code Terminal → hooks (curl POST) → Pixel Office server
19
+ → classify tool risk → auto-approve safe ops, block risky ones
20
+ → WebSocket → browser renders workers on canvas
21
+ → you approve/deny → Claude Code continues
22
+ ```
23
+
24
+ Each Claude Code session registers as a pixel art worker via [Claude Code hooks](https://docs.anthropic.com/en/docs/claude-code/hooks). Workers animate at their desks — reading, typing, or waiting for your approval. Risky operations (git push, rm, sudo) require you to approve or deny from the UI before Claude can continue.
25
+
26
+ ## Features
27
+
28
+ - **Real-time visualization** — workers appear, animate, and leave as Claude Code sessions start and end
29
+ - **Approval flow** — risky Bash commands (rm, sudo, git push) block until you approve/deny from the browser
30
+ - **Tool classification** — AST-based Bash parsing via `shfmt` to accurately classify compound commands
31
+ - **Multi-session** — run multiple Claude Code terminals, each gets their own desk
32
+ - **Break room** — idle workers walk to the break room and return when new work arrives
33
+ - **Worker panel** — click any worker to see their current tool, session info, and recent activity
34
+
35
+ ## Prerequisites
36
+
37
+ - **Node.js** 20+
38
+ - **Claude Code** CLI installed and working
39
+ - **shfmt** (optional, for AST-based Bash command classification — falls back to whitespace splitting)
40
+ ```bash
41
+ brew install shfmt # macOS
42
+ ```
43
+
44
+ ## Setup from source
45
+
46
+ If you want to develop or customize Pixel Office:
47
+
48
+ ```bash
49
+ git clone https://github.com/nedas-dev/pixel-office.git
50
+ cd pixel-office
51
+ npm install
52
+ npm run dev
53
+ ```
54
+
55
+ On first `npm start`, hooks are registered automatically. Or manage them manually:
56
+
57
+ ```bash
58
+ npm run setup # Register hooks in ~/.claude/settings.json
59
+ npm run uninstall # Remove all Pixel Office hooks
60
+ ```
61
+
62
+ ## Commands
63
+
64
+ ```bash
65
+ npm run dev # Dev server (custom Node server with WebSocket)
66
+ npm run build # Production build
67
+ npm start # Start server (auto-registers hooks on first run)
68
+ npm run setup # Manually register hooks
69
+ npm run uninstall # Remove all Pixel Office hooks
70
+ npm run lint # ESLint
71
+ ```
72
+
73
+ ## Architecture
74
+
75
+ **Stack:** Next.js 16 · React 19 · TypeScript · WebSocket · Canvas · Tailwind CSS 4
76
+
77
+ The app uses a custom Node server (`server.ts`) because Next.js doesn't natively support WebSocket. All state lives on `globalThis` to survive HMR re-evaluation.
78
+
79
+ ```
80
+ src/
81
+ ├── app/api/hooks/ # 6 hook endpoints matching Claude Code events
82
+ ├── lib/ # Server: session store, WebSocket, tool classifier, approval queue
83
+ ├── game/ # Canvas engine: office layout, renderer, sprites, pathfinding
84
+ ├── hooks/ # React hooks (game loop, WebSocket, approval state)
85
+ └── components/ # React overlays (canvas, approval toasts, worker panel)
86
+ ```
87
+
88
+ ### Tool classification
89
+
90
+ Tools are classified into tiers:
91
+ - **Auto-approve:** Read, Grep, Glob, Edit, Write, safe Bash commands, safe git subcommands
92
+ - **Ask boss:** git push, rm, sudo, npm install, unknown Bash commands
93
+ - **Bash parsing:** Uses `shfmt --tojson` for AST-based analysis of compound commands; falls back to whitespace splitting
94
+
95
+ ### Canvas rendering
96
+
97
+ - Logical resolution: 1280×960 (20×15 tiles, 16px, 4× scale)
98
+ - Painter's algorithm: floors → walls → furniture (Y-sorted) → workers (Y-sorted) → speech bubbles
99
+ - Pixelated rendering with `image-rendering: pixelated`
100
+
101
+ ### Worker states
102
+
103
+ `idle` → `typing` → `reading` → `waiting` (needs approval) → `walking` → `leaving`
104
+
105
+ Workers animate at desks based on current tool type. Idle workers walk to the break room and return when new work arrives. Leaving workers walk to the door, then get removed.
106
+
107
+ ## Hooks reference
108
+
109
+ Pixel Office uses 7 Claude Code hooks. All are fire-and-forget except `PreToolUse`, which blocks (up to 30s) for approval on risky operations.
110
+
111
+ | Hook | Endpoint | Blocking | Purpose |
112
+ |------|----------|----------|---------|
113
+ | `SessionStart` | `/api/hooks/session-start` | No | Register worker at desk |
114
+ | `PreToolUse` | `/api/hooks/pre-tool-use` | **Yes (30s)** | Classify tool, block if risky |
115
+ | `PostToolUse` | `/api/hooks/post-tool-use` | No | Update worker state |
116
+ | `Notification` | `/api/hooks/notification` | No | Show notification bubble |
117
+ | `UserPromptSubmit` | `/api/hooks/user-prompt` | No | Update worker's current focus |
118
+ | `Stop` | `/api/hooks/stop` | No | Set worker idle |
119
+ | `SessionEnd` | `/api/hooks/session-end` | No | Remove worker from office |
120
+
121
+ ## Environment variable
122
+
123
+ Set `PIXEL_OFFICE_HAIKU=1` in any Claude Code session to skip Pixel Office hooks for that session (useful for lightweight/scripted runs).
124
+
125
+ ## Publishing
126
+
127
+ ```bash
128
+ npm login
129
+ npm publish
130
+ ```
131
+
132
+ After publishing, anyone can run `npx pixel-office` to start.
133
+
134
+ ## License
135
+
136
+ [MIT](LICENSE)
package/bin.sh ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env bash
2
+ # Pixel Office CLI entry point
3
+ SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "$0" 2>/dev/null || realpath "$0" 2>/dev/null || echo "$0")")" && pwd)"
4
+
5
+ # When installed via npm, the actual package is one level up from .bin
6
+ if [ -f "$SCRIPT_DIR/../bin.ts" ]; then
7
+ PKG_DIR="$SCRIPT_DIR/.."
8
+ elif [ -f "$SCRIPT_DIR/bin.ts" ]; then
9
+ PKG_DIR="$SCRIPT_DIR"
10
+ else
11
+ # npm link / global install: resolve from the symlink
12
+ REAL_PATH="$(readlink -f "$0" 2>/dev/null || python3 -c "import os; print(os.path.realpath('$0'))" 2>/dev/null)"
13
+ PKG_DIR="$(dirname "$REAL_PATH")/.."
14
+ fi
15
+
16
+ exec npx tsx "$PKG_DIR/bin.ts" "$@"
package/bin.ts ADDED
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { homedir } from 'os';
5
+ import { exec } from 'child_process';
6
+
7
+ const SETTINGS_PATH = join(homedir(), '.claude', 'settings.json');
8
+ const CLAUDE_DIR = join(homedir(), '.claude');
9
+ const BASE_URL = 'http://localhost:3000/api/hooks';
10
+ const MARKER = 'pixel-office';
11
+
12
+ // ---------- Hook registration (inlined from scripts/setup.ts) ----------
13
+
14
+ const TTY_PREFIX = [
15
+ '[ "$PIXEL_OFFICE_HAIKU" = "1" ] && exit 0',
16
+ 'RAW_TTY=$(ps -o tty= -p $PPID 2>/dev/null | tr -d \' \')',
17
+ '[ -n "$RAW_TTY" ] && [ "$RAW_TTY" != "??" ] && TTY="/dev/$RAW_TTY" || TTY=""',
18
+ 'INPUT=$(cat)',
19
+ ].join('; ');
20
+
21
+ const SIMPLE_PREFIX = '[ "$PIXEL_OFFICE_HAIKU" = "1" ] && exit 0; INPUT=$(cat)';
22
+
23
+ function curlCmd(endpoint: string, maxTime: number, enrichTty: boolean): string {
24
+ if (enrichTty) {
25
+ const body = `$(echo "$INPUT" | jq -c --arg tty "$TTY" '. + {tty: $tty}' 2>/dev/null || echo "$INPUT")`;
26
+ return `${TTY_PREFIX}; curl -sf -X POST ${BASE_URL}/${endpoint} -H 'Content-Type: application/json' -d "${body}" --max-time ${maxTime} 2>/dev/null || true`;
27
+ }
28
+ return `${SIMPLE_PREFIX}; curl -sf -X POST ${BASE_URL}/${endpoint} -H 'Content-Type: application/json' -d "$INPUT" --max-time ${maxTime} 2>/dev/null || true`;
29
+ }
30
+
31
+ interface HookEntry {
32
+ matcher?: string;
33
+ hooks: { type: string; command: string; timeout: number }[];
34
+ }
35
+
36
+ const PIXEL_OFFICE_HOOKS: Record<string, HookEntry> = {
37
+ SessionStart: { hooks: [{ type: 'command', command: curlCmd('session-start', 5, true), timeout: 5 }] },
38
+ PreToolUse: { hooks: [{ type: 'command', command: curlCmd('pre-tool-use', 30, true), timeout: 30 }] },
39
+ PostToolUse: { hooks: [{ type: 'command', command: curlCmd('post-tool-use', 5, false), timeout: 5 }] },
40
+ Notification: { hooks: [{ type: 'command', command: curlCmd('notification', 5, false), timeout: 5 }] },
41
+ UserPromptSubmit: { hooks: [{ type: 'command', command: curlCmd('user-prompt', 5, false), timeout: 5 }] },
42
+ Stop: { hooks: [{ type: 'command', command: curlCmd('stop', 5, false), timeout: 5 }] },
43
+ SessionEnd: { hooks: [{ type: 'command', command: curlCmd('session-end', 5, false), timeout: 5 }] },
44
+ };
45
+
46
+ function hooksRegistered(): boolean {
47
+ if (!existsSync(SETTINGS_PATH)) return false;
48
+ try {
49
+ const settings = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8'));
50
+ const hooks = settings.hooks ?? {};
51
+ return Object.values(hooks).some((entries) =>
52
+ (entries as HookEntry[]).some((entry) =>
53
+ entry.hooks?.some((h) => typeof h.command === 'string' && h.command.includes(MARKER))
54
+ )
55
+ );
56
+ } catch {
57
+ return false;
58
+ }
59
+ }
60
+
61
+ function registerHooks(): void {
62
+ mkdirSync(CLAUDE_DIR, { recursive: true });
63
+ const settings = existsSync(SETTINGS_PATH)
64
+ ? JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8'))
65
+ : {};
66
+ const hooks = settings.hooks ?? {};
67
+
68
+ for (const [event, entry] of Object.entries(PIXEL_OFFICE_HOOKS)) {
69
+ const existing = hooks[event] ?? [];
70
+ const alreadyHas = existing.some((e: HookEntry) =>
71
+ e.hooks?.some((h: { command: string }) => h.command.includes(MARKER))
72
+ );
73
+ if (!alreadyHas) {
74
+ hooks[event] = [...existing, entry];
75
+ }
76
+ }
77
+
78
+ settings.hooks = hooks;
79
+ writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2) + '\n');
80
+ }
81
+
82
+ // ---------- Main ----------
83
+
84
+ function openBrowser(url: string): void {
85
+ const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
86
+ exec(`${cmd} ${url}`);
87
+ }
88
+
89
+ async function main(): Promise<void> {
90
+ console.log('');
91
+ console.log(' ┌──────────────────────────┐');
92
+ console.log(' │ Pixel Office │');
93
+ console.log(' └──────────────────────────┘');
94
+ console.log('');
95
+
96
+ // Auto-register hooks on first run
97
+ if (!hooksRegistered()) {
98
+ console.log(' First run — registering Claude Code hooks...');
99
+ registerHooks();
100
+ console.log(' ✓ Hooks registered in ~/.claude/settings.json');
101
+ console.log('');
102
+ }
103
+
104
+ // Start the server
105
+ const { createServer } = await import('http');
106
+ const next = (await import('next')).default;
107
+ const { initWSS, broadcast } = await import('./src/lib/ws-server');
108
+ const { cleanupStaleSessions, getAllSessions } = await import('./src/lib/store');
109
+ const { getAllPtyEntries, killPty } = await import('./src/lib/pty-manager');
110
+
111
+ const dev = process.env.NODE_ENV !== 'production';
112
+ const port = parseInt(process.env.PORT || '3000', 10);
113
+
114
+ const app = next({ dev, dir: __dirname });
115
+ const handle = app.getRequestHandler();
116
+
117
+ await app.prepare();
118
+
119
+ const server = createServer((req, res) => {
120
+ handle(req, res);
121
+ });
122
+
123
+ initWSS(server);
124
+
125
+ // Stale session cleanup (same as server.ts)
126
+ if (!globalThis.__staleSessionCleanup) {
127
+ globalThis.__staleSessionCleanup = setInterval(() => {
128
+ const isAlivePty = (ptyId: string) => {
129
+ const e = getAllPtyEntries().find((p) => p.ptyId === ptyId);
130
+ return !!e && !e.exited;
131
+ };
132
+ const removed = cleanupStaleSessions(30 * 60_000, isAlivePty);
133
+ for (const sessionId of removed) {
134
+ console.log(`[cleanup] Removed stale session: ${sessionId}`);
135
+ broadcast({ type: 'session-remove', sessionId });
136
+ }
137
+ const activePtyIds = new Set(
138
+ getAllSessions()
139
+ .filter((s) => s.ptyId)
140
+ .map((s) => s.ptyId!)
141
+ );
142
+ for (const entry of getAllPtyEntries()) {
143
+ if (!activePtyIds.has(entry.ptyId) && entry.exited) {
144
+ killPty(entry.ptyId);
145
+ console.log(`[cleanup] Removed orphaned PTY: ${entry.ptyId}`);
146
+ }
147
+ }
148
+ }, 5 * 60_000);
149
+ }
150
+
151
+ server.listen(port, () => {
152
+ console.log(` ✓ Ready on http://localhost:${port}`);
153
+ console.log(' Open a Claude Code terminal — a worker will appear.');
154
+ console.log('');
155
+ openBrowser(`http://localhost:${port}`);
156
+ });
157
+ }
158
+
159
+ main().catch((err) => {
160
+ console.error('Failed to start Pixel Office:', err);
161
+ process.exit(1);
162
+ });
package/next-env.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+ import "./.next/dev/types/routes.d.ts";
4
+
5
+ // NOTE: This file should not be edited
6
+ // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
package/next.config.ts ADDED
@@ -0,0 +1,7 @@
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {
4
+ serverExternalPackages: ['node-pty'],
5
+ };
6
+
7
+ export default nextConfig;
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@neolio42/pixel-office",
3
+ "version": "0.1.0",
4
+ "description": "Visualize your Claude Code sessions as pixel art workers in a tiny office",
5
+ "author": "Nedas Mackevičius",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/nedas-dev/pixel-office.git"
10
+ },
11
+ "keywords": [
12
+ "claude",
13
+ "claude-code",
14
+ "pixel-art",
15
+ "visualization",
16
+ "hooks",
17
+ "developer-tools"
18
+ ],
19
+ "bin": {
20
+ "pixel-office": "./bin.sh"
21
+ },
22
+ "scripts": {
23
+ "dev": "npx tsx server.ts",
24
+ "build": "next build",
25
+ "start": "npx tsx bin.ts",
26
+ "lint": "eslint",
27
+ "setup": "npx tsx scripts/setup.ts",
28
+ "uninstall": "npx tsx scripts/setup.ts uninstall"
29
+ },
30
+ "dependencies": {
31
+ "@xterm/addon-fit": "^0.11.0",
32
+ "@xterm/xterm": "^6.0.0",
33
+ "next": "16.1.6",
34
+ "node-pty": "^1.2.0-beta.12",
35
+ "react": "19.2.3",
36
+ "react-dom": "19.2.3",
37
+ "tsx": "^4.21.0",
38
+ "ws": "^8.19.0"
39
+ },
40
+ "devDependencies": {
41
+ "@tailwindcss/postcss": "^4",
42
+ "@types/node": "^20",
43
+ "@types/react": "^19",
44
+ "@types/react-dom": "^19",
45
+ "@types/ws": "^8.18.1",
46
+ "eslint": "^9",
47
+ "eslint-config-next": "16.1.6",
48
+ "tailwindcss": "^4",
49
+ "typescript": "^5.9.3"
50
+ }
51
+ }
@@ -0,0 +1,7 @@
1
+ const config = {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ };
6
+
7
+ export default config;
Binary file
@@ -0,0 +1,92 @@
1
+ {
2
+ "version": 1,
3
+ "cols": 21,
4
+ "rows": 22,
5
+ "layoutRevision": 1,
6
+ "tiles": [
7
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
8
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
9
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
10
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
11
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
12
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
13
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
14
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
15
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
16
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
17
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255,
18
+ 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 255,
19
+ 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 255,
20
+ 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 255,
21
+ 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 255,
22
+ 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 255,
23
+ 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 255,
24
+ 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 255,
25
+ 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 255,
26
+ 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 9, 9, 9, 9, 9, 9, 9, 9, 0, 255,
27
+ 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 9, 9, 9, 9, 9, 9, 9, 9, 0, 255,
28
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
29
+ ],
30
+ "tileColors": [
31
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
32
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
33
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
34
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
35
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
36
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
37
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
38
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
39
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
40
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
41
+ {"h":214,"s":30,"b":-100,"c":-55}, {"h":214,"s":30,"b":-100,"c":-55}, {"h":214,"s":30,"b":-100,"c":-55}, {"h":214,"s":30,"b":-100,"c":-55}, {"h":214,"s":30,"b":-100,"c":-55}, {"h":214,"s":30,"b":-100,"c":-55}, {"h":214,"s":30,"b":-100,"c":-55}, {"h":214,"s":30,"b":-100,"c":-55}, {"h":214,"s":30,"b":-100,"c":-55}, {"h":214,"s":30,"b":-100,"c":-55}, {"h":214,"s":30,"b":-100,"c":-55}, {"h":214,"s":30,"b":-100,"c":-55}, {"h":214,"s":30,"b":-100,"c":-55}, {"h":214,"s":30,"b":-100,"c":-55}, {"h":214,"s":30,"b":-100,"c":-55}, {"h":214,"s":30,"b":-100,"c":-55}, {"h":214,"s":30,"b":-100,"c":-55}, {"h":214,"s":30,"b":-100,"c":-55}, {"h":214,"s":30,"b":-100,"c":-55}, {"h":214,"s":30,"b":-100,"c":-55}, null,
42
+ {"h":214,"s":30,"b":-100,"c":-55}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":214,"s":30,"b":-100,"c":-55}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":214,"s":30,"b":-100,"c":-55}, null,
43
+ {"h":214,"s":30,"b":-100,"c":-55}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":214,"s":30,"b":-100,"c":-55}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":214,"s":30,"b":-100,"c":-55}, null,
44
+ {"h":214,"s":30,"b":-100,"c":-55}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":214,"s":30,"b":-100,"c":-55}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":214,"s":30,"b":-100,"c":-55}, null,
45
+ {"h":214,"s":30,"b":-100,"c":-55}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":214,"s":30,"b":-100,"c":-55}, null,
46
+ {"h":214,"s":30,"b":-100,"c":-55}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":214,"s":30,"b":-100,"c":-55}, null,
47
+ {"h":214,"s":30,"b":-100,"c":-55}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":214,"s":30,"b":-100,"c":-55}, null,
48
+ {"h":214,"s":30,"b":-100,"c":-55}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":214,"s":30,"b":-100,"c":-55}, null,
49
+ {"h":214,"s":30,"b":-100,"c":-55}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":214,"s":30,"b":-100,"c":-55}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":209,"s":39,"b":-25,"c":-80}, {"h":214,"s":30,"b":-100,"c":-55}, null,
50
+ {"h":214,"s":30,"b":-100,"c":-55}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":214,"s":30,"b":-100,"c":-55}, {"h":209,"s":0,"b":-16,"c":-8}, {"h":209,"s":0,"b":-16,"c":-8}, {"h":209,"s":0,"b":-16,"c":-8}, {"h":209,"s":0,"b":-16,"c":-8}, {"h":209,"s":0,"b":-16,"c":-8}, {"h":209,"s":0,"b":-16,"c":-8}, {"h":209,"s":0,"b":-16,"c":-8}, {"h":209,"s":0,"b":-16,"c":-8}, {"h":214,"s":30,"b":-100,"c":-55}, null,
51
+ {"h":214,"s":30,"b":-100,"c":-55}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":25,"s":48,"b":-43,"c":-88}, {"h":214,"s":30,"b":-100,"c":-55}, {"h":209,"s":0,"b":-16,"c":-8}, {"h":209,"s":0,"b":-16,"c":-8}, {"h":209,"s":0,"b":-16,"c":-8}, {"h":209,"s":0,"b":-16,"c":-8}, {"h":209,"s":0,"b":-16,"c":-8}, {"h":209,"s":0,"b":-16,"c":-8}, {"h":209,"s":0,"b":-16,"c":-8}, {"h":209,"s":0,"b":-16,"c":-8}, {"h":214,"s":30,"b":-100,"c":-55}, null,
52
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null
53
+ ],
54
+ "furniture": [
55
+ {"uid": "f-1773353910654-5cdg", "type": "TABLE_FRONT", "col": 4, "row": 16},
56
+ {"uid": "f-1773354646615-jhxl", "type": "COFFEE_TABLE", "col": 14, "row": 14},
57
+ {"uid": "f-1773354664329-hxsh", "type": "SOFA_SIDE", "col": 13, "row": 14},
58
+ {"uid": "f-1773354665989-zgrw", "type": "SOFA_BACK", "col": 14, "row": 16},
59
+ {"uid": "f-1773354668333-lo7w", "type": "SOFA_FRONT", "col": 14, "row": 13},
60
+ {"uid": "f-1773354670818-r1q2", "type": "SOFA_SIDE:left", "col": 16, "row": 14},
61
+ {"uid": "f-1773354686967-yiua", "type": "HANGING_PLANT", "col": 9, "row": 9},
62
+ {"uid": "f-1773354687677-hn2k", "type": "HANGING_PLANT", "col": 1, "row": 9},
63
+ {"uid": "f-1773354693077-f7aj", "type": "DOUBLE_BOOKSHELF", "col": 7, "row": 9},
64
+ {"uid": "f-1773354700513-f1zs", "type": "DOUBLE_BOOKSHELF", "col": 2, "row": 9},
65
+ {"uid": "f-1773354799984-j5ri", "type": "SMALL_PAINTING", "col": 12, "row": 9},
66
+ {"uid": "f-1773354827151-yox2", "type": "CLOCK", "col": 5, "row": 9},
67
+ {"uid": "f-1773354842615-f5md", "type": "PLANT", "col": 18, "row": 10},
68
+ {"uid": "f-1773354861273-67uo", "type": "COFFEE", "col": 14, "row": 15},
69
+ {"uid": "f-1773354877474-kt9s", "type": "WOODEN_CHAIR_SIDE", "col": 3, "row": 18},
70
+ {"uid": "f-1773354879805-px9b", "type": "WOODEN_CHAIR_SIDE", "col": 3, "row": 16},
71
+ {"uid": "f-1773354880309-yphd", "type": "WOODEN_CHAIR_SIDE:left", "col": 7, "row": 16},
72
+ {"uid": "f-1773354881902-9m50", "type": "WOODEN_CHAIR_SIDE:left", "col": 7, "row": 18},
73
+ {"uid": "f-1773354931010-8vvr", "type": "DESK_FRONT", "col": 2, "row": 12},
74
+ {"uid": "f-1773354932396-5uus", "type": "DESK_FRONT", "col": 6, "row": 12},
75
+ {"uid": "f-1773356768339-eo6u", "type": "CUSHIONED_BENCH", "col": 3, "row": 14},
76
+ {"uid": "f-1773356769007-a8jm", "type": "CUSHIONED_BENCH", "col": 7, "row": 14},
77
+ {"uid": "f-1773356781294-b69z", "type": "PC_FRONT_OFF", "col": 7, "row": 12},
78
+ {"uid": "f-1773356782055-vp70", "type": "PC_FRONT_OFF", "col": 3, "row": 12},
79
+ {"uid": "f-1773356784581-5jw9", "type": "PC_SIDE", "col": 4, "row": 16},
80
+ {"uid": "f-1773356785458-pyjn", "type": "PC_SIDE", "col": 4, "row": 18},
81
+ {"uid": "f-1773356787060-higb", "type": "PC_SIDE:left", "col": 6, "row": 16},
82
+ {"uid": "f-1773356787744-ykrz", "type": "PC_SIDE:left", "col": 6, "row": 18},
83
+ {"uid": "f-1773356878781-rncl", "type": "PLANT_2", "col": 11, "row": 10},
84
+ {"uid": "f-1773356974812-apra", "type": "LARGE_PAINTING", "col": 14, "row": 9},
85
+ {"uid": "f-1773357087399-3kfy", "type": "BIN", "col": 2, "row": 20},
86
+ {"uid": "f-1773357989802-thws", "type": "SMALL_TABLE_FRONT", "col": 17, "row": 19},
87
+ {"uid": "f-1773358001163-aqv4", "type": "SMALL_TABLE_SIDE", "col": 1, "row": 18},
88
+ {"uid": "f-1773358458100-4wm2", "type": "COFFEE", "col": 1, "row": 19},
89
+ {"uid": "f-1773358479734-biia", "type": "PLANT_2", "col": 1, "row": 17},
90
+ {"uid": "f-1773358485454-id8j", "type": "SMALL_PAINTING_2", "col": 17, "row": 9}
91
+ ]
92
+ }
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,13 @@
1
+ {
2
+ "id": "BIN",
3
+ "name": "Bin",
4
+ "category": "misc",
5
+ "type": "asset",
6
+ "canPlaceOnWalls": false,
7
+ "canPlaceOnSurfaces": false,
8
+ "backgroundTiles": 0,
9
+ "width": 16,
10
+ "height": 16,
11
+ "footprintW": 1,
12
+ "footprintH": 1
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "id": "BOOKSHELF",
3
+ "name": "Bookshelf",
4
+ "category": "wall",
5
+ "type": "asset",
6
+ "canPlaceOnWalls": true,
7
+ "canPlaceOnSurfaces": false,
8
+ "backgroundTiles": 0,
9
+ "width": 32,
10
+ "height": 16,
11
+ "footprintW": 2,
12
+ "footprintH": 1
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "id": "CACTUS",
3
+ "name": "Cactus",
4
+ "category": "decor",
5
+ "type": "asset",
6
+ "canPlaceOnWalls": false,
7
+ "canPlaceOnSurfaces": false,
8
+ "backgroundTiles": 1,
9
+ "width": 16,
10
+ "height": 32,
11
+ "footprintW": 1,
12
+ "footprintH": 2
13
+ }