@pixelbyte-software/pixcode 1.53.28 → 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,240 @@
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
+ import { c } from './utils/colors.js';
8
+ const PIXCODE_DIR = path.join(os.homedir(), '.pixcode');
9
+ const SETUP_MARKER = path.join(PIXCODE_DIR, '.setup-complete');
10
+ const DEFAULT_PORT = 3001;
11
+ function isFirstRun() {
12
+ return !fs.existsSync(SETUP_MARKER);
13
+ }
14
+ function markSetupComplete(config) {
15
+ try {
16
+ fs.mkdirSync(PIXCODE_DIR, { recursive: true });
17
+ fs.writeFileSync(SETUP_MARKER, JSON.stringify({
18
+ completedAt: new Date().toISOString(),
19
+ ...config,
20
+ }, null, 2));
21
+ }
22
+ catch {
23
+ // Non-fatal — setup can complete without persistence
24
+ }
25
+ }
26
+ function readSetupConfig() {
27
+ try {
28
+ return JSON.parse(fs.readFileSync(SETUP_MARKER, 'utf8'));
29
+ }
30
+ catch {
31
+ return null;
32
+ }
33
+ }
34
+ function isPortAvailable(port) {
35
+ return new Promise((resolve) => {
36
+ const tester = net.createServer();
37
+ tester.once('error', () => resolve(false));
38
+ tester.once('listening', () => {
39
+ tester.close(() => resolve(true));
40
+ });
41
+ tester.listen(Number(port), '0.0.0.0');
42
+ });
43
+ }
44
+ async function findAvailablePort(startPort, maxAttempts = 10) {
45
+ for (let port = startPort; port < startPort + maxAttempts; port++) {
46
+ if (await isPortAvailable(port))
47
+ return port;
48
+ }
49
+ return null;
50
+ }
51
+ function isPortOpen(port, timeoutMs = 800) {
52
+ return new Promise((resolve) => {
53
+ const socket = net.createConnection({ host: '127.0.0.1', port: Number(port) });
54
+ let settled = false;
55
+ const done = (value) => {
56
+ if (settled)
57
+ return;
58
+ settled = true;
59
+ socket.destroy();
60
+ resolve(value);
61
+ };
62
+ socket.setTimeout(timeoutMs);
63
+ socket.once('connect', () => done(true));
64
+ socket.once('timeout', () => done(false));
65
+ socket.once('error', () => done(false));
66
+ });
67
+ }
68
+ async function waitForServerReady(port, timeoutMs = 30000) {
69
+ const deadline = Date.now() + timeoutMs;
70
+ while (Date.now() < deadline) {
71
+ if (await isPortOpen(port))
72
+ return true;
73
+ await new Promise(resolve => setTimeout(resolve, 500));
74
+ }
75
+ return false;
76
+ }
77
+ function ask(question) {
78
+ return new Promise((resolve) => {
79
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
80
+ resolve(null);
81
+ return;
82
+ }
83
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
84
+ rl.question(question, (answer) => {
85
+ rl.close();
86
+ resolve(answer?.trim() || '');
87
+ });
88
+ });
89
+ }
90
+ async function askSelect(question, options) {
91
+ while (true) {
92
+ const answer = await ask(question);
93
+ if (answer === null)
94
+ return null;
95
+ const lower = answer.toLowerCase();
96
+ for (const opt of options) {
97
+ if (lower === opt.key || lower === String(opt.key))
98
+ return opt;
99
+ if (opt.aliases?.some(a => lower === a))
100
+ return opt;
101
+ }
102
+ console.log(`${c.warn(' Invalid choice. Please try again.')}`);
103
+ }
104
+ }
105
+ function openBrowser(url) {
106
+ const platform = process.platform;
107
+ let cmd;
108
+ if (platform === 'darwin') {
109
+ cmd = `open "${url}"`;
110
+ }
111
+ else if (platform === 'win32') {
112
+ cmd = `start "" "${url}"`;
113
+ }
114
+ else {
115
+ cmd = `xdg-open "${url}"`;
116
+ }
117
+ try {
118
+ exec(cmd, { stdio: 'ignore', timeout: 3000 });
119
+ return true;
120
+ }
121
+ catch {
122
+ return false;
123
+ }
124
+ }
125
+ function printBanner() {
126
+ console.log('');
127
+ console.log(c.dim(' ╔═══════════════════════════════════════════════════════════════╗'));
128
+ console.log(c.dim(' ║') + c.bright(' Pixcode Setup Wizard ') + c.dim('║'));
129
+ console.log(c.dim(' ╠═══════════════════════════════════════════════════════════════╣'));
130
+ console.log(c.dim(' ║') + c.info(' Self-hosted AI coding agent control room ') + c.dim('║'));
131
+ console.log(c.dim(' ║') + c.dim(' Claude Code, Cursor, Codex, Gemini, Qwen... ') + c.dim('║'));
132
+ console.log(c.dim(' ╚═══════════════════════════════════════════════════════════════╝'));
133
+ console.log('');
134
+ }
135
+ function printReady(port) {
136
+ const url = `http://localhost:${port}`;
137
+ console.log('');
138
+ console.log(c.dim(' ╔═══════════════════════════════════════════════════════════════╗'));
139
+ console.log(c.dim(' ║') + c.bright(' Pixcode is Ready! ') + c.dim('║'));
140
+ console.log(c.dim(' ╠═══════════════════════════════════════════════════════════════╣'));
141
+ console.log(c.dim(' ║') + ` Open ${c.bright(url)} in your browser ` + c.dim('║'));
142
+ console.log(c.dim(' ╚═══════════════════════════════════════════════════════════════╝'));
143
+ console.log('');
144
+ console.log(` ${c.info('[INFO]')} Web UI: ${c.bright(url)}`);
145
+ console.log(` ${c.info('[INFO]')} Health: ${c.dim(url + '/health')}`);
146
+ console.log('');
147
+ }
148
+ export async function runSetupWizard(existingOptions = {}) {
149
+ if (!isFirstRun())
150
+ return null;
151
+ printBanner();
152
+ console.log(` ${c.info('Welcome!')} Let's get Pixcode configured.\n`);
153
+ // Step 1: Port selection
154
+ let port = existingOptions.serverPort || process.env.SERVER_PORT || DEFAULT_PORT;
155
+ const portAvailable = await isPortAvailable(port);
156
+ if (!portAvailable) {
157
+ console.log(` ${c.warn('[WARN]')} Port ${c.bright(String(port))} is already in use.`);
158
+ const altPort = await findAvailablePort(Number(port) + 1);
159
+ if (altPort) {
160
+ console.log(` ${c.info('[INFO]')} Port ${c.bright(String(altPort))} is available.`);
161
+ const choice = await askSelect(` Use port ${altPort}? [Y/n] `, [
162
+ { key: 'y', label: 'yes', aliases: ['yes', '', 'e', 'evet'] },
163
+ { key: 'n', label: 'no', aliases: ['no', 'n', 'h', 'hayir'] },
164
+ ]);
165
+ if (choice?.key !== 'n') {
166
+ port = altPort;
167
+ }
168
+ else {
169
+ const customPort = await ask(` Enter a port number (or press Enter for ${altPort}): `);
170
+ if (customPort && /^\d+$/.test(customPort)) {
171
+ port = Number(customPort);
172
+ }
173
+ else {
174
+ port = altPort;
175
+ }
176
+ }
177
+ }
178
+ else {
179
+ const customPort = await ask(` No free ports found near ${port}. Enter a port manually: `);
180
+ if (customPort && /^\d+$/.test(customPort)) {
181
+ port = Number(customPort);
182
+ }
183
+ }
184
+ }
185
+ else {
186
+ console.log(` ${c.ok('[OK]')} Port ${c.bright(String(port))} is available.`);
187
+ const customPort = await ask(` Press Enter to use port ${port}, or enter a different port: `);
188
+ if (customPort && /^\d+$/.test(customPort)) {
189
+ const newPort = Number(customPort);
190
+ if (await isPortAvailable(newPort)) {
191
+ port = newPort;
192
+ console.log(` ${c.ok('[OK]')} Port ${c.bright(String(port))} is available.`);
193
+ }
194
+ else {
195
+ console.log(` ${c.warn('[WARN]')} Port ${newPort} is in use. Keeping port ${port}.`);
196
+ }
197
+ }
198
+ }
199
+ process.env.SERVER_PORT = String(port);
200
+ // Step 2: Mode selection (Linux only)
201
+ let useDaemon = existingOptions.noDaemon ? false : (process.platform === 'linux');
202
+ if (process.platform === 'linux' && !existingOptions.noDaemon) {
203
+ console.log('');
204
+ const modeChoice = await askSelect(` ${c.info('Run mode:')} Keep running in background (daemon) or foreground?\n` +
205
+ ` ${c.dim(' [1]')} Daemon (recommended — survives terminal close)\n` +
206
+ ` ${c.dim(' [2]')} Foreground (stops when terminal closes)\n` +
207
+ ` Choose [1/2] (default 1): `, [
208
+ { key: '1', label: 'daemon', aliases: ['d', 'daemon', ''] },
209
+ { key: '2', label: 'foreground', aliases: ['f', 'fg', 'foreground'] },
210
+ ]);
211
+ useDaemon = modeChoice?.key !== '2';
212
+ }
213
+ // Step 3: Summary
214
+ console.log('');
215
+ console.log(` ${c.info('[Setup]')} Configuration:`);
216
+ console.log(` Port: ${c.bright(String(port))}`);
217
+ if (process.platform === 'linux') {
218
+ console.log(` Mode: ${c.bright(useDaemon ? 'Daemon (background)' : 'Foreground')}`);
219
+ }
220
+ console.log('');
221
+ markSetupComplete({ port, mode: useDaemon ? 'daemon' : 'foreground' });
222
+ return { port, useDaemon };
223
+ }
224
+ export async function postStartupGuidance(port) {
225
+ const effectivePort = Number(port) || DEFAULT_PORT;
226
+ const url = `http://localhost:${effectivePort}`;
227
+ printReady(effectivePort);
228
+ // Try to open browser
229
+ const opened = openBrowser(url);
230
+ if (opened) {
231
+ console.log(` ${c.ok('[OK]')} Opening browser...`);
232
+ }
233
+ else {
234
+ console.log(` ${c.tip('[TIP]')} Open ${c.bright(url)} in your browser to start using Pixcode.`);
235
+ }
236
+ console.log(` ${c.dim('Tip: Run "pixcode status" to see full configuration.')}`);
237
+ console.log('');
238
+ }
239
+ export { isFirstRun, isPortAvailable, findAvailablePort, openBrowser, waitForServerReady, isPortOpen };
240
+ //# sourceMappingURL=setup-wizard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup-wizard.js","sourceRoot":"","sources":["../../server/setup-wizard.js"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixelbyte-software/pixcode",
3
- "version": "1.53.28",
3
+ "version": "1.54.0",
4
4
  "description": "Self-hosted AI coding agent control room for Claude Code, Cursor CLI, OpenAI Codex, Gemini CLI, Qwen Code, and OpenCode with chat, files, shell, Git, orchestration, API keys, Telegram, MCP, plugins, themes, and desktop/server deployment.",
