opencode-pixel-office 1.0.7 → 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.
@@ -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
- const configData = { port };
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-pixel-office",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "bin",
@@ -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
  }