@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.
Files changed (205) hide show
  1. package/LICENSE +718 -718
  2. package/README.de.md +248 -248
  3. package/README.ja.md +240 -240
  4. package/README.ko.md +240 -240
  5. package/README.md +295 -285
  6. package/README.ru.md +248 -248
  7. package/README.tr.md +250 -250
  8. package/README.zh-CN.md +240 -240
  9. package/dist/api-docs.html +879 -879
  10. package/dist/assets/index-BRRJ47XQ.css +32 -0
  11. package/dist/assets/index-EQohwyiC.js +837 -0
  12. package/dist/clear-cache.html +85 -85
  13. package/dist/convert-icons.md +52 -52
  14. package/dist/favicon.png +0 -0
  15. package/dist/favicon.svg +7 -8
  16. package/dist/generate-icons.js +48 -48
  17. package/dist/icons/codex-white.svg +3 -3
  18. package/dist/icons/codex.svg +3 -3
  19. package/dist/icons/cursor-white.svg +11 -11
  20. package/dist/icons/icon-128x128.png +0 -0
  21. package/dist/icons/icon-128x128.svg +9 -12
  22. package/dist/icons/icon-144x144.png +0 -0
  23. package/dist/icons/icon-144x144.svg +9 -12
  24. package/dist/icons/icon-152x152.png +0 -0
  25. package/dist/icons/icon-152x152.svg +9 -12
  26. package/dist/icons/icon-192x192.png +0 -0
  27. package/dist/icons/icon-192x192.svg +9 -12
  28. package/dist/icons/icon-384x384.png +0 -0
  29. package/dist/icons/icon-384x384.svg +9 -12
  30. package/dist/icons/icon-512x512.png +0 -0
  31. package/dist/icons/icon-512x512.svg +9 -12
  32. package/dist/icons/icon-72x72.png +0 -0
  33. package/dist/icons/icon-72x72.svg +9 -12
  34. package/dist/icons/icon-96x96.png +0 -0
  35. package/dist/icons/icon-96x96.svg +9 -12
  36. package/dist/icons/icon-template.svg +9 -12
  37. package/dist/icons/qwen-ai-icon.png +0 -0
  38. package/dist/index.html +59 -49
  39. package/dist/logo.png +0 -0
  40. package/dist/logo.svg +11 -16
  41. package/dist/manifest.json +60 -60
  42. package/dist/sw.js +124 -124
  43. package/dist-server/server/cli.js +100 -97
  44. package/dist-server/server/cli.js.map +1 -1
  45. package/dist-server/server/daemon/manager.js +33 -33
  46. package/dist-server/server/daemon-manager.js +62 -62
  47. package/dist-server/server/database/db.js +114 -22
  48. package/dist-server/server/database/db.js.map +1 -1
  49. package/dist-server/server/database/schema.js +122 -89
  50. package/dist-server/server/database/schema.js.map +1 -1
  51. package/dist-server/server/gemini-cli.js +6 -1
  52. package/dist-server/server/gemini-cli.js.map +1 -1
  53. package/dist-server/server/index.js +234 -65
  54. package/dist-server/server/index.js.map +1 -1
  55. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +29 -2
  56. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -1
  57. package/dist-server/server/modules/providers/list/codex/codex-auth.provider.js +22 -2
  58. package/dist-server/server/modules/providers/list/codex/codex-auth.provider.js.map +1 -1
  59. package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js +2 -2
  60. package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js.map +1 -1
  61. package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js +14 -2
  62. package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js.map +1 -1
  63. package/dist-server/server/modules/providers/list/qwen/qwen-auth.provider.js +132 -0
  64. package/dist-server/server/modules/providers/list/qwen/qwen-auth.provider.js.map +1 -0
  65. package/dist-server/server/modules/providers/list/qwen/qwen-mcp.provider.js +87 -0
  66. package/dist-server/server/modules/providers/list/qwen/qwen-mcp.provider.js.map +1 -0
  67. package/dist-server/server/modules/providers/list/qwen/qwen-sessions.provider.js +201 -0
  68. package/dist-server/server/modules/providers/list/qwen/qwen-sessions.provider.js.map +1 -0
  69. package/dist-server/server/modules/providers/list/qwen/qwen.provider.js +19 -0
  70. package/dist-server/server/modules/providers/list/qwen/qwen.provider.js.map +1 -0
  71. package/dist-server/server/modules/providers/provider.registry.js +2 -0
  72. package/dist-server/server/modules/providers/provider.registry.js.map +1 -1
  73. package/dist-server/server/modules/providers/provider.routes.js +310 -1
  74. package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
  75. package/dist-server/server/projects.js +197 -6
  76. package/dist-server/server/projects.js.map +1 -1
  77. package/dist-server/server/qwen-code-cli.js +350 -0
  78. package/dist-server/server/qwen-code-cli.js.map +1 -0
  79. package/dist-server/server/qwen-response-handler.js +70 -0
  80. package/dist-server/server/qwen-response-handler.js.map +1 -0
  81. package/dist-server/server/routes/commands.js +25 -25
  82. package/dist-server/server/routes/git.js +17 -17
  83. package/dist-server/server/routes/network.js +116 -0
  84. package/dist-server/server/routes/network.js.map +1 -0
  85. package/dist-server/server/routes/projects.js +43 -0
  86. package/dist-server/server/routes/projects.js.map +1 -1
  87. package/dist-server/server/routes/qwen.js +23 -0
  88. package/dist-server/server/routes/qwen.js.map +1 -0
  89. package/dist-server/server/routes/taskmaster.js +419 -419
  90. package/dist-server/server/routes/telegram.js +119 -0
  91. package/dist-server/server/routes/telegram.js.map +1 -0
  92. package/dist-server/server/services/external-access.js +228 -0
  93. package/dist-server/server/services/external-access.js.map +1 -0
  94. package/dist-server/server/services/install-jobs.js +394 -0
  95. package/dist-server/server/services/install-jobs.js.map +1 -0
  96. package/dist-server/server/services/notification-orchestrator.js +19 -5
  97. package/dist-server/server/services/notification-orchestrator.js.map +1 -1
  98. package/dist-server/server/services/provider-credentials.js +154 -0
  99. package/dist-server/server/services/provider-credentials.js.map +1 -0
  100. package/dist-server/server/services/provider-models.js +218 -0
  101. package/dist-server/server/services/provider-models.js.map +1 -0
  102. package/dist-server/server/services/telegram/bot.js +259 -0
  103. package/dist-server/server/services/telegram/bot.js.map +1 -0
  104. package/dist-server/server/services/telegram/translations.js +160 -0
  105. package/dist-server/server/services/telegram/translations.js.map +1 -0
  106. package/dist-server/server/utils/port-access.js +196 -0
  107. package/dist-server/server/utils/port-access.js.map +1 -0
  108. package/dist-server/shared/modelConstants.js +18 -0
  109. package/dist-server/shared/modelConstants.js.map +1 -1
  110. package/package.json +177 -168
  111. package/scripts/fix-node-pty.js +67 -67
  112. package/server/claude-sdk.js +834 -834
  113. package/server/cli.js +940 -937
  114. package/server/constants/config.js +4 -4
  115. package/server/cursor-cli.js +342 -342
  116. package/server/daemon/manager.js +564 -564
  117. package/server/daemon-manager.js +920 -920
  118. package/server/database/db.js +696 -593
  119. package/server/database/schema.js +138 -102
  120. package/server/gemini-cli.js +475 -469
  121. package/server/gemini-response-handler.js +79 -79
  122. package/server/index.js +2730 -2557
  123. package/server/load-env.js +34 -34
  124. package/server/middleware/auth.js +132 -132
  125. package/server/modules/providers/list/claude/claude-auth.provider.ts +145 -123
  126. package/server/modules/providers/list/claude/claude-mcp.provider.ts +135 -135
  127. package/server/modules/providers/list/claude/claude-sessions.provider.ts +306 -306
  128. package/server/modules/providers/list/claude/claude.provider.ts +15 -15
  129. package/server/modules/providers/list/codex/codex-auth.provider.ts +115 -100
  130. package/server/modules/providers/list/codex/codex-mcp.provider.ts +135 -135
  131. package/server/modules/providers/list/codex/codex-sessions.provider.ts +319 -319
  132. package/server/modules/providers/list/codex/codex.provider.ts +15 -15
  133. package/server/modules/providers/list/cursor/cursor-auth.provider.ts +143 -143
  134. package/server/modules/providers/list/cursor/cursor-mcp.provider.ts +108 -108
  135. package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +421 -421
  136. package/server/modules/providers/list/cursor/cursor.provider.ts +15 -15
  137. package/server/modules/providers/list/gemini/gemini-auth.provider.ts +163 -151
  138. package/server/modules/providers/list/gemini/gemini-mcp.provider.ts +110 -110
  139. package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +227 -227
  140. package/server/modules/providers/list/gemini/gemini.provider.ts +15 -15
  141. package/server/modules/providers/list/qwen/qwen-auth.provider.ts +145 -0
  142. package/server/modules/providers/list/qwen/qwen-mcp.provider.ts +114 -0
  143. package/server/modules/providers/list/qwen/qwen-sessions.provider.ts +218 -0
  144. package/server/modules/providers/list/qwen/qwen.provider.ts +21 -0
  145. package/server/modules/providers/provider.registry.ts +38 -36
  146. package/server/modules/providers/provider.routes.ts +583 -217
  147. package/server/modules/providers/services/mcp.service.ts +94 -94
  148. package/server/modules/providers/services/provider-auth.service.ts +26 -26
  149. package/server/modules/providers/services/sessions.service.ts +45 -45
  150. package/server/modules/providers/shared/base/abstract.provider.ts +20 -20
  151. package/server/modules/providers/shared/mcp/mcp.provider.ts +151 -151
  152. package/server/modules/providers/tests/mcp.test.ts +293 -293
  153. package/server/openai-codex.js +426 -426
  154. package/server/projects.js +2993 -2792
  155. package/server/qwen-code-cli.js +392 -0
  156. package/server/qwen-response-handler.js +73 -0
  157. package/server/routes/agent.js +1245 -1245
  158. package/server/routes/auth.js +134 -134
  159. package/server/routes/codex.js +19 -19
  160. package/server/routes/commands.js +554 -554
  161. package/server/routes/cursor.js +52 -52
  162. package/server/routes/gemini.js +24 -24
  163. package/server/routes/git.js +1488 -1488
  164. package/server/routes/mcp-utils.js +31 -31
  165. package/server/routes/messages.js +61 -61
  166. package/server/routes/network.js +128 -0
  167. package/server/routes/plugins.js +307 -307
  168. package/server/routes/projects.js +675 -627
  169. package/server/routes/qwen.js +27 -0
  170. package/server/routes/settings.js +286 -286
  171. package/server/routes/taskmaster.js +1471 -1471
  172. package/server/routes/telegram.js +125 -0
  173. package/server/routes/user.js +123 -123
  174. package/server/services/external-access.js +240 -0
  175. package/server/services/install-jobs.js +410 -0
  176. package/server/services/notification-orchestrator.js +242 -227
  177. package/server/services/provider-credentials.js +151 -0
  178. package/server/services/provider-models.js +225 -0
  179. package/server/services/telegram/bot.js +280 -0
  180. package/server/services/telegram/translations.js +170 -0
  181. package/server/services/vapid-keys.js +35 -35
  182. package/server/sessionManager.js +225 -225
  183. package/server/shared/interfaces.ts +54 -54
  184. package/server/shared/types.ts +172 -172
  185. package/server/shared/utils.ts +193 -193
  186. package/server/tsconfig.json +36 -36
  187. package/server/utils/colors.js +21 -21
  188. package/server/utils/commandParser.js +303 -303
  189. package/server/utils/frontmatter.js +18 -18
  190. package/server/utils/gitConfig.js +34 -34
  191. package/server/utils/mcp-detector.js +147 -147
  192. package/server/utils/plugin-loader.js +457 -457
  193. package/server/utils/plugin-process-manager.js +184 -184
  194. package/server/utils/port-access.js +209 -0
  195. package/server/utils/runtime-paths.js +37 -37
  196. package/server/utils/taskmaster-websocket.js +128 -128
  197. package/server/utils/url-detection.js +71 -71
  198. package/server/vite-daemon.js +78 -78
  199. package/shared/modelConstants.js +117 -97
  200. package/shared/networkHosts.js +22 -22
  201. package/dist/assets/index-C2c9QNwK.css +0 -32
  202. package/dist/assets/index-DyXDZED-.js +0 -1277
  203. package/dist-server/server/routes/cli-auth.js +0 -25
  204. package/dist-server/server/routes/cli-auth.js.map +0 -1
  205. 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
