opencode-pixel-office 1.0.11 → 1.1.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/bin/claude-code-hook.js +138 -39
- package/bin/opencode-pixel-office.js +275 -255
- package/client/dist/assets/{index-CocJhp5H.css → index-Bg5BmWlM.css} +1 -1
- package/client/dist/assets/{index-Cfnbdbzw.js → index-Dyzm-JqB.js} +28 -28
- package/client/dist/index.html +2 -2
- package/package.json +1 -1
- package/plugin/pixel-office.js +6 -22
- package/server/index.ts +4 -0
|
@@ -15,17 +15,20 @@ const DEFAULT_PLUGIN_DIR = path.join(os.homedir(), ".opencode", "plugins");
|
|
|
15
15
|
const DEFAULT_APP_DIR = path.join(os.homedir(), ".opencode", "pixel-office");
|
|
16
16
|
const DEFAULT_CONFIG_PATH = path.join(os.homedir(), ".config", "opencode", "opencode.json");
|
|
17
17
|
const PIXEL_OFFICE_CONFIG_PATH = path.join(DEFAULT_APP_DIR, "config.json");
|
|
18
|
+
const CLAUDE_DIR = path.join(os.homedir(), ".claude");
|
|
19
|
+
const CLAUDE_HOOK_DIR = path.join(CLAUDE_DIR, "hooks");
|
|
20
|
+
const CLAUDE_HOOK_FILE = path.join(CLAUDE_HOOK_DIR, "opencode-pixel-office-hook.js");
|
|
21
|
+
const CLAUDE_SETTINGS_PATH = path.join(CLAUDE_DIR, "settings.json");
|
|
18
22
|
|
|
19
23
|
const args = process.argv.slice(2);
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
24
|
+
const command = args[0];
|
|
25
|
+
const shouldInstall = command === "install";
|
|
26
|
+
const shouldUninstall = command === "uninstall";
|
|
27
|
+
const shouldStart = command === "start";
|
|
28
|
+
const shouldStop = command === "stop";
|
|
29
|
+
const shouldStatus = command === "status";
|
|
26
30
|
const shouldVersion = args.includes("--version") || args.includes("-v");
|
|
27
31
|
const yesFlag = args.includes("--yes") || args.includes("-y");
|
|
28
|
-
const skipJson = args.includes("--no-json");
|
|
29
32
|
const portIndex = args.findIndex((arg) => arg === "--port");
|
|
30
33
|
const portArg = portIndex !== -1 ? args[portIndex + 1] : null;
|
|
31
34
|
|
|
@@ -40,72 +43,31 @@ const getVersion = () => {
|
|
|
40
43
|
};
|
|
41
44
|
|
|
42
45
|
const printHelp = () => {
|
|
43
|
-
console.log(
|
|
46
|
+
console.log(`opencode-pixel-office v${getVersion()}\n`);
|
|
47
|
+
console.log("A pixel-art office visualization for AI coding assistants.");
|
|
48
|
+
console.log("Works with both OpenCode and Claude Code simultaneously.\n");
|
|
44
49
|
console.log("Usage:");
|
|
45
|
-
console.log(" opencode-pixel-office install [
|
|
46
|
-
console.log(" opencode-pixel-office
|
|
47
|
-
console.log(" opencode-pixel-office
|
|
48
|
-
console.log(" opencode-pixel-office
|
|
49
|
-
console.log(" opencode-pixel-office uninstall");
|
|
50
|
-
console.log(" opencode-pixel-office stop");
|
|
50
|
+
console.log(" opencode-pixel-office install [options] Install for both OpenCode & Claude");
|
|
51
|
+
console.log(" opencode-pixel-office start [options] Start the server");
|
|
52
|
+
console.log(" opencode-pixel-office stop Stop the server");
|
|
53
|
+
console.log(" opencode-pixel-office status Show installation status");
|
|
54
|
+
console.log(" opencode-pixel-office uninstall Uninstall completely");
|
|
51
55
|
console.log("\nOptions:");
|
|
52
|
-
console.log(" --port <number>
|
|
53
|
-
console.log(" --
|
|
54
|
-
console.log(" --yes, -y Overwrite without prompting");
|
|
56
|
+
console.log(" --port <number> Set server port (default: 5100)");
|
|
57
|
+
console.log(" --yes, -y Skip confirmation prompts");
|
|
55
58
|
console.log(" --version, -v Show version number");
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const hookDir = path.join(claudeDir, "hooks");
|
|
61
|
-
const settingsPath = path.join(claudeDir, "settings.json");
|
|
62
|
-
fs.mkdirSync(hookDir, { recursive: true });
|
|
63
|
-
|
|
64
|
-
const hookSource = path.resolve(__dirname, "claude-code-hook.js");
|
|
65
|
-
const hookTarget = path.join(hookDir, "opencode-pixel-office-hook.js");
|
|
66
|
-
fs.copyFileSync(hookSource, hookTarget);
|
|
67
|
-
|
|
68
|
-
const hookCommand = `node ${hookTarget}`;
|
|
69
|
-
const hooksConfig = {
|
|
70
|
-
SessionStart: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
71
|
-
UserPromptSubmit: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
72
|
-
PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
73
|
-
PostToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
74
|
-
PostToolUseFailure: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
75
|
-
PermissionRequest: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
76
|
-
Notification: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
77
|
-
SubagentStart: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
78
|
-
SubagentStop: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
79
|
-
Stop: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
80
|
-
PreCompact: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
81
|
-
SessionEnd: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
let settings = { hooks: {} };
|
|
85
|
-
if (fs.existsSync(settingsPath)) {
|
|
86
|
-
try {
|
|
87
|
-
settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
|
|
88
|
-
} catch {
|
|
89
|
-
settings = { hooks: {} };
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
settings.hooks = settings.hooks || {};
|
|
94
|
-
Object.entries(hooksConfig).forEach(([eventName, value]) => {
|
|
95
|
-
settings.hooks[eventName] = value;
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
fs.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, "utf8");
|
|
99
|
-
console.log(`✓ Installed Claude Code hooks into ${settingsPath}`);
|
|
100
|
-
console.log("Claude Code will now stream events to Pixel Office.");
|
|
59
|
+
console.log("\nExamples:");
|
|
60
|
+
console.log(" opencode-pixel-office install # Install for OpenCode + Claude Code");
|
|
61
|
+
console.log(" opencode-pixel-office start # Start server & open browser");
|
|
62
|
+
console.log(" opencode-pixel-office status # Check what's installed");
|
|
101
63
|
};
|
|
102
64
|
|
|
103
65
|
const loadConfig = () => {
|
|
104
66
|
try {
|
|
105
67
|
if (fs.existsSync(PIXEL_OFFICE_CONFIG_PATH)) {
|
|
106
|
-
return JSON.parse(fs.readFileSync(PIXEL_OFFICE_CONFIG_PATH,
|
|
68
|
+
return JSON.parse(fs.readFileSync(PIXEL_OFFICE_CONFIG_PATH, "utf8"));
|
|
107
69
|
}
|
|
108
|
-
} catch
|
|
70
|
+
} catch {}
|
|
109
71
|
return {};
|
|
110
72
|
};
|
|
111
73
|
|
|
@@ -147,148 +109,283 @@ const stopServer = (port) => {
|
|
|
147
109
|
try {
|
|
148
110
|
const pidOutput = execSync(`lsof -t -i :${port} 2>/dev/null`).toString().trim();
|
|
149
111
|
if (pidOutput) {
|
|
150
|
-
const pids = pidOutput.split(
|
|
112
|
+
const pids = pidOutput.split("\n").map((p) => parseInt(p, 10)).filter((p) => !isNaN(p) && p > 0);
|
|
151
113
|
for (const pid of pids) {
|
|
152
114
|
try {
|
|
153
115
|
process.kill(pid);
|
|
154
116
|
console.log(`✓ Stopped server (PID: ${pid})`);
|
|
155
|
-
} catch {
|
|
156
|
-
// Process may have already exited
|
|
157
|
-
}
|
|
117
|
+
} catch {}
|
|
158
118
|
}
|
|
159
119
|
return pids.length > 0;
|
|
160
120
|
}
|
|
161
|
-
} catch {
|
|
162
|
-
|
|
121
|
+
} catch {}
|
|
122
|
+
return false;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Check if opencode plugin is installed
|
|
126
|
+
const isOpencodeInstalled = () => {
|
|
127
|
+
const pluginPath = path.join(DEFAULT_PLUGIN_DIR, PLUGIN_NAME);
|
|
128
|
+
return fs.existsSync(pluginPath);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// Check if claude-code hooks are installed
|
|
132
|
+
const isClaudeCodeInstalled = () => {
|
|
133
|
+
return fs.existsSync(CLAUDE_HOOK_FILE);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Check if app is installed
|
|
137
|
+
const isAppInstalled = () => {
|
|
138
|
+
return fs.existsSync(path.join(DEFAULT_APP_DIR, "server", "index.ts"));
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// Install opencode plugin
|
|
142
|
+
const installOpencodePlugin = () => {
|
|
143
|
+
const rootSource = path.resolve(__dirname, "..");
|
|
144
|
+
const pluginSource = path.join(rootSource, "plugin", PLUGIN_NAME);
|
|
145
|
+
|
|
146
|
+
if (!fs.existsSync(pluginSource)) {
|
|
147
|
+
console.error(` ! Plugin file not found: ${pluginSource}`);
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
fs.mkdirSync(DEFAULT_PLUGIN_DIR, { recursive: true });
|
|
152
|
+
const targetPluginPath = path.join(DEFAULT_PLUGIN_DIR, PLUGIN_NAME);
|
|
153
|
+
fs.copyFileSync(pluginSource, targetPluginPath);
|
|
154
|
+
console.log(` ✓ OpenCode plugin installed`);
|
|
155
|
+
return true;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// Remove opencode plugin
|
|
159
|
+
const removeOpencodePlugin = () => {
|
|
160
|
+
const pluginPath = path.join(DEFAULT_PLUGIN_DIR, PLUGIN_NAME);
|
|
161
|
+
if (fs.existsSync(pluginPath)) {
|
|
162
|
+
fs.unlinkSync(pluginPath);
|
|
163
|
+
console.log(`✓ Removed OpenCode plugin`);
|
|
164
|
+
return true;
|
|
163
165
|
}
|
|
164
166
|
return false;
|
|
165
167
|
};
|
|
166
168
|
|
|
169
|
+
// Install claude-code hooks
|
|
170
|
+
const installClaudeCodeHooks = () => {
|
|
171
|
+
fs.mkdirSync(CLAUDE_HOOK_DIR, { recursive: true });
|
|
172
|
+
|
|
173
|
+
const hookSource = path.resolve(__dirname, "claude-code-hook.js");
|
|
174
|
+
if (!fs.existsSync(hookSource)) {
|
|
175
|
+
console.error(` ! Hook file not found: ${hookSource}`);
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
fs.copyFileSync(hookSource, CLAUDE_HOOK_FILE);
|
|
180
|
+
|
|
181
|
+
const hookCommand = `node ${CLAUDE_HOOK_FILE}`;
|
|
182
|
+
const hooksConfig = {
|
|
183
|
+
SessionStart: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
184
|
+
UserPromptSubmit: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
185
|
+
PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
186
|
+
PostToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
187
|
+
PostToolUseFailure: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
188
|
+
PermissionRequest: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
189
|
+
Notification: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
190
|
+
SubagentStart: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
191
|
+
SubagentStop: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
192
|
+
Stop: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
193
|
+
PreCompact: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
194
|
+
SessionEnd: [{ matcher: "", hooks: [{ type: "command", command: hookCommand }] }],
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
let settings = { hooks: {} };
|
|
198
|
+
if (fs.existsSync(CLAUDE_SETTINGS_PATH)) {
|
|
199
|
+
try {
|
|
200
|
+
settings = JSON.parse(fs.readFileSync(CLAUDE_SETTINGS_PATH, "utf8"));
|
|
201
|
+
} catch {
|
|
202
|
+
settings = { hooks: {} };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
settings.hooks = settings.hooks || {};
|
|
207
|
+
Object.entries(hooksConfig).forEach(([eventName, value]) => {
|
|
208
|
+
settings.hooks[eventName] = value;
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
fs.writeFileSync(CLAUDE_SETTINGS_PATH, `${JSON.stringify(settings, null, 2)}\n`, "utf8");
|
|
212
|
+
console.log(` ✓ Claude Code hooks installed`);
|
|
213
|
+
return true;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// Remove claude-code hooks
|
|
217
|
+
const removeClaudeCodeHooks = () => {
|
|
218
|
+
let removed = false;
|
|
219
|
+
|
|
220
|
+
if (fs.existsSync(CLAUDE_HOOK_FILE)) {
|
|
221
|
+
fs.unlinkSync(CLAUDE_HOOK_FILE);
|
|
222
|
+
removed = true;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (fs.existsSync(CLAUDE_SETTINGS_PATH)) {
|
|
226
|
+
try {
|
|
227
|
+
const settings = JSON.parse(fs.readFileSync(CLAUDE_SETTINGS_PATH, "utf8"));
|
|
228
|
+
if (settings.hooks) {
|
|
229
|
+
let modified = false;
|
|
230
|
+
for (const eventName of Object.keys(settings.hooks)) {
|
|
231
|
+
const hooks = settings.hooks[eventName];
|
|
232
|
+
if (Array.isArray(hooks)) {
|
|
233
|
+
const filtered = hooks.filter((h) => {
|
|
234
|
+
if (Array.isArray(h.hooks)) {
|
|
235
|
+
h.hooks = h.hooks.filter((inner) => !inner.command?.includes("opencode-pixel-office-hook"));
|
|
236
|
+
return h.hooks.length > 0;
|
|
237
|
+
}
|
|
238
|
+
return !h.command?.includes("opencode-pixel-office-hook");
|
|
239
|
+
});
|
|
240
|
+
if (filtered.length !== hooks.length) {
|
|
241
|
+
settings.hooks[eventName] = filtered;
|
|
242
|
+
modified = true;
|
|
243
|
+
}
|
|
244
|
+
if (filtered.length === 0) {
|
|
245
|
+
delete settings.hooks[eventName];
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (Object.keys(settings.hooks).length === 0) {
|
|
250
|
+
delete settings.hooks;
|
|
251
|
+
}
|
|
252
|
+
if (modified) {
|
|
253
|
+
fs.writeFileSync(CLAUDE_SETTINGS_PATH, `${JSON.stringify(settings, null, 2)}\n`, "utf8");
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
} catch {}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (removed) {
|
|
260
|
+
console.log(`✓ Removed Claude Code hooks`);
|
|
261
|
+
}
|
|
262
|
+
return removed;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
// Install the standalone app (server + client)
|
|
266
|
+
const installApp = async (port) => {
|
|
267
|
+
const rootSource = path.resolve(__dirname, "..");
|
|
268
|
+
|
|
269
|
+
console.log(`\nInstalling Pixel Office app...`);
|
|
270
|
+
fs.mkdirSync(DEFAULT_APP_DIR, { recursive: true });
|
|
271
|
+
|
|
272
|
+
// Copy server
|
|
273
|
+
copyRecursiveSync(path.join(rootSource, "server"), path.join(DEFAULT_APP_DIR, "server"));
|
|
274
|
+
|
|
275
|
+
// Copy client/dist
|
|
276
|
+
const clientDist = path.join(rootSource, "client", "dist");
|
|
277
|
+
if (fs.existsSync(clientDist)) {
|
|
278
|
+
copyRecursiveSync(clientDist, path.join(DEFAULT_APP_DIR, "client", "dist"));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Copy package.json
|
|
282
|
+
fs.copyFileSync(path.join(rootSource, "package.json"), path.join(DEFAULT_APP_DIR, "package.json"));
|
|
283
|
+
|
|
284
|
+
// Save config
|
|
285
|
+
const configData = loadConfig();
|
|
286
|
+
if (port) {
|
|
287
|
+
configData.port = port;
|
|
288
|
+
}
|
|
289
|
+
saveConfig(configData);
|
|
290
|
+
|
|
291
|
+
// npm install
|
|
292
|
+
console.log(" Installing dependencies...");
|
|
293
|
+
try {
|
|
294
|
+
execSync("npm install --omit=dev --no-package-lock", {
|
|
295
|
+
cwd: DEFAULT_APP_DIR,
|
|
296
|
+
stdio: "pipe",
|
|
297
|
+
});
|
|
298
|
+
console.log(` ✓ App installed`);
|
|
299
|
+
} catch (e) {
|
|
300
|
+
console.error(" ! Failed to install dependencies");
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
|
|
167
304
|
const run = async () => {
|
|
168
305
|
if (shouldVersion) {
|
|
169
306
|
console.log(`opencode-pixel-office v${getVersion()}`);
|
|
170
307
|
process.exit(0);
|
|
171
308
|
}
|
|
172
309
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
310
|
+
// STATUS
|
|
311
|
+
if (shouldStatus) {
|
|
312
|
+
console.log(`\nPixel Office Status:\n`);
|
|
313
|
+
console.log(` OpenCode plugin: ${isOpencodeInstalled() ? "✓ Installed" : "✗ Not installed"}`);
|
|
314
|
+
console.log(` Claude Code hooks: ${isClaudeCodeInstalled() ? "✓ Installed" : "✗ Not installed"}`);
|
|
315
|
+
console.log(` App/Server: ${isAppInstalled() ? "✓ Installed" : "✗ Not installed"}`);
|
|
316
|
+
|
|
178
317
|
const config = loadConfig();
|
|
179
|
-
config.
|
|
180
|
-
|
|
181
|
-
|
|
318
|
+
const port = config.port || 5100;
|
|
319
|
+
|
|
320
|
+
let serverRunning = false;
|
|
321
|
+
try {
|
|
322
|
+
const pidOutput = execSync(`lsof -t -i :${port} 2>/dev/null`).toString().trim();
|
|
323
|
+
serverRunning = !!pidOutput;
|
|
324
|
+
} catch {}
|
|
325
|
+
|
|
326
|
+
console.log(` Server: ${serverRunning ? `✓ Running on port ${port}` : "✗ Not running"}`);
|
|
327
|
+
console.log("");
|
|
182
328
|
process.exit(0);
|
|
183
329
|
}
|
|
184
330
|
|
|
185
|
-
|
|
331
|
+
// INSTALL
|
|
332
|
+
if (shouldInstall) {
|
|
333
|
+
console.log(`\nInstalling Pixel Office...\n`);
|
|
334
|
+
|
|
335
|
+
// Install OpenCode plugin
|
|
336
|
+
console.log("OpenCode:");
|
|
337
|
+
installOpencodePlugin();
|
|
338
|
+
|
|
339
|
+
// Install Claude Code hooks
|
|
340
|
+
console.log("\nClaude Code:");
|
|
186
341
|
installClaudeCodeHooks();
|
|
342
|
+
|
|
343
|
+
// Install app
|
|
344
|
+
const port = portArg ? parseInt(portArg, 10) : null;
|
|
345
|
+
await installApp(port);
|
|
346
|
+
|
|
347
|
+
console.log("\n✓ Installation complete!");
|
|
348
|
+
console.log("\nBoth OpenCode and Claude Code are now connected.");
|
|
349
|
+
console.log("Run 'opencode-pixel-office start' to launch the server.");
|
|
187
350
|
process.exit(0);
|
|
188
351
|
}
|
|
189
352
|
|
|
353
|
+
// UNINSTALL
|
|
190
354
|
if (shouldUninstall) {
|
|
191
|
-
// Stop server first
|
|
192
355
|
const config = loadConfig();
|
|
193
356
|
const port = config.port || 5100;
|
|
194
|
-
console.log("Stopping Pixel Office server...");
|
|
195
|
-
if (!stopServer(port)) {
|
|
196
|
-
console.log("- Server not running");
|
|
197
|
-
}
|
|
198
357
|
|
|
199
|
-
|
|
358
|
+
console.log("\nUninstalling Pixel Office...\n");
|
|
200
359
|
|
|
201
|
-
|
|
202
|
-
if (
|
|
203
|
-
|
|
204
|
-
console.log(`✓ Removed plugin: ${targetPluginPath}`);
|
|
205
|
-
} else {
|
|
206
|
-
console.log(`- Plugin not found at ${targetPluginPath}`);
|
|
360
|
+
console.log("Stopping server...");
|
|
361
|
+
if (!stopServer(port)) {
|
|
362
|
+
console.log("- Server not running");
|
|
207
363
|
}
|
|
208
364
|
|
|
209
|
-
// Remove
|
|
210
|
-
if (
|
|
211
|
-
|
|
212
|
-
console.log(`✓ Removed app directory: ${DEFAULT_APP_DIR}`);
|
|
213
|
-
} else {
|
|
214
|
-
console.log(`- App directory not found at ${DEFAULT_APP_DIR}`);
|
|
365
|
+
// Remove OpenCode plugin
|
|
366
|
+
if (!removeOpencodePlugin()) {
|
|
367
|
+
console.log("- OpenCode plugin not found");
|
|
215
368
|
}
|
|
216
369
|
|
|
217
370
|
// Remove Claude Code hooks
|
|
218
|
-
|
|
219
|
-
|
|
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}`);
|
|
371
|
+
if (!removeClaudeCodeHooks()) {
|
|
372
|
+
console.log("- Claude Code hooks not found");
|
|
226
373
|
}
|
|
227
374
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
267
|
-
// Clean up Config (Legacy)
|
|
268
|
-
if (fs.existsSync(DEFAULT_CONFIG_PATH) && !skipJson) {
|
|
269
|
-
try {
|
|
270
|
-
const raw = fs.readFileSync(DEFAULT_CONFIG_PATH, "utf8");
|
|
271
|
-
const data = JSON.parse(raw);
|
|
272
|
-
if (Array.isArray(data.plugin)) {
|
|
273
|
-
const initialLength = data.plugin.length;
|
|
274
|
-
data.plugin = data.plugin.filter(p => p !== PLUGIN_ID);
|
|
275
|
-
if (data.plugin.length !== initialLength) {
|
|
276
|
-
fs.writeFileSync(DEFAULT_CONFIG_PATH, `${JSON.stringify(data, null, 2)}\n`, "utf8");
|
|
277
|
-
console.log(`✓ Removed ${PLUGIN_ID} from opencode.json`);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
} catch (e) {
|
|
281
|
-
// ignore config errors on uninstall
|
|
282
|
-
}
|
|
375
|
+
// Remove app directory
|
|
376
|
+
if (fs.existsSync(DEFAULT_APP_DIR)) {
|
|
377
|
+
fs.rmSync(DEFAULT_APP_DIR, { recursive: true, force: true });
|
|
378
|
+
console.log(`✓ Removed app directory`);
|
|
283
379
|
}
|
|
284
380
|
|
|
285
|
-
console.log("\
|
|
381
|
+
console.log("\n✓ Uninstallation complete.");
|
|
286
382
|
process.exit(0);
|
|
287
383
|
}
|
|
288
384
|
|
|
385
|
+
// START
|
|
289
386
|
if (shouldStart) {
|
|
290
387
|
const config = loadConfig();
|
|
291
|
-
const port = portArg ? parseInt(portArg, 10) :
|
|
388
|
+
const port = portArg ? parseInt(portArg, 10) : config.port || 5100;
|
|
292
389
|
const serverScript = "server/index.ts";
|
|
293
390
|
|
|
294
391
|
const rootSource = path.resolve(__dirname, "..");
|
|
@@ -296,30 +393,31 @@ const run = async () => {
|
|
|
296
393
|
const globalServerPath = path.join(DEFAULT_APP_DIR, "server", "index.ts");
|
|
297
394
|
|
|
298
395
|
let serverCwd;
|
|
299
|
-
let useGlobal = false;
|
|
300
396
|
if (fs.existsSync(localServerPath)) {
|
|
301
397
|
serverCwd = rootSource;
|
|
302
398
|
} else if (fs.existsSync(globalServerPath)) {
|
|
303
399
|
serverCwd = DEFAULT_APP_DIR;
|
|
304
|
-
useGlobal = true;
|
|
305
400
|
} else {
|
|
306
401
|
console.error("Server not found. Run 'opencode-pixel-office install' first.");
|
|
307
402
|
process.exit(1);
|
|
308
403
|
}
|
|
309
404
|
|
|
405
|
+
// Check if already running
|
|
310
406
|
try {
|
|
311
407
|
const pidOutput = execSync(`lsof -t -i :${port} 2>/dev/null`).toString().trim();
|
|
312
408
|
if (pidOutput) {
|
|
313
|
-
console.log(`Pixel Office is already running on port ${port}
|
|
409
|
+
console.log(`Pixel Office is already running on port ${port}`);
|
|
314
410
|
const url = `http://localhost:${port}`;
|
|
315
411
|
const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
316
|
-
try {
|
|
412
|
+
try {
|
|
413
|
+
execSync(`${openCmd} ${url}`);
|
|
414
|
+
} catch {}
|
|
317
415
|
console.log(`Opened ${url}`);
|
|
318
416
|
process.exit(0);
|
|
319
417
|
}
|
|
320
|
-
} catch {
|
|
418
|
+
} catch {}
|
|
321
419
|
|
|
322
|
-
// Resolve tsx binary
|
|
420
|
+
// Resolve tsx binary
|
|
323
421
|
let tsxBin = null;
|
|
324
422
|
const tsxLocations = [
|
|
325
423
|
path.join(serverCwd, "node_modules", ".bin", "tsx"),
|
|
@@ -333,7 +431,6 @@ const run = async () => {
|
|
|
333
431
|
}
|
|
334
432
|
}
|
|
335
433
|
if (!tsxBin) {
|
|
336
|
-
// Fallback to PATH
|
|
337
434
|
tsxBin = "tsx";
|
|
338
435
|
}
|
|
339
436
|
|
|
@@ -358,12 +455,15 @@ const run = async () => {
|
|
|
358
455
|
await new Promise((r) => setTimeout(r, 1500));
|
|
359
456
|
const url = `http://localhost:${port}`;
|
|
360
457
|
const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
361
|
-
try {
|
|
458
|
+
try {
|
|
459
|
+
execSync(`${openCmd} ${url}`);
|
|
460
|
+
} catch {}
|
|
362
461
|
console.log(`Opened ${url}`);
|
|
363
462
|
process.exit(0);
|
|
364
463
|
}
|
|
365
464
|
|
|
366
|
-
|
|
465
|
+
// STOP
|
|
466
|
+
if (shouldStop) {
|
|
367
467
|
const config = loadConfig();
|
|
368
468
|
const port = config.port || 5100;
|
|
369
469
|
console.log(`Stopping Pixel Office server on port ${port}...`);
|
|
@@ -373,92 +473,12 @@ const run = async () => {
|
|
|
373
473
|
process.exit(0);
|
|
374
474
|
}
|
|
375
475
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
const rootSource = path.resolve(__dirname, "..");
|
|
382
|
-
const pluginSource = path.join(rootSource, "plugin", PLUGIN_NAME);
|
|
383
|
-
|
|
384
|
-
if (!fs.existsSync(pluginSource)) {
|
|
385
|
-
console.error(`Plugin file not found: ${pluginSource}`);
|
|
386
|
-
process.exit(1);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// 1. Install Plugin Script
|
|
390
|
-
fs.mkdirSync(DEFAULT_PLUGIN_DIR, { recursive: true });
|
|
391
|
-
const targetPluginPath = path.join(DEFAULT_PLUGIN_DIR, PLUGIN_NAME);
|
|
392
|
-
|
|
393
|
-
if (fs.existsSync(targetPluginPath) && !yesFlag) {
|
|
394
|
-
const answer = await prompt(`Overwrite existing ${PLUGIN_NAME}? (y/N): `);
|
|
395
|
-
if (answer !== "y" && answer !== "yes") {
|
|
396
|
-
console.log("Aborted.");
|
|
397
|
-
process.exit(0);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
fs.copyFileSync(pluginSource, targetPluginPath);
|
|
401
|
-
console.log(`✓ Installed plugin to ${targetPluginPath}`);
|
|
402
|
-
|
|
403
|
-
// 2. Install Standalone App (Server + Client)
|
|
404
|
-
console.log(`Installing standalone app to ${DEFAULT_APP_DIR}...`);
|
|
405
|
-
if (fs.existsSync(DEFAULT_APP_DIR)) {
|
|
406
|
-
// rudimentary clean? probably safer to just overwrite
|
|
407
|
-
} else {
|
|
408
|
-
fs.mkdirSync(DEFAULT_APP_DIR, { recursive: true });
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// Copy server
|
|
412
|
-
console.log(" - Copying server...");
|
|
413
|
-
copyRecursiveSync(path.join(rootSource, "server"), path.join(DEFAULT_APP_DIR, "server"));
|
|
414
|
-
|
|
415
|
-
// Copy client/dist
|
|
416
|
-
console.log(" - Copying client assets...");
|
|
417
|
-
const clientDist = path.join(rootSource, "client", "dist");
|
|
418
|
-
if (fs.existsSync(clientDist)) {
|
|
419
|
-
copyRecursiveSync(clientDist, path.join(DEFAULT_APP_DIR, "client", "dist"));
|
|
420
|
-
} else {
|
|
421
|
-
console.warn(" ! Warning: client/dist not found. Did you run 'npm run build:client'?");
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
// Copy package.json
|
|
425
|
-
console.log(" - Copying package.json...");
|
|
426
|
-
fs.copyFileSync(path.join(rootSource, "package.json"), path.join(DEFAULT_APP_DIR, "package.json"));
|
|
427
|
-
|
|
428
|
-
// Save Port Config if specified
|
|
429
|
-
const configData = loadConfig();
|
|
430
|
-
if (portArg) {
|
|
431
|
-
const port = parseInt(portArg, 10);
|
|
432
|
-
if (!isNaN(port)) {
|
|
433
|
-
configData.port = port;
|
|
434
|
-
console.log(` - Saved port configuration: ${port}`);
|
|
435
|
-
} else {
|
|
436
|
-
console.warn(" ! Invalid port number provided. Using default.");
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
if (!configData.mode) {
|
|
440
|
-
configData.mode = "opencode";
|
|
441
|
-
}
|
|
442
|
-
saveConfig(configData);
|
|
443
|
-
|
|
444
|
-
// npm install
|
|
445
|
-
console.log(" - Installing production dependencies...");
|
|
446
|
-
try {
|
|
447
|
-
execSync("npm install --omit=dev --no-package-lock", {
|
|
448
|
-
cwd: DEFAULT_APP_DIR,
|
|
449
|
-
stdio: "inherit"
|
|
450
|
-
});
|
|
451
|
-
} catch (e) {
|
|
452
|
-
console.error(" ! Failed to install dependencies:", e.message);
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
console.log(`✓ Standalone app installed to ${DEFAULT_APP_DIR}`);
|
|
456
|
-
|
|
457
|
-
console.log("\nInstallation complete!");
|
|
458
|
-
console.log("Run 'opencode-pixel-office start' to launch the server.");
|
|
476
|
+
// DEFAULT: show help
|
|
477
|
+
printHelp();
|
|
478
|
+
process.exit(0);
|
|
459
479
|
};
|
|
460
480
|
|
|
461
481
|
run().catch((error) => {
|
|
462
|
-
console.error("
|
|
482
|
+
console.error("Error:", error);
|
|
463
483
|
process.exit(1);
|
|
464
484
|
});
|