5
5
  "type": "module",
6
6
  "main": "dist-server/server/index.js",
package/server/cli.js CHANGED
@@ -7,6 +7,7 @@
7
7
  * Commands:
8
8
  * (no args) - Start the server (default)
9
9
  * start - Start the server
10
+ * setup - Run the interactive setup wizard
10
11
  * sandbox - Manage Docker sandbox environments
11
12
  * daemon - Manage persistent Linux service modes
12
13
  * status - Show configuration and data locations
@@ -23,6 +24,7 @@ import { spawn } from 'node:child_process';
23
24
  import { findAppRoot, getModuleDir } from './utils/runtime-paths.js';
24
25
  import { buildDaemonCliCommand, handleDaemonCommand, hasInstalledDaemonUnit } from './daemon-manager.js';
25
26
  import { runStartupAutoUpdate, startupUpdateReexecEnv } from './services/startup-update.js';
27
+ import { runSetupWizard, postStartupGuidance, isFirstRun, isPortAvailable, findAvailablePort, waitForServerReady } from './setup-wizard.js';
26
28
 
27
29
  const __dirname = getModuleDir(import.meta.url);
28
30
  // The CLI is compiled into dist-server/server, but it still needs to read the top-level
@@ -169,6 +171,7 @@ Usage:
169
171
 
