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.
@@ -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 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;
24
- const shouldUninstall = args.includes("uninstall");
25
- const shouldStart = args[0] === "start";
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("opencode-pixel-office installer\n");
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 [--yes] [--port <number>]");
46
- console.log(" opencode-pixel-office claude-code install");
47
- console.log(" opencode-pixel-office switch <opencode|claude-code>");
48
- console.log(" opencode-pixel-office start");
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> Configure the server port (default: 5100)");
53
- console.log(" --no-json Skip updating opencode.json");
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
- const installClaudeCodeHooks = () => {
59
- const claudeDir = path.join(os.homedir(), ".claude");
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, 'utf8'));
68
+ return JSON.parse(fs.readFileSync(PIXEL_OFFICE_CONFIG_PATH, "utf8"));
107
69
  }
108
- } catch (e) { }
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('\n').map(p => parseInt(p, 10)).filter(p => !isNaN(p) && p > 0);
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
- // lsof failed, no process found
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
- if (shouldSwitch) {
174
- if (switchTarget !== "opencode" && switchTarget !== "claude-code") {
175
- console.error("Switch target must be 'opencode' or 'claude-code'.");
176
- process.exit(1);
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.mode = switchTarget;
180
- saveConfig(config);
181
- console.log(`✓ Mode set to ${switchTarget}`);
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
- if (shouldInstallClaude) {
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
- const targetPluginPath = path.join(DEFAULT_PLUGIN_DIR, PLUGIN_NAME);
358
+ console.log("\nUninstalling Pixel Office...\n");
200
359
 
201
- // Remove Plugin
202
- if (fs.existsSync(targetPluginPath)) {
203
- fs.unlinkSync(targetPluginPath);
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 App
210
- if (fs.existsSync(DEFAULT_APP_DIR)) {
211
- fs.rmSync(DEFAULT_APP_DIR, { recursive: true, force: true });
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
- 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}`);
371
+ if (!removeClaudeCodeHooks()) {
372
+ console.log("- Claude Code hooks not found");
226
373
  }
227
374
 
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
-
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("\nUninstallation complete.");
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) : (config.port || 5100);
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} (PID: ${pidOutput})`);
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 { execSync(`${openCmd} ${url}`); } catch { }
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 - check multiple locations
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 { execSync(`${openCmd} ${url}`); } catch { }
458
+ try {
459
+ execSync(`${openCmd} ${url}`);
460
+ } catch {}
362
461
  console.log(`Opened ${url}`);
363
462
  process.exit(0);
364
463
  }
365
464
 
366
- if (args.includes("stop")) {
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
- if (!shouldInstall) {
377
- printHelp();
378
- process.exit(0);
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("Installer failed:", error);
482
+ console.error("Error:", error);
463
483
  process.exit(1);
464
484
  });