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 +3 -3
- package/bin/claude-code-hook.js +14 -12
- package/bin/opencode-pixel-office.js +125 -30
- package/client/dist/assets/{index-BYMX3DUt.js → index-Cfnbdbzw.js} +73 -73
- package/client/dist/index.html +1 -1
- package/package.json +1 -1
- package/plugin/pixel-office.js +14 -143
- package/server/index.ts +1 -1
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 :
|
|
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:
|
|
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:
|
|
117
|
+
# Server runs on http://localhost:5100, watching for changes
|
|
118
118
|
```
|
|
119
119
|
|
|
120
120
|
#### 3. Start the Client (Dev Mode)
|
package/bin/claude-code-hook.js
CHANGED
|
@@ -48,15 +48,17 @@ const mapHookToEvent = (input) => {
|
|
|
48
48
|
|
|
49
49
|
const info = {
|
|
50
50
|
id: sessionId || `claude-${Date.now()}`,
|
|
51
|
-
sessionID: sessionId
|
|
52
|
-
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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 ||
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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 ||
|
|
256
|
-
|
|
368
|
+
const port = config.port || 5100;
|
|
257
369
|
console.log(`Stopping Pixel Office server on port ${port}...`);
|
|
258
|
-
|
|
259
|
-
|
|
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("\
|
|
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) => {
|