@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.
Files changed (202) 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 -64
  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 -2556
  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
@@ -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
- try {
305
- // Get the project root directory (parent of server directory)
306
- const projectRoot = APP_ROOT;
307
- console.log('Starting system update from directory:', projectRoot);
308
- // Platform deployments use their own update workflow from the project root.
309
- const updateCommand = IS_PLATFORM
310
- // In platform, husky and dev dependencies are not needed
311
- ? 'npm run update:platform'
312
- : installMode === 'git'
313
- ? 'git checkout main && git pull && npm install'
314
- : 'npm install -g @pixelbyte-software/pixcode@latest';
315
- const updateCwd = IS_PLATFORM || installMode === 'git'
316
- ? projectRoot
317
- : os.homedir();
318
- const child = spawn('sh', ['-c', updateCommand], {
319
- cwd: updateCwd,
320
- env: process.env
321
- });
322
- let output = '';
323
- let errorOutput = '';
324
- child.stdout.on('data', (data) => {
325
- const text = data.toString();
326
- output += text;
327
- console.log('Update output:', text);
328
- });
329
- child.stderr.on('data', (data) => {
330
- const text = data.toString();
331
- errorOutput += text;
332
- console.error('Update error:', text);
333
- });
334
- child.on('close', (code) => {
335
- if (code === 0) {
336
- res.json({
337
- success: true,
338
- output: output || 'Update completed successfully',
339
- message: 'Update completed. Please restart the server to apply changes.'
340
- });
341
- }
342
- else {
343
- res.status(500).json({
344
- success: false,
345
- error: 'Update command failed',
346
- output: output,
347
- errorOutput: errorOutput
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
- child.on('error', (error) => {
352
- console.error('Update process error:', error);
353
- 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', {
354
425
  success: false,
355
- error: error.message
426
+ error: `Update command exited with code ${code}`,
356
427
  });
357
- });
358
- }
359
- catch (error) {
360
- console.error('System update error:', error);
361
- res.status(500).json({
362
- success: false,
363
- error: error.message
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' : (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';
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