opencode-pixel-office 1.0.11 → 1.2.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.
package/README.md CHANGED
@@ -1,150 +1,125 @@
1
1
  # Pixel Office
2
2
 
3
- Pixel Office is an OpenCode plugin that visualizes agent activity (LLM thoughts, tool usage, file modifications) in a live, retro-style office scene.
4
- <img width="1455" height="824" alt="Screenshot 2026-02-04 at 7 03 58 PM" src="https://github.com/user-attachments/assets/e20e2e68-a032-4747-a027-aacca0f274e5" />
5
- https://www.npmjs.com/package/opencode-pixel-office
6
-
7
- ## Overview
8
-
9
- The system consists of three main parts:
10
- 1. **OpenCode Plugin**: Captures events from the IDE/Agent execution.
11
- 2. **Server**: A Node.js Express/WebSocket server that aggregates state.
12
- 3. **Client**: A React + PixiJS dashboard that renders the "Office" and "HUD".
13
-
14
- ## System Architecture
15
-
16
- ```mermaid
17
- graph TD
18
- A[OpenCode IDE] -->|Plugin Events via HTTP| B(Pixel Office Server :5100)
19
- B -->|Broadcast State via WebSocket| C[React Client]
20
- C -->|Render| D[PixiJS Scene]
21
- C -->|Render| E[HUD / Sidebar]
22
-
23
- subgraph Server
24
- B1[Event Ingestion /events]
25
- B2[State Manager]
26
- B3[WebSocket Server]
27
- B1 --> B2 --> B3
28
- end
29
- ```
3
+ A pixel-art office visualization for AI coding assistants. Works with both **OpenCode** and **Claude Code** simultaneously.
30
4
 
31
- ## Project Structure
5
+ <img width="1455" height="824" alt="Pixel Office Screenshot" src="https://github.com/user-attachments/assets/e20e2e68-a032-4747-a027-aacca0f274e5" />
32
6
 
