@pixelbyte-software/pixcode 1.53.27 → 1.54.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.
@@ -0,0 +1,257 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import net from 'node:net';
5
+ import readline from 'readline';
6
+ import { exec } from 'node:child_process';
7
+
8
+ import { c } from './utils/colors.js';
9
+
10
+ const PIXCODE_DIR = path.join(os.homedir(), '.pixcode');
11
+ const SETUP_MARKER = path.join(PIXCODE_DIR, '.setup-complete');
12
+ const DEFAULT_PORT = 3001;
13
+
14
+ function isFirstRun() {
15
+ return !fs.existsSync(SETUP_MARKER);
16
+ }
17
+
18
+ function markSetupComplete(config) {
19
+ try {
20
+ fs.mkdirSync(PIXCODE_DIR, { recursive: true });
21
+ fs.writeFileSync(SETUP_MARKER, JSON.stringify({
22
+ completedAt: new Date().toISOString(),
23
+ ...config,
24
+ }, null, 2));
25
+ } catch {
26
+ // Non-fatal — setup can complete without persistence
27
+ }
28
+ }
29
+
30
+ function readSetupConfig() {
31
+ try {
32
+ return JSON.parse(fs.readFileSync(SETUP_MARKER, 'utf8'));
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
37
+
38
+ function isPortAvailable(port) {
39
+ return new Promise((resolve) => {
40
+ const tester = net.createServer();
41
+ tester.once('error', () => resolve(false));
42
+ tester.once('listening', () => {
43
+ tester.close(() => resolve(true));
44
+ });
45
+ tester.listen(Number(port), '0.0.0.0');
46
+ });
47
+ }
48
+
49
+ async function findAvailablePort(startPort, maxAttempts = 10) {
50
+ for (let port = startPort; port < startPort + maxAttempts; port++) {
51
+ if (await isPortAvailable(port)) return port;
52
+ }
53
+ return null;
54
+ }
55
+
56
+ function isPortOpen(port, timeoutMs = 800) {
57
+ return new Promise((resolve) => {
58
+ const socket = net.createConnection({ host: '127.0.0.1', port: Number(port) });
59
+ let settled = false;
60
+ const done = (value) => {
61
+ if (settled) return;
62
+ settled = true;
63
+ socket.destroy();
64
+ resolve(value);
65
+ };
66
+ socket.setTimeout(timeoutMs);
67
+ socket.once('connect', () => done(true));
68
+ socket.once('timeout', () => done(false));
69
+ socket.once('error', () => done(false));
70
+ });
71
+ }
72
+
73
+ async function waitForServerReady(port, timeoutMs = 30000) {
74
+ const deadline = Date.now() + timeoutMs;
75
+ while (Date.now() < deadline) {
76
+ if (await isPortOpen(port)) return true;
77
+ await new Promise(resolve => setTimeout(resolve, 500));
78
+ }
79
+ return false;
80
+ }
81
+
82
+ function ask(question) {
83
+ return new Promise((resolve) => {
84
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
85
+ resolve(null);
86
+ return;
87
+ }
88
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
89
+ rl.question(question, (answer) => {
90
+ rl.close();
91
+ resolve(answer?.trim() || '');
92
+ });
93
+ });
94
+ }
95
+
96
+ async function askSelect(question, options) {
97
+ while (true) {
98
+ const answer = await ask(question);
99
+ if (answer === null) return null;
100
+ const lower = answer.toLowerCase();
101
+ for (const opt of options) {
102
+ if (lower === opt.key || lower === String(opt.key)) return opt;
103
+ if (opt.aliases?.some(a => lower === a)) return opt;
104
+ }
105
+ console.log(`${c.warn(' Invalid choice. Please try again.')}`);
106
+ }
107
+ }
108
+
109
+ function openBrowser(url) {
110
+ const platform = process.platform;
111
+ let cmd;
112
+ if (platform === 'darwin') {
113
+ cmd = `open "${url}"`;
114
+ } else if (platform === 'win32') {
115
+ cmd = `start "" "${url}"`;
116
+ } else {
117
+ cmd = `xdg-open "${url}"`;
118
+ }
119
+ try {
120
+ exec(cmd, { stdio: 'ignore', timeout: 3000 });
121
+ return true;
122
+ } catch {
123
+ return false;
124
+ }
125
+ }
126
+
127
+ function printBanner() {
128
+ console.log('');
129
+ console.log(c.dim(' ╔═══════════════════════════════════════════════════════════════╗'));
130
+ console.log(c.dim(' ║') + c.bright(' Pixcode Setup Wizard ') + c.dim('║'));
131
+ console.log(c.dim(' ╠═══════════════════════════════════════════════════════════════╣'));
132
+ console.log(c.dim(' ║') + c.info(' Self-hosted AI coding agent control room ') + c.dim('║'));
133
+ console.log(c.dim(' ║') + c.dim(' Claude Code, Cursor, Codex, Gemini, Qwen... ') + c.dim('║'));
134
+ console.log(c.dim(' ╚═══════════════════════════════════════════════════════════════╝'));
135
+ console.log('');
136
+ }
137
+
138
+ function printReady(port) {
139
+ const url = `http://localhost:${port}`;
140
+ console.log('');
141
+ console.log(c.dim(' ╔═══════════════════════════════════════════════════════════════╗'));
142
+ console.log(c.dim(' ║') + c.bright(' Pixcode is Ready! ') + c.dim('║'));
143
+ console.log(c.dim(' ╠═══════════════════════════════════════════════════════════════╣'));
144
+ console.log(c.dim(' ║') + ` Open ${c.bright(url)} in your browser ` + c.dim('║'));
145
+ console.log(c.dim(' ╚═══════════════════════════════════════════════════════════════╝'));
146
+ console.log('');
147
+ console.log(` ${c.info('[INFO]')} Web UI: ${c.bright(url)}`);
148
+ console.log(` ${c.info('[INFO]')} Health: ${c.dim(url + '/health')}`);
149
+ console.log('');
150
+ }
151
+
152
+ export async function runSetupWizard(existingOptions = {}) {
153
+ if (!isFirstRun()) return null;
154
+
155
+ printBanner();
156
+
157
+ console.log(` ${c.info('Welcome!')} Let's get Pixcode configured.\n`);
158
+
159
+ // Step 1: Port selection
160
+ let port = existingOptions.serverPort || process.env.SERVER_PORT || DEFAULT_PORT;
161
+ const portAvailable = await isPortAvailable(port);
162
+
163
+ if (!portAvailable) {
164
+ console.log(` ${c.warn('[WARN]')} Port ${c.bright(String(port))} is already in use.`);
165
+ const altPort = await findAvailablePort(Number(port) + 1);
166
+ if (altPort) {
167
+ console.log(` ${c.info('[INFO]')} Port ${c.bright(String(altPort))} is available.`);
168
+ const choice = await askSelect(
169
+ ` Use port ${altPort}? [Y/n] `,
170
+ [
171
+ { key: 'y', label: 'yes', aliases: ['yes', '', 'e', 'evet'] },
172
+ { key: 'n', label: 'no', aliases: ['no', 'n', 'h', 'hayir'] },
173
+ ],
174
+ );
175
+ if (choice?.key !== 'n') {
176
+ port = altPort;
177
+ } else {
178
+ const customPort = await ask(` Enter a port number (or press Enter for ${altPort}): `);
179
+ if (customPort && /^\d+$/.test(customPort)) {
180
+ port = Number(customPort);
181
+ } else {
182
+ port = altPort;
183
+ }
184
+ }
185
+ } else {
186
+ const customPort = await ask(` No free ports found near ${port}. Enter a port manually: `);
187
+ if (customPort && /^\d+$/.test(customPort)) {
188
+ port = Number(customPort);
189
+ }
190
+ }
191
+ } else {
192
+ console.log(` ${c.ok('[OK]')} Port ${c.bright(String(port))} is available.`);
193
+ const customPort = await ask(` Press Enter to use port ${port}, or enter a different port: `);
194
+ if (customPort && /^\d+$/.test(customPort)) {
195
+ const newPort = Number(customPort);
196
+ if (await isPortAvailable(newPort)) {
197
+ port = newPort;
198
+ console.log(` ${c.ok('[OK]')} Port ${c.bright(String(port))} is available.`);
199
+ } else {
200
+ console.log(` ${c.warn('[WARN]')} Port ${newPort} is in use. Keeping port ${port}.`);
201
+ }
202
+ }
203
+ }
204
+
205
+ process.env.SERVER_PORT = String(port);
206
+
207
+ // Step 2: Mode selection (Linux only)
208
+ let useDaemon = existingOptions.noDaemon ? false : (process.platform === 'linux');
209
+
210
+ if (process.platform === 'linux' && !existingOptions.noDaemon) {
211
+ console.log('');
212
+ const modeChoice = await askSelect(
213
+ ` ${c.info('Run mode:')} Keep running in background (daemon) or foreground?\n` +
214
+ ` ${c.dim(' [1]')} Daemon (recommended — survives terminal close)\n` +
215
+ ` ${c.dim(' [2]')} Foreground (stops when terminal closes)\n` +
216
+ ` Choose [1/2] (default 1): `,
217
+ [
218
+ { key: '1', label: 'daemon', aliases: ['d', 'daemon', ''] },
219
+ { key: '2', label: 'foreground', aliases: ['f', 'fg', 'foreground'] },
220
+ ],
221
+ );
222
+ useDaemon = modeChoice?.key !== '2';
223
+ }
224
+
225
+ // Step 3: Summary
226
+ console.log('');
227
+ console.log(` ${c.info('[Setup]')} Configuration:`);
228
+ console.log(` Port: ${c.bright(String(port))}`);
229
+ if (process.platform === 'linux') {
230
+ console.log(` Mode: ${c.bright(useDaemon ? 'Daemon (background)' : 'Foreground')}`);
231
+ }
232
+ console.log('');
233
+
234
+ markSetupComplete({ port, mode: useDaemon ? 'daemon' : 'foreground' });
235
+
236
+ return { port, useDaemon };
237
+ }
238
+
239
+ export async function postStartupGuidance(port) {
240
+ const effectivePort = Number(port) || DEFAULT_PORT;
241
+ const url = `http://localhost:${effectivePort}`;
242
+
243
+ printReady(effectivePort);
244
+
245
+ // Try to open browser
246
+ const opened = openBrowser(url);
247
+ if (opened) {
248
+ console.log(` ${c.ok('[OK]')} Opening browser...`);
249
+ } else {
250
+ console.log(` ${c.tip('[TIP]')} Open ${c.bright(url)} in your browser to start using Pixcode.`);
251
+ }
252
+
253
+ console.log(` ${c.dim('Tip: Run "pixcode status" to see full configuration.')}`);
254
+ console.log('');
255
+ }
256
+
257
+ export { isFirstRun, isPortAvailable, findAvailablePort, openBrowser, waitForServerReady, isPortOpen };