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 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
+ };