- try {
306
- // Get the project root directory (parent of server directory)
307
- const projectRoot = APP_ROOT;
308
- console.log('Starting system update from directory:', projectRoot);
309
- // Platform deployments use their own update workflow from the project root.
310
- const updateCommand = IS_PLATFORM
311
- // In platform, husky and dev dependencies are not needed
312
- ? 'npm run update:platform'
313
- : installMode === 'git'
314
- ? 'git checkout main && git pull && npm install'
315
- : 'npm install -g @pixelbyte-software/pixcode@latest';
316
- const updateCwd = IS_PLATFORM || installMode === 'git'
317
- ? projectRoot
318
- : os.homedir();
319
- const child = spawn('sh', ['-c', updateCommand], {
320
- cwd: updateCwd,
321
- env: process.env
322
- });
323
- let output = '';
324
- let errorOutput = '';
325
- child.stdout.on('data', (data) => {
326
- const text = data.toString();
327
- output += text;
328
- console.log('Update output:', text);
329
- });
330
- child.stderr.on('data', (data) => {
331
- const text = data.toString();
332
- errorOutput += text;
333
- console.error('Update error:', text);
334
- });
335
- child.on('close', (code) => {
336
- if (code === 0) {
337
- res.json({
338
- success: true,
339
- output: output || 'Update completed successfully',
340
- message: 'Update completed. Please restart the server to apply changes.'
341
- });
342
- }
343
- else {
344
- res.status(500).json({
345
- success: false,
346
- error: 'Update command failed',
347
- output: output,
348
- errorOutput: errorOutput
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
- child.on('error', (error) => {
353
- console.error('Update process error:', error);
354
- res.status(500).json({
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: error.message
426
+ error: `Update command exited with code ${code}`,
357
427
  });
358
- });
359
- }
360
- catch (error) {
361
- console.error('System update error:', error);
362
- res.status(500).json({
363
- success: false,
364
- error: error.message
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' : (provider === 'codex' ? 'Codex' : (provider === 'gemini' ? 'Gemini' : 'Claude'));
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