@pixelbyte-software/pixcode 1.30.2 → 1.31.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/LICENSE +718 -718
- package/README.de.md +248 -248
- package/README.ja.md +240 -240
- package/README.ko.md +240 -240
- package/README.md +295 -285
- package/README.ru.md +248 -248
- package/README.tr.md +250 -250
- package/README.zh-CN.md +240 -240
- package/dist/api-docs.html +879 -879
- package/dist/assets/index-BRRJ47XQ.css +32 -0
- package/dist/assets/index-EQohwyiC.js +837 -0
- package/dist/clear-cache.html +85 -85
- package/dist/convert-icons.md +52 -52
- package/dist/favicon.png +0 -0
- package/dist/favicon.svg +7 -8
- package/dist/generate-icons.js +48 -48
- package/dist/icons/codex-white.svg +3 -3
- package/dist/icons/codex.svg +3 -3
- package/dist/icons/cursor-white.svg +11 -11
- package/dist/icons/icon-128x128.png +0 -0
- package/dist/icons/icon-128x128.svg +9 -12
- package/dist/icons/icon-144x144.png +0 -0
- package/dist/icons/icon-144x144.svg +9 -12
- package/dist/icons/icon-152x152.png +0 -0
- package/dist/icons/icon-152x152.svg +9 -12
- package/dist/icons/icon-192x192.png +0 -0
- package/dist/icons/icon-192x192.svg +9 -12
- package/dist/icons/icon-384x384.png +0 -0
- package/dist/icons/icon-384x384.svg +9 -12
- package/dist/icons/icon-512x512.png +0 -0
- package/dist/icons/icon-512x512.svg +9 -12
- package/dist/icons/icon-72x72.png +0 -0
- package/dist/icons/icon-72x72.svg +9 -12
- package/dist/icons/icon-96x96.png +0 -0
- package/dist/icons/icon-96x96.svg +9 -12
- package/dist/icons/icon-template.svg +9 -12
- package/dist/icons/qwen-ai-icon.png +0 -0
- package/dist/index.html +59 -49
- package/dist/logo.png +0 -0
- package/dist/logo.svg +11 -16
- package/dist/manifest.json +60 -60
- package/dist/sw.js +124 -124
- package/dist-server/server/cli.js +100 -97
- package/dist-server/server/cli.js.map +1 -1
- package/dist-server/server/daemon/manager.js +33 -33
- package/dist-server/server/daemon-manager.js +62 -62
- package/dist-server/server/database/db.js +114 -22
- package/dist-server/server/database/db.js.map +1 -1
- package/dist-server/server/database/schema.js +122 -89
- package/dist-server/server/database/schema.js.map +1 -1
- package/dist-server/server/gemini-cli.js +6 -1
- package/dist-server/server/gemini-cli.js.map +1 -1
- package/dist-server/server/index.js +234 -64
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +29 -2
- package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-auth.provider.js +22 -2
- package/dist-server/server/modules/providers/list/codex/codex-auth.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js +2 -2
- package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js +14 -2
- package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/qwen/qwen-auth.provider.js +132 -0
- package/dist-server/server/modules/providers/list/qwen/qwen-auth.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/qwen/qwen-mcp.provider.js +87 -0
- package/dist-server/server/modules/providers/list/qwen/qwen-mcp.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/qwen/qwen-sessions.provider.js +201 -0
- package/dist-server/server/modules/providers/list/qwen/qwen-sessions.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/qwen/qwen.provider.js +19 -0
- package/dist-server/server/modules/providers/list/qwen/qwen.provider.js.map +1 -0
- package/dist-server/server/modules/providers/provider.registry.js +2 -0
- package/dist-server/server/modules/providers/provider.registry.js.map +1 -1
- package/dist-server/server/modules/providers/provider.routes.js +310 -1
- package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
- package/dist-server/server/projects.js +197 -6
- package/dist-server/server/projects.js.map +1 -1
- package/dist-server/server/qwen-code-cli.js +350 -0
- package/dist-server/server/qwen-code-cli.js.map +1 -0
- package/dist-server/server/qwen-response-handler.js +70 -0
- package/dist-server/server/qwen-response-handler.js.map +1 -0
- package/dist-server/server/routes/commands.js +25 -25
- package/dist-server/server/routes/git.js +17 -17
- package/dist-server/server/routes/network.js +116 -0
- package/dist-server/server/routes/network.js.map +1 -0
- package/dist-server/server/routes/projects.js +43 -0
- package/dist-server/server/routes/projects.js.map +1 -1
- package/dist-server/server/routes/qwen.js +23 -0
- package/dist-server/server/routes/qwen.js.map +1 -0
- package/dist-server/server/routes/taskmaster.js +419 -419
- package/dist-server/server/routes/telegram.js +119 -0
- package/dist-server/server/routes/telegram.js.map +1 -0
- package/dist-server/server/services/external-access.js +228 -0
- package/dist-server/server/services/external-access.js.map +1 -0
- package/dist-server/server/services/install-jobs.js +394 -0
- package/dist-server/server/services/install-jobs.js.map +1 -0
- package/dist-server/server/services/notification-orchestrator.js +19 -5
- package/dist-server/server/services/notification-orchestrator.js.map +1 -1
- package/dist-server/server/services/provider-credentials.js +154 -0
- package/dist-server/server/services/provider-credentials.js.map +1 -0
- package/dist-server/server/services/provider-models.js +218 -0
- package/dist-server/server/services/provider-models.js.map +1 -0
- package/dist-server/server/services/telegram/bot.js +259 -0
- package/dist-server/server/services/telegram/bot.js.map +1 -0
- package/dist-server/server/services/telegram/translations.js +160 -0
- package/dist-server/server/services/telegram/translations.js.map +1 -0
- package/dist-server/server/utils/port-access.js +196 -0
- package/dist-server/server/utils/port-access.js.map +1 -0
- package/dist-server/shared/modelConstants.js +18 -0
- package/dist-server/shared/modelConstants.js.map +1 -1
- package/package.json +177 -168
- package/scripts/fix-node-pty.js +67 -67
- package/server/claude-sdk.js +834 -834
- package/server/cli.js +940 -937
- package/server/constants/config.js +4 -4
- package/server/cursor-cli.js +342 -342
- package/server/daemon/manager.js +564 -564
- package/server/daemon-manager.js +920 -920
- package/server/database/db.js +696 -593
- package/server/database/schema.js +138 -102
- package/server/gemini-cli.js +475 -469
- package/server/gemini-response-handler.js +79 -79
- package/server/index.js +2730 -2556
- package/server/load-env.js +34 -34
- package/server/middleware/auth.js +132 -132
- package/server/modules/providers/list/claude/claude-auth.provider.ts +145 -123
- package/server/modules/providers/list/claude/claude-mcp.provider.ts +135 -135
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +306 -306
- package/server/modules/providers/list/claude/claude.provider.ts +15 -15
- package/server/modules/providers/list/codex/codex-auth.provider.ts +115 -100
- package/server/modules/providers/list/codex/codex-mcp.provider.ts +135 -135
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +319 -319
- package/server/modules/providers/list/codex/codex.provider.ts +15 -15
- package/server/modules/providers/list/cursor/cursor-auth.provider.ts +143 -143
- package/server/modules/providers/list/cursor/cursor-mcp.provider.ts +108 -108
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +421 -421
- package/server/modules/providers/list/cursor/cursor.provider.ts +15 -15
- package/server/modules/providers/list/gemini/gemini-auth.provider.ts +163 -151
- package/server/modules/providers/list/gemini/gemini-mcp.provider.ts +110 -110
- package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +227 -227
- package/server/modules/providers/list/gemini/gemini.provider.ts +15 -15
- package/server/modules/providers/list/qwen/qwen-auth.provider.ts +145 -0
- package/server/modules/providers/list/qwen/qwen-mcp.provider.ts +114 -0
- package/server/modules/providers/list/qwen/qwen-sessions.provider.ts +218 -0
- package/server/modules/providers/list/qwen/qwen.provider.ts +21 -0
- package/server/modules/providers/provider.registry.ts +38 -36
- package/server/modules/providers/provider.routes.ts +583 -217
- package/server/modules/providers/services/mcp.service.ts +94 -94
- package/server/modules/providers/services/provider-auth.service.ts +26 -26
- package/server/modules/providers/services/sessions.service.ts +45 -45
- package/server/modules/providers/shared/base/abstract.provider.ts +20 -20
- package/server/modules/providers/shared/mcp/mcp.provider.ts +151 -151
- package/server/modules/providers/tests/mcp.test.ts +293 -293
- package/server/openai-codex.js +426 -426
- package/server/projects.js +2993 -2792
- package/server/qwen-code-cli.js +392 -0
- package/server/qwen-response-handler.js +73 -0
- package/server/routes/agent.js +1245 -1245
- package/server/routes/auth.js +134 -134
- package/server/routes/codex.js +19 -19
- package/server/routes/commands.js +554 -554
- package/server/routes/cursor.js +52 -52
- package/server/routes/gemini.js +24 -24
- package/server/routes/git.js +1488 -1488
- package/server/routes/mcp-utils.js +31 -31
- package/server/routes/messages.js +61 -61
- package/server/routes/network.js +128 -0
- package/server/routes/plugins.js +307 -307
- package/server/routes/projects.js +675 -627
- package/server/routes/qwen.js +27 -0
- package/server/routes/settings.js +286 -286
- package/server/routes/taskmaster.js +1471 -1471
- package/server/routes/telegram.js +125 -0
- package/server/routes/user.js +123 -123
- package/server/services/external-access.js +240 -0
- package/server/services/install-jobs.js +410 -0
- package/server/services/notification-orchestrator.js +242 -227
- package/server/services/provider-credentials.js +151 -0
- package/server/services/provider-models.js +225 -0
- package/server/services/telegram/bot.js +280 -0
- package/server/services/telegram/translations.js +170 -0
- package/server/services/vapid-keys.js +35 -35
- package/server/sessionManager.js +225 -225
- package/server/shared/interfaces.ts +54 -54
- package/server/shared/types.ts +172 -172
- package/server/shared/utils.ts +193 -193
- package/server/tsconfig.json +36 -36
- package/server/utils/colors.js +21 -21
- package/server/utils/commandParser.js +303 -303
- package/server/utils/frontmatter.js +18 -18
- package/server/utils/gitConfig.js +34 -34
- package/server/utils/mcp-detector.js +147 -147
- package/server/utils/plugin-loader.js +457 -457
- package/server/utils/plugin-process-manager.js +184 -184
- package/server/utils/port-access.js +209 -0
- package/server/utils/runtime-paths.js +37 -37
- package/server/utils/taskmaster-websocket.js +128 -128
- package/server/utils/url-detection.js +71 -71
- package/server/vite-daemon.js +78 -78
- package/shared/modelConstants.js +117 -97
- package/shared/networkHosts.js +22 -22
- package/dist/assets/index-C2c9QNwK.css +0 -32
- package/dist/assets/index-DyXDZED-.js +0 -1277
|
@@ -10,6 +10,15 @@ const __dirname = getModuleDir(import.meta.url);
|
|
|
10
10
|
// Resolving the app root once keeps every repo-level lookup below aligned across both layouts.
|
|
11
11
|
const APP_ROOT = findAppRoot(__dirname);
|
|
12
12
|
const installMode = fs.existsSync(path.join(APP_ROOT, '.git')) ? 'git' : 'npm';
|
|
13
|
+
const SERVER_VERSION = (() => {
|
|
14
|
+
try {
|
|
15
|
+
const pkgRaw = fs.readFileSync(path.join(APP_ROOT, 'package.json'), 'utf8');
|
|
16
|
+
return JSON.parse(pkgRaw).version || '0.0.0';
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return '0.0.0';
|
|
20
|
+
}
|
|
21
|
+
})();
|
|
13
22
|
const DAEMON_COMMAND_CONTEXT = {
|
|
14
23
|
appRoot: APP_ROOT,
|
|
15
24
|
cliEntry: path.join(APP_ROOT, 'server', 'cli.js'),
|
|
@@ -32,6 +41,7 @@ import { queryClaudeSDK, abortClaudeSDKSession, isClaudeSDKSessionActive, getAct
|
|
|
32
41
|
import { spawnCursor, abortCursorSession, isCursorSessionActive, getActiveCursorSessions } from './cursor-cli.js';
|
|
33
42
|
import { queryCodex, abortCodexSession, isCodexSessionActive, getActiveCodexSessions } from './openai-codex.js';
|
|
34
43
|
import { spawnGemini, abortGeminiSession, isGeminiSessionActive, getActiveGeminiSessions } from './gemini-cli.js';
|
|
44
|
+
import { spawnQwen, abortQwenSession, isQwenSessionActive, getActiveQwenSessions } from './qwen-code-cli.js';
|
|
35
45
|
import sessionManager from './sessionManager.js';
|
|
36
46
|
import gitRoutes from './routes/git.js';
|
|
37
47
|
import authRoutes from './routes/auth.js';
|
|
@@ -45,9 +55,16 @@ import projectsRoutes, { WORKSPACES_ROOT, WORKSPACES_BASE, validateWorkspacePath
|
|
|
45
55
|
import userRoutes from './routes/user.js';
|
|
46
56
|
import codexRoutes from './routes/codex.js';
|
|
47
57
|
import geminiRoutes from './routes/gemini.js';
|
|
58
|
+
import qwenRoutes from './routes/qwen.js';
|
|
48
59
|
import pluginsRoutes from './routes/plugins.js';
|
|
49
60
|
import messagesRoutes from './routes/messages.js';
|
|
50
61
|
import providerRoutes from './modules/providers/provider.routes.js';
|
|
62
|
+
import networkRoutes from './routes/network.js';
|
|
63
|
+
import telegramRoutes from './routes/telegram.js';
|
|
64
|
+
import { restoreBotFromConfig } from './services/telegram/bot.js';
|
|
65
|
+
import { ensurePortOpen } from './utils/port-access.js';
|
|
66
|
+
import { applyAllStoredCredentialsToEnv, applyProviderCredentialsToEnv, listProviderCredentialSummaries, PROVIDER_ENV_VARS, setProviderCredentials, } from './services/provider-credentials.js';
|
|
67
|
+
import { primeCliBinPath } from './services/install-jobs.js';
|
|
51
68
|
import { startEnabledPluginServers, stopAllPlugins, getPluginPort } from './utils/plugin-process-manager.js';
|
|
52
69
|
import { initializeDatabase, sessionNamesDb, applyCustomSessionNames } from './database/db.js';
|
|
53
70
|
import { configureWebPush } from './services/vapid-keys.js';
|
|
@@ -55,7 +72,7 @@ import { validateApiKey, authenticateToken, authenticateWebSocket } from './midd
|
|
|
55
72
|
import { IS_PLATFORM } from './constants/config.js';
|
|
56
73
|
import { getConnectableHost } from '../shared/networkHosts.js';
|
|
57
74
|
import { buildDaemonCliCommand, handleDaemonCommand } from './daemon-manager.js';
|
|
58
|
-
const VALID_PROVIDERS = ['claude', 'codex', 'cursor', 'gemini'];
|
|
75
|
+
const VALID_PROVIDERS = ['claude', 'codex', 'cursor', 'gemini', 'qwen'];
|
|
59
76
|
// File system watchers for provider project/session folders
|
|
60
77
|
const PROVIDER_WATCH_PATHS = [
|
|
61
78
|
{ provider: 'claude', rootPath: path.join(os.homedir(), '.claude', 'projects') },
|
|
@@ -64,6 +81,10 @@ const PROVIDER_WATCH_PATHS = [
|
|
|
64
81
|
{ provider: 'gemini', rootPath: path.join(os.homedir(), '.gemini', 'projects') },
|
|
65
82
|
{ provider: 'gemini_sessions', rootPath: path.join(os.homedir(), '.gemini', 'sessions') },
|
|
66
83
|
{ provider: 'gemini_cli', rootPath: path.join(os.homedir(), '.gemini', 'tmp') },
|
|
84
|
+
// Qwen Code is a Gemini-CLI fork so its on-disk layout mirrors ~/.gemini/.
|
|
85
|
+
{ provider: 'qwen', rootPath: path.join(os.homedir(), '.qwen', 'projects') },
|
|
86
|
+
{ provider: 'qwen_sessions', rootPath: path.join(os.homedir(), '.qwen', 'sessions') },
|
|
87
|
+
{ provider: 'qwen_cli', rootPath: path.join(os.homedir(), '.qwen', 'tmp') },
|
|
67
88
|
];
|
|
68
89
|
const WATCHER_IGNORED_PATTERNS = [
|
|
69
90
|
'**/node_modules/**',
|
|
@@ -243,7 +264,8 @@ app.get('/health', (req, res) => {
|
|
|
243
264
|
res.json({
|
|
244
265
|
status: 'ok',
|
|
245
266
|
timestamp: new Date().toISOString(),
|
|
246
|
-
installMode
|
|
267
|
+
installMode,
|
|
268
|
+
version: SERVER_VERSION
|
|
247
269
|
});
|
|
248
270
|
});
|
|
249
271
|
// Optional API key validation (if configured)
|
|
@@ -270,12 +292,18 @@ app.use('/api/user', authenticateToken, userRoutes);
|
|
|
270
292
|
app.use('/api/codex', authenticateToken, codexRoutes);
|
|
271
293
|
// Gemini API Routes (protected)
|
|
272
294
|
app.use('/api/gemini', authenticateToken, geminiRoutes);
|
|
295
|
+
// Qwen Code API Routes (protected)
|
|
296
|
+
app.use('/api/qwen', authenticateToken, qwenRoutes);
|
|
273
297
|
// Plugins API Routes (protected)
|
|
274
298
|
app.use('/api/plugins', authenticateToken, pluginsRoutes);
|
|
275
299
|
// Unified session messages route (protected)
|
|
276
300
|
app.use('/api/sessions', authenticateToken, messagesRoutes);
|
|
277
301
|
// Unified provider MCP routes (protected)
|
|
278
302
|
app.use('/api/providers', authenticateToken, providerRoutes);
|
|
303
|
+
// Network discovery / QR endpoints (protected)
|
|
304
|
+
app.use('/api/network', authenticateToken, networkRoutes);
|
|
305
|
+
// Telegram integration (protected)
|
|
306
|
+
app.use('/api/telegram', authenticateToken, telegramRoutes);
|
|
279
307
|
// Agent API Routes (uses API key authentication)
|
|
280
308
|
app.use('/api/agent', agentRoutes);
|
|
281
309
|
// Serve public files (like api-docs.html)
|
|
@@ -299,70 +327,122 @@ app.use(express.static(path.join(APP_ROOT, 'dist'), {
|
|
|
299
327
|
// API Routes (protected)
|
|
300
328
|
// /api/config endpoint removed - no longer needed
|
|
301
329
|
// Frontend now uses window.location for WebSocket URLs
|
|
302
|
-
// System update endpoint
|
|
330
|
+
// System update endpoint — streams live output via Server-Sent Events so the
|
|
331
|
+
// UI sees npm/git progress in real time instead of waiting ~2 minutes for the
|
|
332
|
+
// buffered response.
|
|
303
333
|
app.post('/api/system/update', authenticateToken, async (req, res) => {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
334
|
+
const projectRoot = APP_ROOT;
|
|
335
|
+
console.log('Starting system update from directory:', projectRoot);
|
|
336
|
+
const updateCommand = IS_PLATFORM
|
|
337
|
+
? 'npm run update:platform'
|
|
338
|
+
: installMode === 'git'
|
|
339
|
+
? 'git checkout main && git pull && npm install'
|
|
340
|
+
: 'npm install -g @pixelbyte-software/pixcode@latest';
|
|
341
|
+
const updateCwd = IS_PLATFORM || installMode === 'git'
|
|
342
|
+
? projectRoot
|
|
343
|
+
: os.homedir();
|
|
344
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
345
|
+
res.setHeader('Cache-Control', 'no-cache, no-transform');
|
|
346
|
+
res.setHeader('Connection', 'keep-alive');
|
|
347
|
+
res.setHeader('X-Accel-Buffering', 'no');
|
|
348
|
+
if (typeof res.flushHeaders === 'function') {
|
|
349
|
+
res.flushHeaders();
|
|
350
|
+
}
|
|
351
|
+
// Single-source end guard. When spawn() fails with ENOENT both the
|
|
352
|
+
// 'error' and 'close' handlers can fire on the child, and before this
|
|
353
|
+
// guard both would try to write a `done` event + call res.end(), which
|
|
354
|
+
// crashed the process with ERR_HTTP_HEADERS_SENT.
|
|
355
|
+
let ended = false;
|
|
356
|
+
const endStream = () => {
|
|
357
|
+
if (ended)
|
|
358
|
+
return;
|
|
359
|
+
ended = true;
|
|
360
|
+
clearInterval(heartbeat);
|
|
361
|
+
res.end();
|
|
362
|
+
};
|
|
363
|
+
const send = (event, payload) => {
|
|
364
|
+
if (ended)
|
|
365
|
+
return;
|
|
366
|
+
res.write(`event: ${event}\n`);
|
|
367
|
+
res.write(`data: ${JSON.stringify(payload)}\n\n`);
|
|
368
|
+
};
|
|
369
|
+
// Heartbeat keeps intermediate proxies from closing an idle connection
|
|
370
|
+
// during long npm installs. Declared before spawn so the handlers below
|
|
371
|
+
// can reference it, and guarded against writes after end.
|
|
372
|
+
const heartbeat = setInterval(() => {
|
|
373
|
+
if (ended)
|
|
374
|
+
return;
|
|
375
|
+
res.write(': ping\n\n');
|
|
376
|
+
}, 15000);
|
|
377
|
+
send('log', { stream: 'meta', chunk: `Running: ${updateCommand}\n` });
|
|
378
|
+
// Cross-platform shell invocation. Using { shell: true } delegates to
|
|
379
|
+
// cmd.exe on Windows and /bin/sh on POSIX, so the endpoint no longer
|
|
380
|
+
// fails with `spawn sh ENOENT` on Windows installs.
|
|
381
|
+
const child = spawn(updateCommand, {
|
|
382
|
+
cwd: updateCwd,
|
|
383
|
+
env: process.env,
|
|
384
|
+
shell: true,
|
|
385
|
+
});
|
|
386
|
+
let clientAborted = false;
|
|
387
|
+
req.on('close', () => {
|
|
388
|
+
if (!res.writableEnded) {
|
|
389
|
+
clientAborted = true;
|
|
390
|
+
try {
|
|
391
|
+
child.kill();
|
|
349
392
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
393
|
+
catch { /* noop */ }
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
child.stdout?.on('data', (data) => {
|
|
397
|
+
send('log', { stream: 'stdout', chunk: data.toString() });
|
|
398
|
+
});
|
|
399
|
+
child.stderr?.on('data', (data) => {
|
|
400
|
+
send('log', { stream: 'stderr', chunk: data.toString() });
|
|
401
|
+
});
|
|
402
|
+
child.on('error', (error) => {
|
|
403
|
+
if (ended)
|
|
404
|
+
return;
|
|
405
|
+
console.error('Update process error:', error);
|
|
406
|
+
send('done', { success: false, error: error.message });
|
|
407
|
+
endStream();
|
|
408
|
+
});
|
|
409
|
+
child.on('close', (code) => {
|
|
410
|
+
if (ended)
|
|
411
|
+
return;
|
|
412
|
+
if (clientAborted) {
|
|
413
|
+
endStream();
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (code === 0) {
|
|
417
|
+
send('done', {
|
|
418
|
+
success: true,
|
|
419
|
+
version: SERVER_VERSION,
|
|
420
|
+
message: 'Update completed. Restart the server to apply changes.',
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
send('done', {
|
|
354
425
|
success: false,
|
|
355
|
-
error:
|
|
426
|
+
error: `Update command exited with code ${code}`,
|
|
356
427
|
});
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
428
|
+
}
|
|
429
|
+
endStream();
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
// Restart endpoint — exits the current process so an external wrapper
|
|
433
|
+
// (systemd/pm2/daemon manager) can bring the server back on the new code.
|
|
434
|
+
// Foreground installs without a wrapper will simply stop; the UI reports this.
|
|
435
|
+
app.post('/api/system/restart', authenticateToken, (req, res) => {
|
|
436
|
+
res.json({
|
|
437
|
+
success: true,
|
|
438
|
+
version: SERVER_VERSION,
|
|
439
|
+
message: 'Server is shutting down for restart. Reconnecting...'
|
|
440
|
+
});
|
|
441
|
+
// Give the response time to flush before we exit.
|
|
442
|
+
setTimeout(() => {
|
|
443
|
+
console.log('Restart requested via /api/system/restart — exiting process.');
|
|
444
|
+
process.exit(0);
|
|
445
|
+
}, 250);
|
|
366
446
|
});
|
|
367
447
|
app.get('/api/projects', authenticateToken, async (req, res) => {
|
|
368
448
|
try {
|
|
@@ -1317,6 +1397,13 @@ function handleChatConnection(ws, request) {
|
|
|
1317
1397
|
console.log('🤖 Model:', data.options?.model || 'default');
|
|
1318
1398
|
await spawnGemini(data.command, data.options, writer);
|
|
1319
1399
|
}
|
|
1400
|
+
else if (data.type === 'qwen-command') {
|
|
1401
|
+
console.log('[DEBUG] Qwen Code message:', data.command || '[Continue/Resume]');
|
|
1402
|
+
console.log('📁 Project:', data.options?.projectPath || data.options?.cwd || 'Unknown');
|
|
1403
|
+
console.log('🔄 Session:', data.options?.sessionId ? 'Resume' : 'New');
|
|
1404
|
+
console.log('🤖 Model:', data.options?.model || 'default');
|
|
1405
|
+
await spawnQwen(data.command, data.options, writer);
|
|
1406
|
+
}
|
|
1320
1407
|
else if (data.type === 'cursor-resume') {
|
|
1321
1408
|
// Backward compatibility: treat as cursor-command with resume and no prompt
|
|
1322
1409
|
console.log('[DEBUG] Cursor resume session (compat):', data.sessionId);
|
|
@@ -1339,6 +1426,9 @@ function handleChatConnection(ws, request) {
|
|
|
1339
1426
|
else if (provider === 'gemini') {
|
|
1340
1427
|
success = abortGeminiSession(data.sessionId);
|
|
1341
1428
|
}
|
|
1429
|
+
else if (provider === 'qwen') {
|
|
1430
|
+
success = abortQwenSession(data.sessionId);
|
|
1431
|
+
}
|
|
1342
1432
|
else {
|
|
1343
1433
|
// Use Claude Agents SDK
|
|
1344
1434
|
success = await abortClaudeSDKSession(data.sessionId);
|
|
@@ -1377,6 +1467,9 @@ function handleChatConnection(ws, request) {
|
|
|
1377
1467
|
else if (provider === 'gemini') {
|
|
1378
1468
|
isActive = isGeminiSessionActive(sessionId);
|
|
1379
1469
|
}
|
|
1470
|
+
else if (provider === 'qwen') {
|
|
1471
|
+
isActive = isQwenSessionActive(sessionId);
|
|
1472
|
+
}
|
|
1380
1473
|
else {
|
|
1381
1474
|
// Use Claude Agents SDK
|
|
1382
1475
|
isActive = isClaudeSDKSessionActive(sessionId);
|
|
@@ -1411,7 +1504,8 @@ function handleChatConnection(ws, request) {
|
|
|
1411
1504
|
claude: getActiveClaudeSDKSessions(),
|
|
1412
1505
|
cursor: getActiveCursorSessions(),
|
|
1413
1506
|
codex: getActiveCodexSessions(),
|
|
1414
|
-
gemini: getActiveGeminiSessions()
|
|
1507
|
+
gemini: getActiveGeminiSessions(),
|
|
1508
|
+
qwen: getActiveQwenSessions()
|
|
1415
1509
|
};
|
|
1416
1510
|
writer.send({
|
|
1417
1511
|
type: 'active-sessions',
|
|
@@ -1507,7 +1601,11 @@ function handleShellConnection(ws) {
|
|
|
1507
1601
|
welcomeMsg = `\x1b[36mStarting terminal in: ${projectPath}\x1b[0m\r\n`;
|
|
1508
1602
|
}
|
|
1509
1603
|
else {
|
|
1510
|
-
const providerName = provider === 'cursor' ? 'Cursor'
|
|
1604
|
+
const providerName = provider === 'cursor' ? 'Cursor'
|
|
1605
|
+
: provider === 'codex' ? 'Codex'
|
|
1606
|
+
: provider === 'gemini' ? 'Gemini'
|
|
1607
|
+
: provider === 'qwen' ? 'Qwen Code'
|
|
1608
|
+
: 'Claude';
|
|
1511
1609
|
welcomeMsg = hasSession ?
|
|
1512
1610
|
`\x1b[36mResuming ${providerName} session ${sessionId} in: ${projectPath}\x1b[0m\r\n` :
|
|
1513
1611
|
`\x1b[36mStarting new ${providerName} session in: ${projectPath}\x1b[0m\r\n`;
|
|
@@ -1592,6 +1690,33 @@ function handleShellConnection(ws) {
|
|
|
1592
1690
|
shellCommand = command;
|
|
1593
1691
|
}
|
|
1594
1692
|
}
|
|
1693
|
+
else if (provider === 'qwen') {
|
|
1694
|
+
// Qwen Code shares Gemini CLI's --resume semantics (it's a fork),
|
|
1695
|
+
// so the resume path resolves the backend-tracked cliSessionId the
|
|
1696
|
+
// same way. Falls back to a fresh session when the ID can't be found.
|
|
1697
|
+
const command = initialCommand || 'qwen';
|
|
1698
|
+
let resumeId = sessionId;
|
|
1699
|
+
if (hasSession && sessionId) {
|
|
1700
|
+
try {
|
|
1701
|
+
const sess = sessionManager.getSession(sessionId);
|
|
1702
|
+
if (sess && sess.cliSessionId) {
|
|
1703
|
+
resumeId = sess.cliSessionId;
|
|
1704
|
+
if (!safeSessionIdPattern.test(resumeId)) {
|
|
1705
|
+
resumeId = null;
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
catch (err) {
|
|
1710
|
+
console.error('Failed to get Qwen Code CLI session ID:', err);
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
if (hasSession && resumeId) {
|
|
1714
|
+
shellCommand = `${command} --resume "${resumeId}"`;
|
|
1715
|
+
}
|
|
1716
|
+
else {
|
|
1717
|
+
shellCommand = command;
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1595
1720
|
else {
|
|
1596
1721
|
// Claude (default provider)
|
|
1597
1722
|
const command = initialCommand || 'claude';
|
|
@@ -1869,6 +1994,17 @@ app.get('/api/projects/:projectName/sessions/:sessionId/token-usage', authentica
|
|
|
1869
1994
|
message: 'Token usage tracking not available for Gemini sessions'
|
|
1870
1995
|
});
|
|
1871
1996
|
}
|
|
1997
|
+
// Qwen Code is a Gemini CLI fork and doesn't expose per-session token
|
|
1998
|
+
// accounting either; treat it the same way.
|
|
1999
|
+
if (provider === 'qwen') {
|
|
2000
|
+
return res.json({
|
|
2001
|
+
used: 0,
|
|
2002
|
+
total: 0,
|
|
2003
|
+
breakdown: { input: 0, cacheCreation: 0, cacheRead: 0 },
|
|
2004
|
+
unsupported: true,
|
|
2005
|
+
message: 'Token usage tracking not available for Qwen Code sessions'
|
|
2006
|
+
});
|
|
2007
|
+
}
|
|
1872
2008
|
// Handle Codex sessions
|
|
1873
2009
|
if (provider === 'codex') {
|
|
1874
2010
|
const codexSessionsDir = path.join(homeDir, '.codex', 'sessions');
|
|
@@ -2279,6 +2415,31 @@ async function startServer() {
|
|
|
2279
2415
|
await initializeDatabase();
|
|
2280
2416
|
// Configure Web Push (VAPID keys)
|
|
2281
2417
|
configureWebPush();
|
|
2418
|
+
// Load any provider API keys saved through the UI into process.env so
|
|
2419
|
+
// the Claude/Codex SDKs pick them up automatically. Spawn-based
|
|
2420
|
+
// adapters (Gemini, Qwen) layer their own env on top via buildSpawnEnv.
|
|
2421
|
+
try {
|
|
2422
|
+
await applyAllStoredCredentialsToEnv();
|
|
2423
|
+
}
|
|
2424
|
+
catch (err) {
|
|
2425
|
+
console.warn('[provider-credentials] Failed to apply stored credentials:', err?.message || err);
|
|
2426
|
+
}
|
|
2427
|
+
// Prepend the pixcode-managed CLI sandbox
|
|
2428
|
+
// (~/.pixcode/cli-bin/node_modules/.bin) to PATH so any provider CLI
|
|
2429
|
+
// installed via the in-app installer is instantly resolvable by
|
|
2430
|
+
// cross-spawn calls in the provider adapters — no server restart
|
|
2431
|
+
// required, no need to touch the user's shell PATH.
|
|
2432
|
+
try {
|
|
2433
|
+
primeCliBinPath();
|
|
2434
|
+
}
|
|
2435
|
+
catch (err) {
|
|
2436
|
+
console.warn('[install-jobs] Failed to prime CLI bin path:', err?.message || err);
|
|
2437
|
+
}
|
|
2438
|
+
// Restore any previously-configured Telegram bot. This is best-effort:
|
|
2439
|
+
// a bad token or network blip should warn, not crash the server.
|
|
2440
|
+
restoreBotFromConfig().catch((err) => {
|
|
2441
|
+
console.warn('[telegram] restore failed:', err?.message || err);
|
|
2442
|
+
});
|
|
2282
2443
|
// Check if running in production mode (dist folder exists)
|
|
2283
2444
|
const distIndexPath = path.join(APP_ROOT, 'dist', 'index.html');
|
|
2284
2445
|
const isProduction = fs.existsSync(distIndexPath);
|
|
@@ -2298,6 +2459,15 @@ async function startServer() {
|
|
|
2298
2459
|
console.log('');
|
|
2299
2460
|
console.log(`${c.info('[INFO]')} Server URL: ${c.bright('http://' + DISPLAY_HOST + ':' + SERVER_PORT)}`);
|
|
2300
2461
|
console.log(`${c.info('[INFO]')} Installed at: ${c.dim(appInstallPath)}`);
|
|
2462
|
+
// Print LAN IP + open inbound firewall port (Linux auto, Windows/Mac
|
|
2463
|
+
// ask interactively once, then persist the decision). Non-fatal on
|
|
2464
|
+
// any error — LAN access often works without a rule anyway.
|
|
2465
|
+
try {
|
|
2466
|
+
await ensurePortOpen(Number(SERVER_PORT));
|
|
2467
|
+
}
|
|
2468
|
+
catch (err) {
|
|
2469
|
+
console.log(`${c.dim('[INFO]')} Port-access helper failed: ${err?.message || err}`);
|
|
2470
|
+
}
|
|
2301
2471
|
console.log(`${c.tip('[TIP]')} Run "pixcode status" for full configuration details`);
|
|
2302
2472
|
console.log('');
|
|
2303
2473
|
// Start watching the projects folder for changes
|