@pixelbyte-software/pixcode 1.30.1 → 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 -65
- 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 -2557
- 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
- package/dist-server/server/routes/cli-auth.js +0 -25
- package/dist-server/server/routes/cli-auth.js.map +0 -1
- package/server/routes/cli-auth.js +0 -27
|
@@ -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';
|
|
@@ -42,13 +52,19 @@ import commandsRoutes from './routes/commands.js';
|
|
|
42
52
|
import settingsRoutes from './routes/settings.js';
|
|
43
53
|
import agentRoutes from './routes/agent.js';
|
|
44
54
|
import projectsRoutes, { WORKSPACES_ROOT, WORKSPACES_BASE, validateWorkspacePath, normalizeWorkspacePath, } from './routes/projects.js';
|
|
45
|
-
import cliAuthRoutes from './routes/cli-auth.js';
|
|
46
55
|
import userRoutes from './routes/user.js';
|
|
47
56
|
import codexRoutes from './routes/codex.js';
|
|
48
57
|
import geminiRoutes from './routes/gemini.js';
|
|
58
|
+
import qwenRoutes from './routes/qwen.js';
|
|
49
59
|
import pluginsRoutes from './routes/plugins.js';
|
|
50
60
|
import messagesRoutes from './routes/messages.js';
|
|
51
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';
|
|
52
68
|
import { startEnabledPluginServers, stopAllPlugins, getPluginPort } from './utils/plugin-process-manager.js';
|
|
53
69
|
import { initializeDatabase, sessionNamesDb, applyCustomSessionNames } from './database/db.js';
|
|
54
70
|
import { configureWebPush } from './services/vapid-keys.js';
|
|
@@ -56,7 +72,7 @@ import { validateApiKey, authenticateToken, authenticateWebSocket } from './midd
|
|
|
56
72
|
import { IS_PLATFORM } from './constants/config.js';
|
|
57
73
|
import { getConnectableHost } from '../shared/networkHosts.js';
|
|
58
74
|
import { buildDaemonCliCommand, handleDaemonCommand } from './daemon-manager.js';
|
|
59
|
-
const VALID_PROVIDERS = ['claude', 'codex', 'cursor', 'gemini'];
|
|
75
|
+
const VALID_PROVIDERS = ['claude', 'codex', 'cursor', 'gemini', 'qwen'];
|
|
60
76
|
// File system watchers for provider project/session folders
|
|
61
77
|
const PROVIDER_WATCH_PATHS = [
|
|
62
78
|
{ provider: 'claude', rootPath: path.join(os.homedir(), '.claude', 'projects') },
|
|
@@ -65,6 +81,10 @@ const PROVIDER_WATCH_PATHS = [
|
|
|
65
81
|
{ provider: 'gemini', rootPath: path.join(os.homedir(), '.gemini', 'projects') },
|
|
66
82
|
{ provider: 'gemini_sessions', rootPath: path.join(os.homedir(), '.gemini', 'sessions') },
|
|
67
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') },
|
|
68
88
|
];
|
|
69
89
|
const WATCHER_IGNORED_PATTERNS = [
|
|
70
90
|
'**/node_modules/**',
|
|
@@ -244,7 +264,8 @@ app.get('/health', (req, res) => {
|
|
|
244
264
|
res.json({
|
|
245
265
|
status: 'ok',
|
|
246
266
|
timestamp: new Date().toISOString(),
|
|
247
|
-
installMode
|
|
267
|
+
installMode,
|
|
268
|
+
version: SERVER_VERSION
|
|
248
269
|
});
|
|
249
270
|
});
|
|
250
271
|
// Optional API key validation (if configured)
|
|
@@ -271,12 +292,18 @@ app.use('/api/user', authenticateToken, userRoutes);
|
|
|
271
292
|
app.use('/api/codex', authenticateToken, codexRoutes);
|
|
272
293
|
// Gemini API Routes (protected)
|
|
273
294
|
app.use('/api/gemini', authenticateToken, geminiRoutes);
|
|
295
|
+
// Qwen Code API Routes (protected)
|
|
296
|
+
app.use('/api/qwen', authenticateToken, qwenRoutes);
|
|
274
297
|
// Plugins API Routes (protected)
|
|
275
298
|
app.use('/api/plugins', authenticateToken, pluginsRoutes);
|
|
276
299
|
// Unified session messages route (protected)
|
|
277
300
|
app.use('/api/sessions', authenticateToken, messagesRoutes);
|
|
278
301
|
// Unified provider MCP routes (protected)
|
|
279
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);
|
|
280
307
|
// Agent API Routes (uses API key authentication)
|
|
281
308
|
app.use('/api/agent', agentRoutes);
|
|
282
309
|
// Serve public files (like api-docs.html)
|
|
@@ -300,70 +327,122 @@ app.use(express.static(path.join(APP_ROOT, 'dist'), {
|
|
|
300
327
|
// API Routes (protected)
|
|
301
328
|
// /api/config endpoint removed - no longer needed
|
|
302
329
|
// Frontend now uses window.location for WebSocket URLs
|
|
303
|
-
// 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.
|
|
304
333
|
app.post('/api/system/update', authenticateToken, async (req, res) => {
|
|
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
|
-
|
|
349
|
-
|
|
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();
|
|
350
392
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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', {
|
|
355
425
|
success: false,
|
|
356
|
-
error:
|
|
426
|
+
error: `Update command exited with code ${code}`,
|
|
357
427
|
});
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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);
|
|
367
446
|
});
|
|
368
447
|
app.get('/api/projects', authenticateToken, async (req, res) => {
|
|
369
448
|
try {
|
|
@@ -1318,6 +1397,13 @@ function handleChatConnection(ws, request) {
|
|
|
1318
1397
|
console.log('🤖 Model:', data.options?.model || 'default');
|
|
1319
1398
|
await spawnGemini(data.command, data.options, writer);
|
|
1320
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
|
+
}
|
|
1321
1407
|
else if (data.type === 'cursor-resume') {
|
|
1322
1408
|
// Backward compatibility: treat as cursor-command with resume and no prompt
|
|
1323
1409
|
console.log('[DEBUG] Cursor resume session (compat):', data.sessionId);
|
|
@@ -1340,6 +1426,9 @@ function handleChatConnection(ws, request) {
|
|
|
1340
1426
|
else if (provider === 'gemini') {
|
|
1341
1427
|
success = abortGeminiSession(data.sessionId);
|
|
1342
1428
|
}
|
|
1429
|
+
else if (provider === 'qwen') {
|
|
1430
|
+
success = abortQwenSession(data.sessionId);
|
|
1431
|
+
}
|
|
1343
1432
|
else {
|
|
1344
1433
|
// Use Claude Agents SDK
|
|
1345
1434
|
success = await abortClaudeSDKSession(data.sessionId);
|
|
@@ -1378,6 +1467,9 @@ function handleChatConnection(ws, request) {
|
|
|
1378
1467
|
else if (provider === 'gemini') {
|
|
1379
1468
|
isActive = isGeminiSessionActive(sessionId);
|
|
1380
1469
|
}
|
|
1470
|
+
else if (provider === 'qwen') {
|
|
1471
|
+
isActive = isQwenSessionActive(sessionId);
|
|
1472
|
+
}
|
|
1381
1473
|
else {
|
|
1382
1474
|
// Use Claude Agents SDK
|
|
1383
1475
|
isActive = isClaudeSDKSessionActive(sessionId);
|
|
@@ -1412,7 +1504,8 @@ function handleChatConnection(ws, request) {
|
|
|
1412
1504
|
claude: getActiveClaudeSDKSessions(),
|
|
1413
1505
|
cursor: getActiveCursorSessions(),
|
|
1414
1506
|
codex: getActiveCodexSessions(),
|
|
1415
|
-
gemini: getActiveGeminiSessions()
|
|
1507
|
+
gemini: getActiveGeminiSessions(),
|
|
1508
|
+
qwen: getActiveQwenSessions()
|
|
1416
1509
|
};
|
|
1417
1510
|
writer.send({
|
|
1418
1511
|
type: 'active-sessions',
|
|
@@ -1508,7 +1601,11 @@ function handleShellConnection(ws) {
|
|
|
1508
1601
|
welcomeMsg = `\x1b[36mStarting terminal in: ${projectPath}\x1b[0m\r\n`;
|
|
1509
1602
|
}
|
|
1510
1603
|
else {
|
|
1511
|
-
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';
|
|
1512
1609
|
welcomeMsg = hasSession ?
|
|
1513
1610
|
`\x1b[36mResuming ${providerName} session ${sessionId} in: ${projectPath}\x1b[0m\r\n` :
|
|
1514
1611
|
`\x1b[36mStarting new ${providerName} session in: ${projectPath}\x1b[0m\r\n`;
|
|
@@ -1593,6 +1690,33 @@ function handleShellConnection(ws) {
|
|
|
1593
1690
|
shellCommand = command;
|
|
1594
1691
|
}
|
|
1595
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
|
+
}
|
|
1596
1720
|
else {
|
|
1597
1721
|
// Claude (default provider)
|
|
1598
1722
|
const command = initialCommand || 'claude';
|
|
@@ -1870,6 +1994,17 @@ app.get('/api/projects/:projectName/sessions/:sessionId/token-usage', authentica
|
|
|
1870
1994
|
message: 'Token usage tracking not available for Gemini sessions'
|
|
1871
1995
|
});
|
|
1872
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
|
+
}
|
|
1873
2008
|
// Handle Codex sessions
|
|
1874
2009
|
if (provider === 'codex') {
|
|
1875
2010
|
const codexSessionsDir = path.join(homeDir, '.codex', 'sessions');
|
|
@@ -2280,6 +2415,31 @@ async function startServer() {
|
|
|
2280
2415
|
await initializeDatabase();
|
|
2281
2416
|
// Configure Web Push (VAPID keys)
|
|
2282
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
|
+
});
|
|
2283
2443
|
// Check if running in production mode (dist folder exists)
|
|
2284
2444
|
const distIndexPath = path.join(APP_ROOT, 'dist', 'index.html');
|
|
2285
2445
|
const isProduction = fs.existsSync(distIndexPath);
|
|
@@ -2299,6 +2459,15 @@ async function startServer() {
|
|
|
2299
2459
|
console.log('');
|
|
2300
2460
|
console.log(`${c.info('[INFO]')} Server URL: ${c.bright('http://' + DISPLAY_HOST + ':' + SERVER_PORT)}`);
|
|
2301
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
|
+
}
|
|
2302
2471
|
console.log(`${c.tip('[TIP]')} Run "pixcode status" for full configuration details`);
|
|
2303
2472
|
console.log('');
|
|
2304
2473
|
// Start watching the projects folder for changes
|