opencode-pixel-office 1.0.9 → 1.0.11

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
@@ -15,7 +15,7 @@ The system consists of three main parts:
15
15
 
16
16
  ```mermaid
17
17
  graph TD
18
- A[OpenCode IDE] -->|Plugin Events via HTTP| B(Pixel Office Server :3000)
18
+ A[OpenCode IDE] -->|Plugin Events via HTTP| B(Pixel Office Server :5100)
19
19
  B -->|Broadcast State via WebSocket| C[React Client]
20
20
  C -->|Render| D[PixiJS Scene]
21
21
  C -->|Render| E[HUD / Sidebar]
@@ -92,7 +92,7 @@ Monitor your agents from your phone or tablet!
92
92
  This sets up the standalone app in `~/.opencode/pixel-office` and installs the `pixel-office.js` plugin script to `~/.opencode/plugins/`.
93
93
 
94
94
  3. **Start OpenCode**:
95
- Simply open your IDE. Pixel Office will auto-launch in your browser at `http://localhost:3000`.
95
+ Simply open your IDE. Pixel Office will auto-launch in your browser at `http://localhost:5100`.
96
96
 
97
97
  ### CLI Commands
98
98
 
@@ -114,7 +114,7 @@ npm install
114
114
  #### 2. Start the Server (Dev Mode)
115
115
  ```bash
116
116
  npm start
117
- # Server runs on http://localhost:3000, watching for changes
117
+ # Server runs on http://localhost:5100, watching for changes
118
118
  ```
119
119
 
120
120
  #### 3. Start the Client (Dev Mode)
