opencode-pixel-office 1.0.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 +142 -0
- package/bin/opencode-pixel-office.js +174 -0
- package/client/dist/assets/index-Cfnbdbzw.js +1206 -0
- package/client/dist/assets/index-CocJhp5H.css +1 -0
- package/client/dist/favicon.png +0 -0
- package/client/dist/idle.png +0 -0
- package/client/dist/index.html +14 -0
- package/client/dist/jump.png +0 -0
- package/client/dist/office.png +0 -0
- package/client/dist/office_floor.png +0 -0
- package/client/dist/run.png +0 -0
- package/client/dist/walk.png +0 -0
- package/package.json +48 -0
- package/plugin/pixel-office.js +174 -0
- package/server/index.ts +971 -0
- package/server/types.d.ts +2 -0
package/README.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Pixel Office
|
|
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
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The system consists of three main parts:
|
|
8
|
+
1. **OpenCode Plugin**: Captures events from the IDE/Agent execution.
|
|
9
|
+
2. **Server**: A Node.js Express/WebSocket server that aggregates state.
|
|
10
|
+
3. **Client**: A React + PixiJS dashboard that renders the "Office" and "HUD".
|
|
11
|
+
|
|
12
|
+
## System Architecture
|
|
13
|
+
|
|
14
|
+
```mermaid
|
|
15
|
+
graph TD
|
|
16
|
+
A[OpenCode IDE] -->|Plugin Events via HTTP| B(Pixel Office Server :5100)
|
|
17
|
+
B -->|Broadcast State via WebSocket| C[React Client]
|
|
18
|
+
C -->|Render| D[PixiJS Scene]
|
|
19
|
+
C -->|Render| E[HUD / Sidebar]
|
|
20
|
+
|
|
21
|
+
subgraph Server
|
|
22
|
+
B1[Event Ingestion /events]
|
|
23
|
+
B2[State Manager]
|
|
24
|
+
B3[WebSocket Server]
|
|
25
|
+
B1 --> B2 --> B3
|
|
26
|
+
end
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Project Structure
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
pixel-opencode/
|
|
33
|
+
├── client/ # Frontend Dashboard
|
|
34
|
+
│ ├── src/
|
|
35
|
+
│ │ ├── components/ # UI Components (HUD, Panels)
|
|
36
|
+
│ │ ├── PixiScene.tsx # Core Game Loop & Rendering (PixiJS)
|
|
37
|
+
│ │ ├── useOfficeState.ts # State Management & WebSocket Hook
|
|
38
|
+
│ │ └── styles.css # Retro Theme Styles (Tailwind + CSS Variables)
|
|
39
|
+
│ └── vite.config.ts # Client Build Config
|
|
40
|
+
├── server/ # Backend
|
|
41
|
+
│ └── index.ts # Express App & Event Handling
|
|
42
|
+
├── plugin/ # OpenCode Integration
|
|
43
|
+
│ └── pixel-office.js # Script to forward IDE events
|
|
44
|
+
└── bin/ # Executable binaries
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Key Components
|
|
48
|
+
|
|
49
|
+
### 1. `PixiScene.tsx`
|
|
50
|
+
The heart of the visualization. It handles:
|
|
51
|
+
- **Sprite Management**: Rendering agents, furniture, and effects.
|
|
52
|
+
- **Pathfinding**: A* algorithm for agent movement (using `pathfinding.ts`).
|
|
53
|
+
- **Game Loop**: Updates positions and animations at 60fps.
|
|
54
|
+
|
|
55
|
+
### 2. `useOfficeState.ts`
|
|
56
|
+
A custom React hook that:
|
|
57
|
+
- Maintains the WebSocket connection.
|
|
58
|
+
- Merges incoming event deltas into the current `OfficeState`.
|
|
59
|
+
- Manage active sessions, todos, and agent statuses.
|
|
60
|
+
|
|
61
|
+
### 3. `plugin/pixel-office.js`
|
|
62
|
+
A standard OpenCode plugin script that hooks into the event bus:
|
|
63
|
+
- `agent` events (thoughts, tools)
|
|
64
|
+
- `session` events (status changes)
|
|
65
|
+
- `fs` events (file modifications)
|
|
66
|
+
|
|
67
|
+
## 📱 Mobile Support
|
|
68
|
+
|
|
69
|
+
Monitor your agents from your phone or tablet!
|
|
70
|
+
|
|
71
|
+
- **Network Access**: The server automatically binds to your LAN IP.
|
|
72
|
+
- **QR Code**: In the dashboard, click the Network URL in the top-right corner to reveal a QR code. Scan it to instantly connect.
|
|
73
|
+
- **Responsive Design**: The dashboard adapts to portrait and landscape modes on mobile devices.
|
|
74
|
+
|
|
75
|
+
<img src="https://github.com/user-attachments/assets/c5420d78-9c87-4062-b034-21ae3defd52f" width="375" alt="Mobile View" />
|
|
76
|
+
|
|
77
|
+
## Installation
|
|
78
|
+
|
|
79
|
+
### 🚀 For Users (The Easy Way)
|
|
80
|
+
|
|
81
|
+
1. **Install the Global Package**:
|
|
82
|
+
```bash
|
|
83
|
+
npm install -g opencode-pixel-office
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
2. **Run the Installer**:
|
|
87
|
+
```bash
|
|
88
|
+
opencode-pixel-office install
|
|
89
|
+
```
|
|
90
|
+
This sets up the standalone app in `~/.opencode/pixel-office` and enables the plugin in your `opencode.json`.
|
|
91
|
+
|
|
92
|
+
3. **Start OpenCode**:
|
|
93
|
+
Simply open your IDE. Pixel Office will auto-launch in your browser at `http://localhost:5100`.
|
|
94
|
+
|
|
95
|
+
### 🛠️ For Developers
|
|
96
|
+
|
|
97
|
+
If you want to modify the source code or contribute:
|
|
98
|
+
|
|
99
|
+
#### 1. Clone & Install
|
|
100
|
+
```bash
|
|
101
|
+
git clone https://github.com/your-username/opencode-pixel-office.git
|
|
102
|
+
cd opencode-pixel-office
|
|
103
|
+
npm install
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
#### 2. Start the Server (Dev Mode)
|
|
107
|
+
```bash
|
|
108
|
+
npm start
|
|
109
|
+
# Server runs on http://localhost:5100, watching for changes
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
#### 3. Start the Client (Dev Mode)
|
|
113
|
+
In a separate terminal:
|
|
114
|
+
```bash
|
|
115
|
+
npm run dev:client
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### 4. Install the Plugin (Dev Mode)
|
|
119
|
+
To use your *local* version instead of the global one:
|
|
120
|
+
```bash
|
|
121
|
+
mkdir -p ~/.opencode/plugins
|
|
122
|
+
cp plugin/pixel-office.js ~/.opencode/plugins/
|
|
123
|
+
```
|
|
124
|
+
(The plugin automatically prefers your local `server/` if you are opening the `pixel-opencode` project in OpenCode.)
|
|
125
|
+
|
|
126
|
+
#### 2. Install the Plugin (Dev Mode)
|
|
127
|
+
Copy the plugin script to your OpenCode configuration:
|
|
128
|
+
```bash
|
|
129
|
+
mkdir -p .opencode/plugins
|
|
130
|
+
cp plugin/pixel-office.js .opencode/plugins/
|
|
131
|
+
```
|
|
132
|
+
Restart OpenCode to activate.
|
|
133
|
+
|
|
134
|
+
## Development
|
|
135
|
+
|
|
136
|
+
- **Client Dev**: `npm run dev:client` (Vite dev server)
|
|
137
|
+
- **Server Dev**: `npm start` (Runs via `tsx`)
|
|
138
|
+
|
|
139
|
+
## Credits
|
|
140
|
+
- **Tileset**: [Office Tileset by DonArg](https://donarg.itch.io/officetileset)
|
|
141
|
+
- **Icons**: Lucide React
|
|
142
|
+
- **Engine**: PixiJS
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import readline from "node:readline";
|
|
7
|
+
import { execSync } from "node:child_process";
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
|
|
12
|
+
const PLUGIN_NAME = "pixel-office.js";
|
|
13
|
+
const PLUGIN_ID = "opencode-pixel-office@latest";
|
|
14
|
+
const DEFAULT_PLUGIN_DIR = path.join(os.homedir(), ".opencode", "plugins");
|
|
15
|
+
const DEFAULT_APP_DIR = path.join(os.homedir(), ".opencode", "pixel-office");
|
|
16
|
+
const DEFAULT_CONFIG_PATH = path.join(os.homedir(), ".config", "opencode", "opencode.json");
|
|
17
|
+
const PIXEL_OFFICE_CONFIG_PATH = path.join(DEFAULT_APP_DIR, "config.json");
|
|
18
|
+
|
|
19
|
+
const args = process.argv.slice(2);
|
|
20
|
+
const shouldInstall = args.includes("install");
|
|
21
|
+
const yesFlag = args.includes("--yes") || args.includes("-y");
|
|
22
|
+
const skipJson = args.includes("--no-json");
|
|
23
|
+
const portIndex = args.findIndex((arg) => arg === "--port");
|
|
24
|
+
const portArg = portIndex !== -1 ? args[portIndex + 1] : null;
|
|
25
|
+
|
|
26
|
+
const printHelp = () => {
|
|
27
|
+
console.log("opencode-pixel-office installer\n");
|
|
28
|
+
console.log("Usage:");
|
|
29
|
+
console.log(" opencode-pixel-office install [--yes] [--port <number>]");
|
|
30
|
+
console.log("\nOptions:");
|
|
31
|
+
console.log(" --port <number> Configure the server port (default: 5100)");
|
|
32
|
+
console.log(" --no-json Skip updating opencode.json");
|
|
33
|
+
console.log(" --yes, -y Overwrite without prompting");
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const prompt = async (question) => {
|
|
37
|
+
const rl = readline.createInterface({
|
|
38
|
+
input: process.stdin,
|
|
39
|
+
output: process.stdout,
|
|
40
|
+
});
|
|
41
|
+
return new Promise((resolve) => {
|
|
42
|
+
rl.question(question, (answer) => {
|
|
43
|
+
rl.close();
|
|
44
|
+
resolve(answer.trim().toLowerCase());
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const copyRecursiveSync = (src, dest) => {
|
|
50
|
+
const exists = fs.existsSync(src);
|
|
51
|
+
const stats = exists && fs.statSync(src);
|
|
52
|
+
const isDirectory = exists && stats.isDirectory();
|
|
53
|
+
if (isDirectory) {
|
|
54
|
+
if (!fs.existsSync(dest)) {
|
|
55
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
56
|
+
}
|
|
57
|
+
fs.readdirSync(src).forEach((childItemName) => {
|
|
58
|
+
copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName));
|
|
59
|
+
});
|
|
60
|
+
} else if (exists) {
|
|
61
|
+
fs.copyFileSync(src, dest);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const run = async () => {
|
|
66
|
+
if (!shouldInstall) {
|
|
67
|
+
printHelp();
|
|
68
|
+
process.exit(0);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const rootSource = path.resolve(__dirname, "..");
|
|
72
|
+
const pluginSource = path.join(rootSource, "plugin", PLUGIN_NAME);
|
|
73
|
+
|
|
74
|
+
if (!fs.existsSync(pluginSource)) {
|
|
75
|
+
console.error(`Plugin file not found: ${pluginSource}`);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 1. Install Plugin Script
|
|
80
|
+
fs.mkdirSync(DEFAULT_PLUGIN_DIR, { recursive: true });
|
|
81
|
+
const targetPluginPath = path.join(DEFAULT_PLUGIN_DIR, PLUGIN_NAME);
|
|
82
|
+
|
|
83
|
+
if (fs.existsSync(targetPluginPath) && !yesFlag) {
|
|
84
|
+
const answer = await prompt(`Overwrite existing ${PLUGIN_NAME}? (y/N): `);
|
|
85
|
+
if (answer !== "y" && answer !== "yes") {
|
|
86
|
+
console.log("Aborted.");
|
|
87
|
+
process.exit(0);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
fs.copyFileSync(pluginSource, targetPluginPath);
|
|
91
|
+
console.log(`✓ Installed plugin to ${targetPluginPath}`);
|
|
92
|
+
|
|
93
|
+
// 2. Install Standalone App (Server + Client)
|
|
94
|
+
console.log(`Installing standalone app to ${DEFAULT_APP_DIR}...`);
|
|
95
|
+
if (fs.existsSync(DEFAULT_APP_DIR)) {
|
|
96
|
+
// rudimentary clean? probably safer to just overwrite
|
|
97
|
+
} else {
|
|
98
|
+
fs.mkdirSync(DEFAULT_APP_DIR, { recursive: true });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Copy server
|
|
102
|
+
console.log(" - Copying server...");
|
|
103
|
+
copyRecursiveSync(path.join(rootSource, "server"), path.join(DEFAULT_APP_DIR, "server"));
|
|
104
|
+
|
|
105
|
+
// Copy client/dist
|
|
106
|
+
console.log(" - Copying client assets...");
|
|
107
|
+
const clientDist = path.join(rootSource, "client", "dist");
|
|
108
|
+
if (fs.existsSync(clientDist)) {
|
|
109
|
+
copyRecursiveSync(clientDist, path.join(DEFAULT_APP_DIR, "client", "dist"));
|
|
110
|
+
} else {
|
|
111
|
+
console.warn(" ! Warning: client/dist not found. Did you run 'npm run build:client'?");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Copy package.json
|
|
115
|
+
console.log(" - Copying package.json...");
|
|
116
|
+
fs.copyFileSync(path.join(rootSource, "package.json"), path.join(DEFAULT_APP_DIR, "package.json"));
|
|
117
|
+
|
|
118
|
+
// Save Port Config if specified
|
|
119
|
+
if (portArg) {
|
|
120
|
+
const port = parseInt(portArg, 10);
|
|
121
|
+
if (!isNaN(port)) {
|
|
122
|
+
const configData = { port };
|
|
123
|
+
fs.writeFileSync(PIXEL_OFFICE_CONFIG_PATH, JSON.stringify(configData, null, 2), "utf8");
|
|
124
|
+
console.log(` - Saved port configuration: ${port}`);
|
|
125
|
+
} else {
|
|
126
|
+
console.warn(" ! Invalid port number provided. Using default.");
|
|
127
|
+
}
|
|
128
|
+
} else if (!fs.existsSync(PIXEL_OFFICE_CONFIG_PATH)) {
|
|
129
|
+
// Ensure a default config exists or verify if we need one?
|
|
130
|
+
// For now, let's leave it minimal. The plugin defaults to 5100.
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// npm install
|
|
134
|
+
console.log(" - Installing production dependencies...");
|
|
135
|
+
try {
|
|
136
|
+
execSync("npm install --omit=dev --no-package-lock", {
|
|
137
|
+
cwd: DEFAULT_APP_DIR,
|
|
138
|
+
stdio: "inherit"
|
|
139
|
+
});
|
|
140
|
+
} catch (e) {
|
|
141
|
+
console.error(" ! Failed to install dependencies:", e.message);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
console.log(`✓ Standalone app installed to ${DEFAULT_APP_DIR}`);
|
|
145
|
+
|
|
146
|
+
await updateConfig();
|
|
147
|
+
console.log("\nSuccess! Restart OpenCode to launch Pixel Office.");
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
run().catch((error) => {
|
|
151
|
+
console.error("Installer failed:", error);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const updateConfig = async () => {
|
|
156
|
+
if (skipJson) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (!fs.existsSync(DEFAULT_CONFIG_PATH)) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
const raw = fs.readFileSync(DEFAULT_CONFIG_PATH, "utf8");
|
|
164
|
+
const data = JSON.parse(raw);
|
|
165
|
+
const list = Array.isArray(data.plugin) ? data.plugin : [];
|
|
166
|
+
if (!list.includes(PLUGIN_ID)) {
|
|
167
|
+
data.plugin = [...list, PLUGIN_ID];
|
|
168
|
+
fs.writeFileSync(DEFAULT_CONFIG_PATH, `${JSON.stringify(data, null, 2)}\n`, "utf8");
|
|
169
|
+
console.log(`✓ Added ${PLUGIN_ID} to opencode.json`);
|
|
170
|
+
}
|
|
171
|
+
} catch (error) {
|
|
172
|
+
console.warn(`Failed to update ${DEFAULT_CONFIG_PATH}:`, error);
|
|
173
|
+
}
|
|
174
|
+
};
|