opencode-pixel-office 1.0.6 → 1.0.8
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 +6 -0
- package/bin/claude-code-hook.js +134 -0
- package/bin/opencode-pixel-office.js +79 -9
- package/package.json +1 -1
- package/plugin/pixel-office.js +19 -0
package/README.md
CHANGED
|
@@ -94,6 +94,12 @@ Monitor your agents from your phone or tablet!
|
|
|
94
94
|
3. **Start OpenCode**:
|
|
95
95
|
Simply open your IDE. Pixel Office will auto-launch in your browser at `http://localhost:5100`.
|
|
96
96
|
|
|
97
|
+
### CLI Commands
|
|
98
|
+
|
|
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)
|
|
102
|
+
|
|
97
103
|
### 🛠️ For Developers
|
|
98
104
|
|
|
99
105
|
If you want to modify the source code or contribute:
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
|
|
6
|
+
const readStdin = async () => {
|
|
7
|
+
const chunks = [];
|
|
8
|
+
for await (const chunk of process.stdin) {
|
|
9
|
+
chunks.push(chunk);
|
|
10
|
+
}
|
|
11
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const postEvent = async (endpoint, event) => {
|
|
15
|
+
try {
|
|
16
|
+
await fetch(endpoint, {
|
|
17
|
+
method: "POST",
|
|
18
|
+
headers: { "Content-Type": "application/json" },
|
|
19
|
+
body: JSON.stringify(event),
|
|
20
|
+
});
|
|
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`);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
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";
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const mapHookToEvent = (input) => {
|
|
41
|
+
const hook = input.hook_event_name || "";
|
|
42
|
+
const sessionId = input.session_id || "";
|
|
43
|
+
const cwd = input.cwd || "";
|
|
44
|
+
const toolName = input.tool_name || "";
|
|
45
|
+
const toolInput = input.tool_input || {};
|
|
46
|
+
const prompt = input.prompt || input.user_prompt || "";
|
|
47
|
+
const permission = input.permission || {};
|
|
48
|
+
|
|
49
|
+
const info = {
|
|
50
|
+
id: sessionId || `claude-${Date.now()}`,
|
|
51
|
+
sessionID: sessionId || "",
|
|
52
|
+
role: "user",
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
switch (hook) {
|
|
56
|
+
case "SessionStart":
|
|
57
|
+
return {
|
|
58
|
+
type: "session.created",
|
|
59
|
+
properties: { info: { id: sessionId, title: cwd } },
|
|
60
|
+
};
|
|
61
|
+
case "SessionEnd":
|
|
62
|
+
return {
|
|
63
|
+
type: "session.deleted",
|
|
64
|
+
properties: { sessionID: sessionId },
|
|
65
|
+
};
|
|
66
|
+
case "UserPromptSubmit":
|
|
67
|
+
return {
|
|
68
|
+
type: "message.updated",
|
|
69
|
+
properties: {
|
|
70
|
+
info,
|
|
71
|
+
message: { content: prompt },
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
case "PreToolUse":
|
|
75
|
+
return {
|
|
76
|
+
type: "tool.execute.before",
|
|
77
|
+
properties: { tool: { name: toolName, input: toolInput } },
|
|
78
|
+
};
|
|
79
|
+
case "PostToolUse":
|
|
80
|
+
case "PostToolUseFailure":
|
|
81
|
+
return {
|
|
82
|
+
type: "tool.execute.after",
|
|
83
|
+
properties: { tool: { name: toolName, input: toolInput } },
|
|
84
|
+
};
|
|
85
|
+
case "PermissionRequest":
|
|
86
|
+
return {
|
|
87
|
+
type: "permission.asked",
|
|
88
|
+
properties: { permission },
|
|
89
|
+
};
|
|
90
|
+
case "Notification":
|
|
91
|
+
return {
|
|
92
|
+
type: "tui.toast.show",
|
|
93
|
+
properties: { message: input.message || "" },
|
|
94
|
+
};
|
|
95
|
+
case "PreCompact":
|
|
96
|
+
return {
|
|
97
|
+
type: "session.compacted",
|
|
98
|
+
properties: { info: { id: sessionId } },
|
|
99
|
+
};
|
|
100
|
+
case "Stop":
|
|
101
|
+
return {
|
|
102
|
+
type: "session.status",
|
|
103
|
+
properties: { sessionID: sessionId, status: { type: "idle" } },
|
|
104
|
+
};
|
|
105
|
+
case "SubagentStart":
|
|
106
|
+
case "SubagentStop":
|
|
107
|
+
return {
|
|
108
|
+
type: "session.updated",
|
|
109
|
+
properties: { info: { id: sessionId } },
|
|
110
|
+
};
|
|
111
|
+
default:
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const main = async () => {
|
|
117
|
+
const raw = await readStdin();
|
|
118
|
+
if (!raw) {
|
|
119
|
+
process.exit(0);
|
|
120
|
+
}
|
|
121
|
+
const input = JSON.parse(raw);
|
|
122
|
+
const endpoint = process.env.PIXEL_OFFICE_URL || "http://localhost:5100/events";
|
|
123
|
+
if (readMode() !== "claude-code") {
|
|
124
|
+
process.exit(0);
|
|
125
|
+
}
|
|
126
|
+
const event = mapHookToEvent(input);
|
|
127
|
+
if (!event) {
|
|
128
|
+
process.exit(0);
|
|
129
|
+
}
|
|
130
|
+
await postEvent(endpoint, event);
|
|
131
|
+
process.exit(0);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
main().catch(() => process.exit(0));
|
|
@@ -17,7 +17,10 @@ const DEFAULT_CONFIG_PATH = path.join(os.homedir(), ".config", "opencode", "open
|
|
|
17
17
|
const PIXEL_OFFICE_CONFIG_PATH = path.join(DEFAULT_APP_DIR, "config.json");
|
|
18
18
|
|
|
19
19
|
const args = process.argv.slice(2);
|
|
20
|
-
const shouldInstall = args.includes("install");
|
|
20
|
+
const shouldInstall = args.includes("install") && !args.includes("claude-code") && !args.includes("switch");
|
|
21
|
+
const shouldInstallClaude = args[0] === "claude-code" && args[1] === "install";
|
|
22
|
+
const shouldSwitch = args[0] === "switch";
|
|
23
|
+
const switchTarget = shouldSwitch ? args[1] : null;
|
|
21
24
|
const shouldUninstall = args.includes("uninstall");
|
|
22
25
|
const yesFlag = args.includes("--yes") || args.includes("-y");
|
|
23
26
|
const skipJson = args.includes("--no-json");
|
|
@@ -28,6 +31,8 @@ const printHelp = () => {
|
|
|
28
31
|
console.log("opencode-pixel-office installer\n");
|
|
29
32
|
console.log("Usage:");
|
|
30
33
|
console.log(" opencode-pixel-office install [--yes] [--port <number>]");
|
|
34
|
+
console.log(" opencode-pixel-office claude-code install");
|
|
35
|
+
console.log(" opencode-pixel-office switch <opencode|claude-code>");
|
|
31
36
|
console.log(" opencode-pixel-office uninstall");
|
|
32
37
|
console.log(" opencode-pixel-office stop");
|
|
33
38
|
console.log("\nOptions:");
|
|
@@ -36,6 +41,51 @@ const printHelp = () => {
|
|
|
36
41
|
console.log(" --yes, -y Overwrite without prompting");
|
|
37
42
|
};
|
|
38
43
|
|
|
44
|
+
const installClaudeCodeHooks = () => {
|
|
45
|
+
const claudeDir = path.join(os.homedir(), ".claude");
|
|
46
|
+
const hookDir = path.join(claudeDir, "hooks");
|
|
47
|
+
const settingsPath = path.join(claudeDir, "settings.json");
|
|
48
|
+
fs.mkdirSync(hookDir, { recursive: true });
|
|
49
|
+
|
|
50
|
+
const hookSource = path.resolve(__dirname, "claude-code-hook.js");
|
|
51
|
+
const hookTarget = path.join(hookDir, "opencode-pixel-office-hook.js");
|
|
52
|
+
fs.copyFileSync(hookSource, hookTarget);
|
|
53
|
+
|
|
54
|
+
const hookCommand = `node ${hookTarget}`;
|
|
55
|
+
const hooksConfig = {
|
|
56
|
+
SessionStart: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
57
|
+
UserPromptSubmit: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
58
|
+
PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
59
|
+
PostToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
60
|
+
PostToolUseFailure: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
61
|
+
PermissionRequest: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
62
|
+
Notification: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
63
|
+
SubagentStart: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
64
|
+
SubagentStop: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
65
|
+
Stop: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
66
|
+
PreCompact: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
67
|
+
SessionEnd: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
let settings = { hooks: {} };
|
|
71
|
+
if (fs.existsSync(settingsPath)) {
|
|
72
|
+
try {
|
|
73
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
|
|
74
|
+
} catch {
|
|
75
|
+
settings = { hooks: {} };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
settings.hooks = settings.hooks || {};
|
|
80
|
+
Object.entries(hooksConfig).forEach(([eventName, value]) => {
|
|
81
|
+
settings.hooks[eventName] = value;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
fs.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, "utf8");
|
|
85
|
+
console.log(`✓ Installed Claude Code hooks into ${settingsPath}`);
|
|
86
|
+
console.log("Claude Code will now stream events to Pixel Office.");
|
|
87
|
+
};
|
|
88
|
+
|
|
39
89
|
const loadConfig = () => {
|
|
40
90
|
try {
|
|
41
91
|
if (fs.existsSync(PIXEL_OFFICE_CONFIG_PATH)) {
|
|
@@ -45,6 +95,11 @@ const loadConfig = () => {
|
|
|
45
95
|
return {};
|
|
46
96
|
};
|
|
47
97
|
|
|
98
|
+
const saveConfig = (config) => {
|
|
99
|
+
fs.mkdirSync(DEFAULT_APP_DIR, { recursive: true });
|
|
100
|
+
fs.writeFileSync(PIXEL_OFFICE_CONFIG_PATH, JSON.stringify(config, null, 2), "utf8");
|
|
101
|
+
};
|
|
102
|
+
|
|
48
103
|
const prompt = async (question) => {
|
|
49
104
|
const rl = readline.createInterface({
|
|
50
105
|
input: process.stdin,
|
|
@@ -75,6 +130,23 @@ const copyRecursiveSync = (src, dest) => {
|
|
|
75
130
|
};
|
|
76
131
|
|
|
77
132
|
const run = async () => {
|
|
133
|
+
if (shouldSwitch) {
|
|
134
|
+
if (switchTarget !== "opencode" && switchTarget !== "claude-code") {
|
|
135
|
+
console.error("Switch target must be 'opencode' or 'claude-code'.");
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
const config = loadConfig();
|
|
139
|
+
config.mode = switchTarget;
|
|
140
|
+
saveConfig(config);
|
|
141
|
+
console.log(`✓ Mode set to ${switchTarget}`);
|
|
142
|
+
process.exit(0);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (shouldInstallClaude) {
|
|
146
|
+
installClaudeCodeHooks();
|
|
147
|
+
process.exit(0);
|
|
148
|
+
}
|
|
149
|
+
|
|
78
150
|
if (shouldUninstall) {
|
|
79
151
|
const targetPluginPath = path.join(DEFAULT_PLUGIN_DIR, PLUGIN_NAME);
|
|
80
152
|
|
|
@@ -198,19 +270,20 @@ const run = async () => {
|
|
|
198
270
|
fs.copyFileSync(path.join(rootSource, "package.json"), path.join(DEFAULT_APP_DIR, "package.json"));
|
|
199
271
|
|
|
200
272
|
// Save Port Config if specified
|
|
273
|
+
const configData = loadConfig();
|
|
201
274
|
if (portArg) {
|
|
202
275
|
const port = parseInt(portArg, 10);
|
|
203
276
|
if (!isNaN(port)) {
|
|
204
|
-
|
|
205
|
-
fs.writeFileSync(PIXEL_OFFICE_CONFIG_PATH, JSON.stringify(configData, null, 2), "utf8");
|
|
277
|
+
configData.port = port;
|
|
206
278
|
console.log(` - Saved port configuration: ${port}`);
|
|
207
279
|
} else {
|
|
208
280
|
console.warn(" ! Invalid port number provided. Using default.");
|
|
209
281
|
}
|
|
210
|
-
} else if (!fs.existsSync(PIXEL_OFFICE_CONFIG_PATH)) {
|
|
211
|
-
// Ensure a default config exists or verify if we need one?
|
|
212
|
-
// For now, let's leave it minimal. The plugin defaults to 5100.
|
|
213
282
|
}
|
|
283
|
+
if (!configData.mode) {
|
|
284
|
+
configData.mode = "opencode";
|
|
285
|
+
}
|
|
286
|
+
saveConfig(configData);
|
|
214
287
|
|
|
215
288
|
// npm install
|
|
216
289
|
console.log(" - Installing production dependencies...");
|
|
@@ -232,6 +305,3 @@ run().catch((error) => {
|
|
|
232
305
|
console.error("Installer failed:", error);
|
|
233
306
|
process.exit(1);
|
|
234
307
|
});
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
package/package.json
CHANGED
package/plugin/pixel-office.js
CHANGED
|
@@ -28,6 +28,22 @@ const homeDir = process.env.HOME || process.env.USERPROFILE || "/";
|
|
|
28
28
|
const globalDistDir = path.join(homeDir, ".opencode", "pixel-office");
|
|
29
29
|
const configuredPort = getConfiguredPort(globalDistDir);
|
|
30
30
|
|
|
31
|
+
const readMode = () => {
|
|
32
|
+
try {
|
|
33
|
+
const configPath = path.join(globalDistDir, "config.json");
|
|
34
|
+
if (fs.existsSync(configPath)) {
|
|
35
|
+
const raw = fs.readFileSync(configPath, "utf8");
|
|
36
|
+
const data = JSON.parse(raw);
|
|
37
|
+
if (data && typeof data.mode === "string") {
|
|
38
|
+
return data.mode;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
} catch (e) {
|
|
42
|
+
return "opencode";
|
|
43
|
+
}
|
|
44
|
+
return "opencode";
|
|
45
|
+
};
|
|
46
|
+
|
|
31
47
|
const resolveEndpoint = () => {
|
|
32
48
|
const port = process.env.PIXEL_OFFICE_PORT || configuredPort;
|
|
33
49
|
const raw =
|
|
@@ -153,6 +169,9 @@ export const PixelOfficePlugin = async ({ directory, worktree, client }) => {
|
|
|
153
169
|
|
|
154
170
|
return {
|
|
155
171
|
event: async ({ event }) => {
|
|
172
|
+
if (readMode() !== "opencode") {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
156
175
|
if (!event) {
|
|
157
176
|
return;
|
|
158
177
|
}
|