33
- ```text
34
- pixel-opencode/
35
- ├── client/ # Frontend Dashboard
36
- │ ├── src/
37
- │ │ ├── components/ # UI Components (HUD, Panels)
38
- │ │ ├── PixiScene.tsx # Core Game Loop & Rendering (PixiJS)
39
- │ │ ├── useOfficeState.ts # State Management & WebSocket Hook
40
- │ │ └── styles.css # Retro Theme Styles (Tailwind + CSS Variables)
41
- │ └── vite.config.ts # Client Build Config
42
- ├── server/ # Backend
43
- │ └── index.ts # Express App & Event Handling
44
- ├── plugin/ # OpenCode Integration
45
- │ └── pixel-office.js # Script to forward IDE events
46
- └── bin/ # Executable binaries
47
- ```
7
+ [![npm version](https://img.shields.io/npm/v/opencode-pixel-office.svg)](https://www.npmjs.com/package/opencode-pixel-office)
48
8
 
49
- ## Key Components
9
+ ## Features
50
10
 
51
- ### 1. `PixiScene.tsx`
52
- The heart of the visualization. It handles:
53
- - **Sprite Management**: Rendering agents, furniture, and effects.
54
- - **Pathfinding**: A* algorithm for agent movement (using `pathfinding.ts`).
55
- - **Game Loop**: Updates positions and animations at 60fps.
11
+ - **Dual Support**: Works with both OpenCode and Claude Code CLI
12
+ - **Live Visualization**: Real-time pixel-art office showing agent activity
13
+ - **Tabbed Interface**: Switch between OpenCode Office and Claude Office views
14
+ - **Mobile Ready**: Connect via local network + QR code
15
+ - **Session Tracking**: Monitor multiple sessions across different repos
56
16
 
57
- ### 2. `useOfficeState.ts`
58
- A custom React hook that:
59
- - Maintains the WebSocket connection.
60
- - Merges incoming event deltas into the current `OfficeState`.
61
- - Manage active sessions, todos, and agent statuses.
17
+ ## Quick Start
62
18
 
63
- ### 3. `plugin/pixel-office.js`
64
- A standard OpenCode plugin script that hooks into the event bus:
65
- - `agent` events (thoughts, tools)
66
- - `session` events (status changes)
67
- - `fs` events (file modifications)
19
+ ```bash
20
+ # Install globally
21
+ npm install -g opencode-pixel-office
68
22
 
69
- ## 📱 Mobile Support
23
+ # Install for both OpenCode and Claude Code
24
+ opencode-pixel-office install
70
25
 
71
- Monitor your agents from your phone or tablet!
26
+ # Start the server
27
+ opencode-pixel-office start
28
+ ```
72
29
 
73
- - **Network Access**: The server automatically binds to your LAN IP.
74
- - **QR Code**: In the dashboard, click the Network URL in the top-right corner to reveal a QR code. Scan it to instantly connect.
75
- - **Responsive Design**: The dashboard adapts to portrait and landscape modes on mobile devices.
30
+ That's it! The dashboard opens at `http://localhost:5100`.
76
31
 
77
- <img src="https://github.com/user-attachments/assets/c5420d78-9c87-4062-b034-21ae3defd52f" width="375" alt="Mobile View" />
32
+ ## CLI Commands
78
33
 
79
- ## Installation
34
+ | Command | Description |
35
+ |---------|-------------|
36
+ | `install` | Install plugin + hooks for both OpenCode and Claude Code |
37
+ | `start` | Start the Pixel Office server |
38
+ | `stop` | Stop the server |
39
+ | `status` | Show installation status |
40
+ | `uninstall` | Remove everything |
80
41
 
81
- ### 🚀 For Users (The Easy Way)
42
+ ### Options
82
43
 
83
- 1. **Install the Global Package**:
84
- ```bash
85
- npm install -g opencode-pixel-office
86
- ```
44
+ - `--port <number>` - Set server port (default: 5100)
45
+ - `--version, -v` - Show version number
87
46
 
88
- 2. **Run the Installer**:
89
- ```bash
90
- opencode-pixel-office install
91
- ```
92
- This sets up the standalone app in `~/.opencode/pixel-office` and installs the `pixel-office.js` plugin script to `~/.opencode/plugins/`.
47
+ ## How It Works
93
48
 
94
- 3. **Start OpenCode**:
95
- Simply open your IDE. Pixel Office will auto-launch in your browser at `http://localhost:5100`.
49
+ ```
50
+ ┌─────────────┐ ┌─────────────┐
51
+ │ OpenCode │ │ Claude Code │
52
+ │ Plugin │ │ Hooks │
53
+ └──────┬──────┘ └──────┬──────┘
54
+ │ │
55
+ │ HTTP POST │
56
+ └───────┬───────────┘
57
+
58
+ ┌───────────────┐
59
+ │ Pixel Office │
60
+ │ Server │
61
+ │ :5100 │
62
+ └───────┬───────┘
63
+ │ WebSocket
64
+
65
+ ┌───────────────┐
66
+ │ Browser │
67
+ │ Dashboard │
68
+ └───────────────┘
69
+ ```
96
70
 
97
- ### CLI Commands
71
+ - **OpenCode Plugin** → `~/.opencode/plugins/pixel-office.js`
72
+ - **Claude Code Hooks** → `~/.claude/hooks/` + `~/.claude/settings.json`
73
+ - **Server/App** → `~/.opencode/pixel-office/`
74
+
75
+ ## 📱 Mobile Support
98
76
 
99
- - **Install**: `opencode-pixel-office install [--port <number>]`
100
- - **Uninstall**: `opencode-pixel-office uninstall`
101
- - **Stop Server**: `opencode-pixel-office stop` (Manually kills the server process on the configured port)
77
+ Monitor your agents from your phone!
102
78
 
103
- ### 🛠️ For Developers
79
+ 1. Click the Network URL in the top-right corner
80
+ 2. Scan the QR code
81
+ 3. Watch agents work from anywhere on your network
104
82
 
105
- If you want to modify the source code or contribute:
83
+ <img src="https://github.com/user-attachments/assets/c5420d78-9c87-4062-b034-21ae3defd52f" width="375" alt="Mobile View" />
84
+
85
+ ## Development
106
86
 
107
- #### 1. Clone & Install
108
87
  ```bash
109
- git clone https://github.com/your-username/opencode-pixel-office.git
88
+ # Clone the repo
89
+ git clone https://github.com/anthropics/opencode-pixel-office.git
110
90
  cd opencode-pixel-office
111
91
  npm install
112
- ```
113
92
 
114
- #### 2. Start the Server (Dev Mode)
115
- ```bash
93
+ # Run server (dev mode)
116
94
  npm start
117
- # Server runs on http://localhost:5100, watching for changes
118
- ```
119
95
 
120
- #### 3. Start the Client (Dev Mode)
121
- In a separate terminal:
122
- ```bash
96
+ # Run client (dev mode, separate terminal)
123
97
  npm run dev:client
124
- ```
125
98
 
126
- #### 4. Install the Plugin (Dev Mode)
127
- To use your *local* version instead of the global one:
128
- ```bash
129
- mkdir -p ~/.opencode/plugins
130
- cp plugin/pixel-office.js ~/.opencode/plugins/
99
+ # Build client for production
100
+ npm run build:client
131
101
  ```
132
- (The plugin automatically prefers your local `server/` if you are opening the `pixel-opencode` project in OpenCode.)
133
102
 
134
- #### 2. Install the Plugin (Dev Mode)
135
- Copy the plugin script to your OpenCode configuration:
136
- ```bash
137
- mkdir -p .opencode/plugins
138
- cp plugin/pixel-office.js .opencode/plugins/
139
- ```
140
- Restart OpenCode to activate.
141
-
142
- ## Development
103
+ ## Project Structure
143
104
 
144
- - **Client Dev**: `npm run dev:client` (Vite dev server)
145
- - **Server Dev**: `npm start` (Runs via `tsx`)
105
+ ```
106
+ pixel-opencode/
107
+ ├── client/ # React + PixiJS Frontend
108
+ │ └── src/
109
+ │ ├── App.tsx # Main app with tabs
110
+ │ ├── PixiScene.tsx # Pixel art rendering
111
+ │ └── useOfficeState.ts
112
+ ├── server/
113
+ │ └── index.ts # Express + WebSocket server
114
+ ├── plugin/
115
+ │ └── pixel-office.js # OpenCode plugin
116
+ └── bin/
117
+ ├── opencode-pixel-office.js # CLI
118
+ └── claude-code-hook.js # Claude Code hook
119
+ ```
146
120
 
147
121
  ## Credits
148
- - **Tileset**: [Office Tileset by DonArg](https://donarg.itch.io/officetileset)
149
- - **Icons**: Lucide React
150
- - **Engine**: PixiJS
122
+
123
+ - **Tileset**: [Office Tileset by DonArg](https://donarg.itch.io/officetileset)
124
+ - **Icons**: Lucide React
125
+ - **Engine**: PixiJS
@@ -19,22 +19,22 @@ const postEvent = async (endpoint, event) => {
19
19
  body: JSON.stringify(event),
20
20
  });
21
21
  } catch (error) {
22
- const logPath = path.join(os.homedir(), ".claude", "pixel-office-hook.log");
23
- fs.appendFileSync(logPath, `${new Date().toISOString()} ${String(error)}\n`);
22
+ // Silently ignore - server might not be running
24
23
  }
25
24
  };
26
25
 
27
- const readMode = () => {
28
- try {
29
- const configPath = path.join(os.homedir(), ".opencode", "pixel-office", "config.json");
30
- if (fs.existsSync(configPath)) {
31
- const data = JSON.parse(fs.readFileSync(configPath, "utf8"));
32
- return data.mode || "opencode";
33
- }
34
- } catch (error) {
35
- return "opencode";
36
- }
37
- return "opencode";
26
+ const getProjectName = (cwd) => {
27
+ if (!cwd) return "Claude Code";
28
+ const parts = cwd.split(path.sep).filter(Boolean);
29
+ return parts[parts.length - 1] || "Claude Code";
30
+ };
31
+
32
+ const getModelFromEnv = () => {
33
+ const model = process.env.ANTHROPIC_MODEL || process.env.CLAUDE_MODEL || "";
34
+ if (model.includes("opus")) return "claude-opus";
35
+ if (model.includes("sonnet")) return "claude-sonnet";
36
+ if (model.includes("haiku")) return "claude-haiku";
37
+ return "claude";
38
38
  };
39
39
 
40
40
  const mapHookToEvent = (input) => {
@@ -43,76 +43,169 @@ const mapHookToEvent = (input) => {
43
43
  const cwd = input.cwd || "";
44
44
  const toolName = input.tool_name || "";
45
45
  const toolInput = input.tool_input || {};
46
+ const toolOutput = input.tool_output || {};
46
47
  const prompt = input.prompt || input.user_prompt || "";
47
48
  const permission = input.permission || {};
49
+ const message = input.message || "";
50
+
51
+ const projectName = getProjectName(cwd);
52
+ const agentId = sessionId || `claude-${Date.now()}`;
53
+ const modelId = getModelFromEnv();
48
54
 
49
55
  const info = {
50
- id: sessionId || `claude-${Date.now()}`,
56
+ id: agentId,
51
57
  sessionID: sessionId,
52
58
  agent: "Claude",
53
- title: cwd || "Claude Code",
54
- model: { modelID: "claude", providerID: "anthropic" },
59
+ title: projectName,
60
+ directory: cwd,
61
+ model: {
62
+ modelID: modelId,
63
+ providerID: "anthropic",
64
+ },
55
65
  };
56
66
 
67
+ let event = null;
68
+
57
69
  switch (hook) {
58
70
  case "SessionStart":
59
- return {
71
+ event = {
60
72
  type: "session.created",
61
- properties: { info },
73
+ properties: {
74
+ info: {
75
+ ...info,
76
+ title: projectName,
77
+ slug: projectName.toLowerCase().replace(/[^a-z0-9]/g, "-"),
78
+ },
79
+ },
62
80
  };
81
+ break;
82
+
63
83
  case "SessionEnd":
64
- return {
84
+ event = {
65
85
  type: "session.deleted",
66
- properties: { sessionID: sessionId },
86
+ properties: {
87
+ sessionID: sessionId,
88
+ info,
89
+ },
67
90
  };
91
+ break;
92
+
68
93
  case "UserPromptSubmit":
69
- return {
94
+ event = {
70
95
  type: "message.updated",
71
96
  properties: {
72
97
  info: { ...info, role: "user" },
73
- message: { content: prompt, role: "user" },
98
+ message: {
99
+ id: `msg-${Date.now()}`,
100
+ content: prompt,
101
+ role: "user",
102
+ status: "pending",
103
+ },
74
104
  },
75
105
  };
106
+ break;
107
+
76
108
  case "PreToolUse":
77
- return {
109
+ event = {
78
110
  type: "tool.execute.before",
79
- properties: { info, tool: { name: toolName, input: toolInput } },
111
+ properties: {
112
+ info,
113
+ tool: { name: toolName, input: toolInput },
114
+ },
80
115
  };
116
+ break;
117
+
81
118
  case "PostToolUse":
119
+ event = {
120
+ type: "tool.execute.after",
121
+ properties: {
122
+ info,
123
+ tool: { name: toolName, input: toolInput, output: toolOutput },
124
+ status: "success",
125
+ },
126
+ };
127
+ break;
128
+
82
129
  case "PostToolUseFailure":
83
- return {
130
+ event = {
84
131
  type: "tool.execute.after",
85
- properties: { info, tool: { name: toolName, input: toolInput } },
132
+ properties: {
133
+ info,
134
+ tool: { name: toolName, input: toolInput, output: toolOutput },
135
+ status: "error",
136
+ },
86
137
  };
138
+ break;
139
+
87
140
  case "PermissionRequest":
88
- return {
141
+ event = {
89
142
  type: "permission.asked",
90
- properties: { info, permission },
143
+ properties: {
144
+ info,
145
+ permission: { ...permission, tool: toolName },
146
+ },
91
147
  };
148
+ break;
149
+
92
150
  case "Notification":
93
- return {
151
+ event = {
94
152
  type: "tui.toast.show",
95
- properties: { info, message: input.message || "" },
153
+ properties: { info, message },
96
154
  };
155
+ break;
156
+
97
157
  case "PreCompact":
98
- return {
158
+ event = {
99
159
  type: "session.compacted",
100
160
  properties: { info },
101
161
  };
162
+ break;
163
+
102
164
  case "Stop":
103
- return {
165
+ event = {
104
166
  type: "session.status",
105
- properties: { sessionID: sessionId, status: { type: "idle" } },
167
+ properties: {
168
+ sessionID: sessionId,
169
+ info,
170
+ status: { type: "idle" },
171
+ },
106
172
  };
173
+ break;
174
+
107
175
  case "SubagentStart":
108
- case "SubagentStop":
109
- return {
176
+ event = {
110
177
  type: "session.updated",
111
- properties: { info },
178
+ properties: {
179
+ info: {
180
+ ...info,
181
+ agent: input.subagent_type || "Subagent",
182
+ title: `${projectName} (${input.subagent_type || "Task"})`,
183
+ },
184
+ parentSessionId: sessionId,
185
+ },
112
186
  };
187
+ break;
188
+
189
+ case "SubagentStop":
190
+ event = {
191
+ type: "session.idle",
192
+ properties: {
193
+ sessionID: input.subagent_id || sessionId,
194
+ info,
195
+ },
196
+ };
197
+ break;
198
+
113
199
  default:
114
200
  return null;
115
201
  }
202
+
203
+ // Add source tag to identify this is from Claude Code
204
+ if (event) {
205
+ event.source = "claude";
206
+ }
207
+
208
+ return event;
116
209
  };
117
210
 
118
211
  const main = async () => {
@@ -120,15 +213,21 @@ const main = async () => {
120
213
  if (!raw) {
121
214
  process.exit(0);
122
215
  }
123
- const input = JSON.parse(raw);
124
- const endpoint = process.env.PIXEL_OFFICE_URL || "http://localhost:5100/events";
125
- if (readMode() !== "claude-code") {
216
+
217
+ let input;
218
+ try {
219
+ input = JSON.parse(raw);
220
+ } catch {
126
221
  process.exit(0);
127
222
  }
223
+
224
+ const endpoint = process.env.PIXEL_OFFICE_URL || "http://localhost:5100/events";
128
225
  const event = mapHookToEvent(input);
226
+
129
227
  if (!event) {
130
228
  process.exit(0);
131
229
  }
230
+
132
231
  await postEvent(endpoint, event);
133
232
  process.exit(0);
134
233
  };