@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.
- package/dist/assets/index-B6Ssy_IG.css +32 -0
- package/dist/assets/{index-BBDYzJ7B.js → index-CEw93JQ7.js} +195 -195
- package/dist/index.html +2 -2
- package/dist-server/server/cli.js +67 -0
- package/dist-server/server/cli.js.map +1 -1
- package/dist-server/server/index.js +43 -13
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/setup-wizard.js +240 -0
- package/dist-server/server/setup-wizard.js.map +1 -0
- package/package.json +1 -1
- package/server/cli.js +67 -0
- package/server/index.js +43 -14
- package/server/setup-wizard.js +257 -0
- package/dist/assets/index-DvudRU5X.css +0 -32
|
@@ -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.
|
|
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 =
|
|
1123
|
-
const SHELL_PENDING_INPUT_MAX_BYTES = Number.parseInt(process.env.PIXCODE_SHELL_PENDING_INPUT_MAX_BYTES || '', 10) || (
|
|
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
|
-
|
|
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
|
-
|
|
3907
|
-
|
|
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(
|
|
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
|
|