170
172
  Commands:
171
173
  start Start the Pixcode server (default)
174
+ setup Run the interactive setup wizard (port, mode, browser)
172
175
  daemon Manage persistent Linux service (system-first)
173
176
  sandbox Manage Docker sandbox environments
174
177
  status Show configuration and data locations
@@ -180,14 +183,17 @@ Options:
180
183
  -p, --port <port> Set server port (default: 3001)
181
184
  --database-path <path> Set custom database location
182
185
  --no-daemon Disable automatic daemon startup on Linux
186
+ --no-browser Do not auto-open browser on startup
183
187
  --restart-daemon Restart daemon automatically after update
184
188
  -h, --help Show this help information
185
189
  -v, --version Show version information
186
190
 
187
191
  Examples:
188
192
  $ pixcode # Start with defaults
193
+ $ pixcode setup # Run the interactive setup wizard
189
194
  $ pixcode --port 8080 # Start on port 8080
190
195
  $ pixcode --no-daemon # Force foreground mode
196
+ $ pixcode --no-browser # Start without auto-opening browser
191
197
  $ sudo pixcode daemon install --mode system --port 3001
192
198
  $ pixcode daemon install --mode user --port 3001 --single-port
193
199
  $ pixcode daemon doctor --mode system
@@ -945,6 +951,8 @@ function parseArgs(args) {
945
951
  parsed.options.databasePath = arg.split('=')[1];
946
952
  } else if (arg === '--no-daemon') {
947
953
  parsed.options.noDaemon = true;
954
+ } else if (arg === '--no-browser') {
955
+ parsed.options.noBrowser = true;
948
956
  } else if (arg === '--restart-daemon') {
949
957
  parsed.options.restartDaemon = true;
950
958
  } else if (arg === '--help' || arg === '-h') {
@@ -982,6 +990,9 @@ async function main() {
982
990
  if (options.noDaemon) {
983
991
  process.env.PIXCODE_NO_DAEMON = '1';
984
992
  }
993
+ if (options.noBrowser) {
994
+ process.env.PIXCODE_NO_BROWSER = '1';
995
+ }
985
996
  if (options.databasePath) {
986
997
  process.env.DATABASE_PATH = options.databasePath;
987
998
  }
@@ -991,11 +1002,67 @@ async function main() {
991
1002
  if (process.env.PIXCODE_ENABLE_STARTUP_UPDATE === '1' && await runStartupUpdateGate()) {
992
1003
  break;
993
1004
  }
1005
+ {
1006
+ const effectivePort = Number(options.serverPort || process.env.SERVER_PORT || process.env.PORT || '3001');
1007
+
1008
+ // First-run setup wizard
1009
+ if (isFirstRun()) {
1010
+ const wizardResult = await runSetupWizard(options);
1011
+ if (wizardResult) {
1012
+ if (!options.serverPort) {
1013
+ process.env.SERVER_PORT = String(wizardResult.port);
1014
+ }
1015
+ if (wizardResult.useDaemon === false) {
1016
+ options.noDaemon = true;
1017
+ process.env.PIXCODE_NO_DAEMON = '1';
1018
+ }
1019
+ }
1020
+ }
1021
+
1022
+ // Port conflict detection (every run, not just first)
1023
+ const currentPort = Number(options.serverPort || process.env.SERVER_PORT || process.env.PORT || '3001');
1024
+ if (!await isPortAvailable(currentPort)) {
1025
+ const alreadyRunning = await waitForServerReady(currentPort, 2000);
1026
+ if (alreadyRunning) {
1027
+ console.log(`\n${c.ok('[OK]')} Pixcode is already running on port ${c.bright(String(currentPort))}.`);
1028
+ await postStartupGuidance(currentPort);
1029
+ break;
1030
+ }
1031
+ console.log(`\n${c.warn('[WARN]')} Port ${c.bright(String(currentPort))} is in use by another process.`);
1032
+ const altPort = await findAvailablePort(currentPort + 1);
1033
+ if (altPort) {
1034
+ console.log(`${c.info('[INFO]')} Switching to port ${c.bright(String(altPort))}.`);
1035
+ process.env.SERVER_PORT = String(altPort);
1036
+ options.serverPort = String(altPort);
1037
+ } else {
1038
+ console.error(`${c.error('[ERROR]')} No free ports found. Specify one with ${c.bright('--port <port>')}.`);
1039
+ process.exit(1);
1040
+ }
1041
+ }
1042
+ }
994
1043
  if (await maybeAutoDaemonStart(options)) {
1044
+ const daemonPort = Number(options.serverPort || process.env.SERVER_PORT || process.env.PORT || '3001');
1045
+ const ready = await waitForServerReady(daemonPort, 15000);
1046
+ if (ready) {
1047
+ await postStartupGuidance(daemonPort);
1048
+ }
995
1049
  break;
996
1050
  }
997
1051
  await startServer();
998
1052
  break;
1053
+ case 'setup':
1054
+ {
1055
+ const wizardResult = await runSetupWizard(options);
1056
+ if (wizardResult) {
1057
+ process.env.SERVER_PORT = String(wizardResult.port);
1058
+ if (wizardResult.useDaemon === false) {
1059
+ options.noDaemon = true;
1060
+ process.env.PIXCODE_NO_DAEMON = '1';
1061
+ }
1062
+ }
1063
+ console.log(`\n${c.ok('[OK]')} Setup complete! Run ${c.bright('pixcode start')} to launch.`);
1064
+ }
1065
+ break;
999
1066
  case 'sandbox':
1000
1067
  await sandboxCommand(remainingArgs || []);
1001
1068
  break;
package/server/index.js CHANGED
@@ -1119,8 +1119,8 @@ const SHELL_URL_PARSE_BUFFER_LIMIT = 32768;
1119
1119
  const SHELL_OUTPUT_FLUSH_MAX_CHARS = 128 * 1024;
1120
1120
  const SHELL_WS_BACKPRESSURE_LIMIT = 8 * 1024 * 1024;
1121
1121
  const PTY_SESSION_BUFFER_MAX_BYTES = Number.parseInt(process.env.PIXCODE_PTY_BUFFER_MAX_BYTES || '', 10) || (512 * 1024);
1122
- const SHELL_INPUT_CHUNK_CHARS = 4096;
1123
- const SHELL_PENDING_INPUT_MAX_BYTES = Number.parseInt(process.env.PIXCODE_SHELL_PENDING_INPUT_MAX_BYTES || '', 10) || (16 * 1024 * 1024);
1122
+ const SHELL_INPUT_CHUNK_CHARS = 16384;
1123
+ const SHELL_PENDING_INPUT_MAX_BYTES = Number.parseInt(process.env.PIXCODE_SHELL_PENDING_INPUT_MAX_BYTES || '', 10) || (256 * 1024 * 1024);
1124
1124
  const SHELL_CLI_PROVIDERS = new Set(['claude', 'codex', 'cursor', 'gemini', 'qwen', 'opencode']);
1125
1125
  const FILE_TREE_MAX_ITEMS = Number.parseInt(process.env.PIXCODE_FILE_TREE_MAX_ITEMS || '', 10) || 5000;
1126
1126
  const FILE_TREE_MAX_DIRECTORIES = Number.parseInt(process.env.PIXCODE_FILE_TREE_MAX_DIRECTORIES || '', 10) || 1200;
@@ -3899,24 +3899,38 @@ function handleShellConnection(ws, request) {
3899
3899
  const chunks = splitTerminalInput(data);
3900
3900
  if (chunks.length === 0) return;
3901
3901
 
3902
- let droppedBytes = 0;
3903
3902
  for (const chunk of chunks) {
3904
- const chunkBytes = Buffer.byteLength(chunk, 'utf8');
3903
+ // Filter terminal-generated response sequences that xterm.js sends back
3904
+ // via onData. Without this, DA2/mouse/DSR responses get echoed by the PTY,
3905
+ // xterm.js interprets the echo as a new query, and an infinite loop starts.
3906
+ // This is the server-side guard complementing the frontend sanitizer.
3907
+ const filteredChunk = chunk
3908
+ .replace(/\x1b\[>\d+[;:\d]*c/g, '') // DA2 response (self-looping)
3909
+ .replace(/\x1b\[\?\d+[;:\d]*c/g, '') // DA1 response
3910
+ .replace(/\x1b\[\d+;\d+R/g, '') // DSR cursor
3911
+ .replace(/\x1b\[\?\d+;\d+R/g, '') // DSR private cursor
3912
+ .replace(/\x1b\[\d+n/g, '') // DSR status
3913
+ .replace(/\x1b\[M[\s\S]{3}/g, '') // Mouse report (default)
3914
+ .replace(/\x1b\[<\d+;\d+;\d+[Mm]/g, '') // Mouse report (SGR)
3915
+ .replace(/\x1b\[IO]/g, '') // Focus events
3916
+ .replace(/\x1b\[\d+;\d+;\d+t/g, '') // Window size report
3917
+ .replace(/\x1b\[\d+;\d+\$y/g, ''); // DECRQM response
3918
+ if (!filteredChunk) continue;
3919
+
3920
+ const chunkBytes = Buffer.byteLength(filteredChunk, 'utf8');
3905
3921
  if (pendingInputBytes + chunkBytes > SHELL_PENDING_INPUT_MAX_BYTES) {
3906
- droppedBytes += chunkBytes;
3907
- continue;
3922
+ // Drop only the oldest queued chunk to make room, not the new one.
3923
+ // This prevents the queue from growing unbounded without silently
3924
+ // discarding the user's latest paste.
3925
+ while (pendingInputQueue.length > 0 && pendingInputBytes + chunkBytes > SHELL_PENDING_INPUT_MAX_BYTES) {
3926
+ const dropped = pendingInputQueue.shift();
3927
+ pendingInputBytes = Math.max(0, pendingInputBytes - Buffer.byteLength(dropped, 'utf8'));
3928
+ }
3908
3929
  }
3909
- pendingInputQueue.push(chunk);
3930
+ pendingInputQueue.push(filteredChunk);
3910
3931
  pendingInputBytes += chunkBytes;
3911
3932
  }
3912
3933
 
3913
- if (droppedBytes > 0 && ws.readyState === WebSocket.OPEN) {
3914
- ws.send(JSON.stringify({
3915
- type: 'output',
3916
- data: `\r\n\x1b[33m[Pixcode] ${droppedBytes} bytes of pasted terminal input were not sent because the input queue limit was reached. Split very large pastes into smaller chunks.\x1b[0m\r\n`,
3917
- }));
3918
- }
3919
-
3920
3934
  scheduleInputFlush();
3921
3935
  }
3922
3936
 
@@ -5293,6 +5307,21 @@ async function startServer() {
5293
5307
  console.warn('[external-access] tunnel restore failed:', err?.message || err);
5294
5308
  });
5295
5309
 
5310
+ // Auto-open browser unless explicitly disabled
5311
+ if (process.env.PIXCODE_NO_BROWSER !== '1') {
5312
+ const openUrl = `http://${DISPLAY_HOST}:${SERVER_PORT}`;
5313
+ try {
5314
+ const { exec } = await import('node:child_process');
5315
+ const opener = process.platform === 'darwin' ? `open "${openUrl}"`
5316
+ : process.platform === 'win32' ? `start "" "${openUrl}"`
5317
+ : `xdg-open "${openUrl}"`;
5318
+ exec(opener, { stdio: 'ignore', timeout: 3000 });
5319
+ console.log(`${c.ok('[OK]')} Opening browser at ${c.bright(openUrl)}`);
5320
+ } catch {
5321
+ console.log(`${c.tip('[TIP]')} Open ${c.bright(openUrl)} in your browser to start using Pixcode.`);
5322
+ }
5323
+ }
5324
+
5296
5325
  console.log(`${c.tip('[TIP]')} Run "pixcode status" for full configuration details`);
5297
5326
  console.log('');
5298
5327