@@ -48,15 +48,17 @@ const mapHookToEvent = (input) => {
48
48
 
49
49
  const info = {
50
50
  id: sessionId || `claude-${Date.now()}`,
51
- sessionID: sessionId || "",
52
- role: "user",
51
+ sessionID: sessionId,
52
+ agent: "Claude",
53
+ title: cwd || "Claude Code",
54
+ model: { modelID: "claude", providerID: "anthropic" },
53
55
  };
54
56
 
55
57
  switch (hook) {
56
58
  case "SessionStart":
57
59
  return {
58
60
  type: "session.created",
59
- properties: { info: { id: sessionId, title: cwd } },
61
+ properties: { info },
60
62
  };
61
63
  case "SessionEnd":
62
64
  return {
@@ -67,35 +69,35 @@ const mapHookToEvent = (input) => {
67
69
  return {
68
70
  type: "message.updated",
69
71
  properties: {
70
- info,
71
- message: { content: prompt },
72
+ info: { ...info, role: "user" },
73
+ message: { content: prompt, role: "user" },
72
74
  },
73
75
  };
74
76
  case "PreToolUse":
75
77
  return {
76
78
  type: "tool.execute.before",
77
- properties: { tool: { name: toolName, input: toolInput } },
79
+ properties: { info, tool: { name: toolName, input: toolInput } },
78
80
  };
79
81
  case "PostToolUse":
80
82
  case "PostToolUseFailure":
81
83
  return {
82
84
  type: "tool.execute.after",
83
- properties: { tool: { name: toolName, input: toolInput } },
85
+ properties: { info, tool: { name: toolName, input: toolInput } },
84
86
  };
85
87
  case "PermissionRequest":
86
88
  return {
87
89
  type: "permission.asked",
88
- properties: { permission },
90
+ properties: { info, permission },
89
91
  };
90
92
  case "Notification":
91
93
  return {
92
94
  type: "tui.toast.show",
93
- properties: { message: input.message || "" },
95
+ properties: { info, message: input.message || "" },
94
96
  };
95
97
  case "PreCompact":
96
98
  return {
97
99
  type: "session.compacted",
98
- properties: { info: { id: sessionId } },
100
+ properties: { info },
99
101
  };
100
102
  case "Stop":
101
103
  return {
@@ -106,7 +108,7 @@ const mapHookToEvent = (input) => {
106
108
  case "SubagentStop":
107
109
  return {
108
110
  type: "session.updated",
109
- properties: { info: { id: sessionId } },
111
+ properties: { info },
110
112
  };
111
113
  default:
112
114
  return null;
@@ -119,7 +121,7 @@ const main = async () => {
119
121
  process.exit(0);
120
122
  }
121
123
  const input = JSON.parse(raw);
122
- const endpoint = process.env.PIXEL_OFFICE_URL || "http://localhost:3000/events";
124
+ const endpoint = process.env.PIXEL_OFFICE_URL || "http://localhost:5100/events";
123
125
  if (readMode() !== "claude-code") {
124
126
  process.exit(0);
125
127
  }
@@ -23,11 +23,22 @@ const shouldSwitch = args[0] === "switch";
23
23
  const switchTarget = shouldSwitch ? args[1] : null;
24
24
  const shouldUninstall = args.includes("uninstall");
25
25
  const shouldStart = args[0] === "start";
26
+ const shouldVersion = args.includes("--version") || args.includes("-v");
26
27
  const yesFlag = args.includes("--yes") || args.includes("-y");
27
28
  const skipJson = args.includes("--no-json");
28
29
  const portIndex = args.findIndex((arg) => arg === "--port");
29
30
  const portArg = portIndex !== -1 ? args[portIndex + 1] : null;
30
31
 
32
+ const getVersion = () => {
33
+ try {
34
+ const pkgPath = path.resolve(__dirname, "..", "package.json");
35
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
36
+ return pkg.version || "unknown";
37
+ } catch {
38
+ return "unknown";
39
+ }
40
+ };
41
+
31
42
  const printHelp = () => {
32
43
  console.log("opencode-pixel-office installer\n");
33
44
  console.log("Usage:");
@@ -38,9 +49,10 @@ const printHelp = () => {
38
49
  console.log(" opencode-pixel-office uninstall");
39
50
  console.log(" opencode-pixel-office stop");
40
51
  console.log("\nOptions:");
41
- console.log(" --port <number> Configure the server port (default: 3000)");
52
+ console.log(" --port <number> Configure the server port (default: 5100)");
42
53
  console.log(" --no-json Skip updating opencode.json");
43
54
  console.log(" --yes, -y Overwrite without prompting");
55
+ console.log(" --version, -v Show version number");
44
56
  };
45
57
 
46
58
  const installClaudeCodeHooks = () => {
@@ -131,7 +143,33 @@ const copyRecursiveSync = (src, dest) => {
131
143
  }
132
144
  };
133
145
 
146
+ const stopServer = (port) => {
147
+ try {
148
+ const pidOutput = execSync(`lsof -t -i :${port} 2>/dev/null`).toString().trim();
149
+ if (pidOutput) {
150
+ const pids = pidOutput.split('\n').map(p => parseInt(p, 10)).filter(p => !isNaN(p) && p > 0);
151
+ for (const pid of pids) {
152
+ try {
153
+ process.kill(pid);
154
+ console.log(`✓ Stopped server (PID: ${pid})`);
155
+ } catch {
156
+ // Process may have already exited
157
+ }
158
+ }
159
+ return pids.length > 0;
160
+ }
161
+ } catch {
162
+ // lsof failed, no process found
163
+ }
164
+ return false;
165
+ };
166
+
134
167
  const run = async () => {
168
+ if (shouldVersion) {
169
+ console.log(`opencode-pixel-office v${getVersion()}`);
170
+ process.exit(0);
171
+ }
172
+
135
173
  if (shouldSwitch) {
136
174
  if (switchTarget !== "opencode" && switchTarget !== "claude-code") {
137
175
  console.error("Switch target must be 'opencode' or 'claude-code'.");
@@ -150,6 +188,14 @@ const run = async () => {
150
188
  }
151
189
 
152
190
  if (shouldUninstall) {
191
+ // Stop server first
192
+ const config = loadConfig();
193
+ const port = config.port || 5100;
194
+ console.log("Stopping Pixel Office server...");
195
+ if (!stopServer(port)) {
196
+ console.log("- Server not running");
197
+ }
198
+
153
199
  const targetPluginPath = path.join(DEFAULT_PLUGIN_DIR, PLUGIN_NAME);
154
200
 
155
201
  // Remove Plugin
@@ -168,6 +214,56 @@ const run = async () => {
168
214
  console.log(`- App directory not found at ${DEFAULT_APP_DIR}`);
169
215
  }
170
216
 
217
+ // Remove Claude Code hooks
218
+ const claudeDir = path.join(os.homedir(), ".claude");
219
+ const hookDir = path.join(claudeDir, "hooks");
220
+ const hookFile = path.join(hookDir, "opencode-pixel-office-hook.js");
221
+ const settingsPath = path.join(claudeDir, "settings.json");
222
+
223
+ if (fs.existsSync(hookFile)) {
224
+ fs.unlinkSync(hookFile);
225
+ console.log(`✓ Removed Claude Code hook: ${hookFile}`);
226
+ }
227
+
228
+ if (fs.existsSync(settingsPath)) {
229
+ try {
230
+ const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
231
+ if (settings.hooks) {
232
+ let modified = false;
233
+ for (const eventName of Object.keys(settings.hooks)) {
234
+ const hooks = settings.hooks[eventName];
235
+ if (Array.isArray(hooks)) {
236
+ const filtered = hooks.filter(h => {
237
+ if (Array.isArray(h.hooks)) {
238
+ h.hooks = h.hooks.filter(inner =>
239
+ !inner.command?.includes("opencode-pixel-office-hook")
240
+ );
241
+ return h.hooks.length > 0;
242
+ }
243
+ return !h.command?.includes("opencode-pixel-office-hook");
244
+ });
245
+ if (filtered.length !== hooks.length) {
246
+ settings.hooks[eventName] = filtered;
247
+ modified = true;
248
+ }
249
+ if (filtered.length === 0) {
250
+ delete settings.hooks[eventName];
251
+ }
252
+ }
253
+ }
254
+ if (Object.keys(settings.hooks).length === 0) {
255
+ delete settings.hooks;
256
+ }
257
+ if (modified) {
258
+ fs.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, "utf8");
259
+ console.log(`✓ Removed hooks from Claude Code settings`);
260
+ }
261
+ }
262
+ } catch {
263
+ // ignore settings parse errors
264
+ }
265
+ }
266
+
171
267
  // Clean up Config (Legacy)
172
268
  if (fs.existsSync(DEFAULT_CONFIG_PATH) && !skipJson) {
173
269
  try {
@@ -192,7 +288,7 @@ const run = async () => {
192
288
 
193
289
  if (shouldStart) {
194
290
  const config = loadConfig();
195
- const port = portArg ? parseInt(portArg, 10) : (config.port || 3000);
291
+ const port = portArg ? parseInt(portArg, 10) : (config.port || 5100);
196
292
  const serverScript = "server/index.ts";
197
293
 
198
294
  const rootSource = path.resolve(__dirname, "..");
@@ -223,21 +319,38 @@ const run = async () => {
223
319
  }
224
320
  } catch { }
225
321
 
226
- let tsxBin = "tsx";
227
- if (useGlobal) {
228
- const globalTsx = path.join(serverCwd, "node_modules", ".bin", "tsx");
229
- if (fs.existsSync(globalTsx)) {
230
- tsxBin = globalTsx;
322
+ // Resolve tsx binary - check multiple locations
323
+ let tsxBin = null;
324
+ const tsxLocations = [
325
+ path.join(serverCwd, "node_modules", ".bin", "tsx"),
326
+ path.join(__dirname, "..", "node_modules", ".bin", "tsx"),
327
+ path.join(DEFAULT_APP_DIR, "node_modules", ".bin", "tsx"),
328
+ ];
329
+ for (const loc of tsxLocations) {
330
+ if (fs.existsSync(loc)) {
331
+ tsxBin = loc;
332
+ break;
231
333
  }
232
334
  }
335
+ if (!tsxBin) {
336
+ // Fallback to PATH
337
+ tsxBin = "tsx";
338
+ }
233
339
 
234
340
  const { spawn } = await import("node:child_process");
341
+
235
342
  const child = spawn(tsxBin, [serverScript], {
236
343
  cwd: serverCwd,
237
344
  env: { ...process.env, PORT: String(port) },
238
345
  detached: true,
239
346
  stdio: "ignore",
240
347
  });
348
+
349
+ child.on("error", (err) => {
350
+ console.error(`Failed to start server: ${err.message}`);
351
+ process.exit(1);
352
+ });
353
+
241
354
  child.unref();
242
355
 
243
356
  console.log(`Pixel Office server started on port ${port} (PID: ${child.pid})`);
@@ -252,29 +365,10 @@ const run = async () => {
252
365
 
253
366
  if (args.includes("stop")) {
254
367
  const config = loadConfig();
255
- const port = config.port || 3000;
256
-
368
+ const port = config.port || 5100;
257
369
  console.log(`Stopping Pixel Office server on port ${port}...`);
258
- try {
259
- const pidOutput = execSync(`lsof -t -i :${port}`).toString().trim();
260
- if (pidOutput) {
261
- const pid = parseInt(pidOutput, 10);
262
- if (!isNaN(pid) && pid > 0) {
263
- process.kill(pid);
264
- console.log(`✓ Server stopped (PID: ${pid})`);
265
- } else {
266
- console.log(`- No server found on port ${port} (PID: ${pidOutput})`);
267
- }
268
- } else {
269
- console.log(`- No server found on port ${port}`);
270
- }
271
- } catch (e) {
272
- if (e.message.includes("Command failed")) {
273
- // likely lsof failed meaning no process found
274
- console.log(`- No server found on port ${port}`);
275
- } else {
276
- console.error(`! Failed to stop server: ${e.message}`);
277
- }
370
+ if (!stopServer(port)) {
371
+ console.log(`- No server found on port ${port}`);
278
372
  }
279
373
  process.exit(0);
280
374
  }
@@ -360,7 +454,8 @@ const run = async () => {
360
454
 
361
455
  console.log(`✓ Standalone app installed to ${DEFAULT_APP_DIR}`);
362
456
 
363
- console.log("\nSuccess! Restart OpenCode to launch Pixel Office.");
457
+ console.log("\nInstallation complete!");
458
+ console.log("Run 'opencode-pixel-office start' to launch the server.");
364
459
  };
365
460
 
366
461
  run().catch((error) => {