@pixelbyte-software/pixcode 1.33.11 → 1.35.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 (164) hide show
  1. package/dist/api-docs.html +162 -9
  2. package/dist/assets/index-B8w57E1r.css +32 -0
  3. package/dist/assets/index-Djuh0wHV.js +854 -0
  4. package/dist/favicon.svg +8 -8
  5. package/dist/icons/icon-128x128.svg +9 -9
  6. package/dist/icons/icon-144x144.svg +9 -9
  7. package/dist/icons/icon-152x152.svg +9 -9
  8. package/dist/icons/icon-192x192.svg +9 -9
  9. package/dist/icons/icon-384x384.svg +9 -9
  10. package/dist/icons/icon-512x512.svg +9 -9
  11. package/dist/icons/icon-72x72.svg +9 -9
  12. package/dist/icons/icon-96x96.svg +9 -9
  13. package/dist/icons/icon-template.svg +9 -9
  14. package/dist/index.html +2 -2
  15. package/dist/logo.svg +12 -12
  16. package/dist/openapi.yaml +383 -1
  17. package/dist-server/server/claude-sdk.js +38 -7
  18. package/dist-server/server/claude-sdk.js.map +1 -1
  19. package/dist-server/server/cli.js +12 -17
  20. package/dist-server/server/cli.js.map +1 -1
  21. package/dist-server/server/daemon-manager.js +98 -51
  22. package/dist-server/server/daemon-manager.js.map +1 -1
  23. package/dist-server/server/database/json-store.js +8 -5
  24. package/dist-server/server/database/json-store.js.map +1 -1
  25. package/dist-server/server/index.js +34 -9
  26. package/dist-server/server/index.js.map +1 -1
  27. package/dist-server/server/modules/orchestration/a2a/adapter-registry.js +73 -0
  28. package/dist-server/server/modules/orchestration/a2a/adapter-registry.js.map +1 -0
  29. package/dist-server/server/modules/orchestration/a2a/adapters/abstract-a2a.adapter.js +17 -0
  30. package/dist-server/server/modules/orchestration/a2a/adapters/abstract-a2a.adapter.js.map +1 -0
  31. package/dist-server/server/modules/orchestration/a2a/adapters/claude-code.adapter.js +234 -0
  32. package/dist-server/server/modules/orchestration/a2a/adapters/claude-code.adapter.js.map +1 -0
  33. package/dist-server/server/modules/orchestration/a2a/adapters/codex.adapter.js +202 -0
  34. package/dist-server/server/modules/orchestration/a2a/adapters/codex.adapter.js.map +1 -0
  35. package/dist-server/server/modules/orchestration/a2a/adapters/cursor.adapter.js +205 -0
  36. package/dist-server/server/modules/orchestration/a2a/adapters/cursor.adapter.js.map +1 -0
  37. package/dist-server/server/modules/orchestration/a2a/adapters/gemini.adapter.js +205 -0
  38. package/dist-server/server/modules/orchestration/a2a/adapters/gemini.adapter.js.map +1 -0
  39. package/dist-server/server/modules/orchestration/a2a/adapters/opencode.adapter.js +205 -0
  40. package/dist-server/server/modules/orchestration/a2a/adapters/opencode.adapter.js.map +1 -0
  41. package/dist-server/server/modules/orchestration/a2a/adapters/qwen.adapter.js +205 -0
  42. package/dist-server/server/modules/orchestration/a2a/adapters/qwen.adapter.js.map +1 -0
  43. package/dist-server/server/modules/orchestration/a2a/agent-card.js +50 -0
  44. package/dist-server/server/modules/orchestration/a2a/agent-card.js.map +1 -0
  45. package/dist-server/server/modules/orchestration/a2a/auth.middleware.js +25 -0
  46. package/dist-server/server/modules/orchestration/a2a/auth.middleware.js.map +1 -0
  47. package/dist-server/server/modules/orchestration/a2a/bus.js +34 -0
  48. package/dist-server/server/modules/orchestration/a2a/bus.js.map +1 -0
  49. package/dist-server/server/modules/orchestration/a2a/routes.js +497 -0
  50. package/dist-server/server/modules/orchestration/a2a/routes.js.map +1 -0
  51. package/dist-server/server/modules/orchestration/a2a/task-store.js +144 -0
  52. package/dist-server/server/modules/orchestration/a2a/task-store.js.map +1 -0
  53. package/dist-server/server/modules/orchestration/a2a/types.js +6 -0
  54. package/dist-server/server/modules/orchestration/a2a/types.js.map +1 -0
  55. package/dist-server/server/modules/orchestration/a2a/validator.js +101 -0
  56. package/dist-server/server/modules/orchestration/a2a/validator.js.map +1 -0
  57. package/dist-server/server/modules/orchestration/index.js +24 -0
  58. package/dist-server/server/modules/orchestration/index.js.map +1 -0
  59. package/dist-server/server/modules/orchestration/preview/port-watcher.js +90 -0
  60. package/dist-server/server/modules/orchestration/preview/port-watcher.js.map +1 -0
  61. package/dist-server/server/modules/orchestration/preview/preview-proxy.js +58 -0
  62. package/dist-server/server/modules/orchestration/preview/preview-proxy.js.map +1 -0
  63. package/dist-server/server/modules/orchestration/preview/types.js +2 -0
  64. package/dist-server/server/modules/orchestration/preview/types.js.map +1 -0
  65. package/dist-server/server/modules/orchestration/tasks/orchestration-task-store.js +37 -0
  66. package/dist-server/server/modules/orchestration/tasks/orchestration-task-store.js.map +1 -0
  67. package/dist-server/server/modules/orchestration/tasks/orchestration-task.routes.js +68 -0
  68. package/dist-server/server/modules/orchestration/tasks/orchestration-task.routes.js.map +1 -0
  69. package/dist-server/server/modules/orchestration/tasks/orchestration-task.service.js +128 -0
  70. package/dist-server/server/modules/orchestration/tasks/orchestration-task.service.js.map +1 -0
  71. package/dist-server/server/modules/orchestration/tasks/orchestration-task.types.js +2 -0
  72. package/dist-server/server/modules/orchestration/tasks/orchestration-task.types.js.map +1 -0
  73. package/dist-server/server/modules/orchestration/workflows/built-in-workflows.js +126 -0
  74. package/dist-server/server/modules/orchestration/workflows/built-in-workflows.js.map +1 -0
  75. package/dist-server/server/modules/orchestration/workflows/workflow-runner.js +1047 -0
  76. package/dist-server/server/modules/orchestration/workflows/workflow-runner.js.map +1 -0
  77. package/dist-server/server/modules/orchestration/workflows/workflow-store.js +76 -0
  78. package/dist-server/server/modules/orchestration/workflows/workflow-store.js.map +1 -0
  79. package/dist-server/server/modules/orchestration/workflows/workflow.routes.js +151 -0
  80. package/dist-server/server/modules/orchestration/workflows/workflow.routes.js.map +1 -0
  81. package/dist-server/server/modules/orchestration/workflows/workflow.types.js +2 -0
  82. package/dist-server/server/modules/orchestration/workflows/workflow.types.js.map +1 -0
  83. package/dist-server/server/modules/orchestration/workflows/workspace-target.js +98 -0
  84. package/dist-server/server/modules/orchestration/workflows/workspace-target.js.map +1 -0
  85. package/dist-server/server/modules/orchestration/workspace/docker-workspace.js +122 -0
  86. package/dist-server/server/modules/orchestration/workspace/docker-workspace.js.map +1 -0
  87. package/dist-server/server/modules/orchestration/workspace/path-safety.js +48 -0
  88. package/dist-server/server/modules/orchestration/workspace/path-safety.js.map +1 -0
  89. package/dist-server/server/modules/orchestration/workspace/types.js +11 -0
  90. package/dist-server/server/modules/orchestration/workspace/types.js.map +1 -0
  91. package/dist-server/server/modules/orchestration/workspace/workspace-manager.js +80 -0
  92. package/dist-server/server/modules/orchestration/workspace/workspace-manager.js.map +1 -0
  93. package/dist-server/server/modules/orchestration/workspace/worktree-workspace.js +96 -0
  94. package/dist-server/server/modules/orchestration/workspace/worktree-workspace.js.map +1 -0
  95. package/dist-server/server/modules/providers/index.js +3 -0
  96. package/dist-server/server/modules/providers/index.js.map +1 -0
  97. package/dist-server/server/openai-codex.js +35 -4
  98. package/dist-server/server/openai-codex.js.map +1 -1
  99. package/dist-server/server/routes/taskmaster.js +106 -89
  100. package/dist-server/server/routes/taskmaster.js.map +1 -1
  101. package/package.json +3 -1
  102. package/scripts/smoke/a2a-roundtrip.mjs +167 -0
  103. package/scripts/smoke/orchestration-api.mjs +172 -0
  104. package/scripts/smoke/orchestration-live-run.mjs +176 -0
  105. package/server/claude-sdk.js +48 -7
  106. package/server/cli.js +12 -17
  107. package/server/daemon-manager.js +90 -51
  108. package/server/database/db.js +794 -794
  109. package/server/database/json-store.js +8 -5
  110. package/server/index.js +49 -9
  111. package/server/modules/orchestration/a2a/adapter-registry.ts +108 -0
  112. package/server/modules/orchestration/a2a/adapters/abstract-a2a.adapter.ts +55 -0
  113. package/server/modules/orchestration/a2a/adapters/claude-code.adapter.ts +284 -0
  114. package/server/modules/orchestration/a2a/adapters/codex.adapter.ts +244 -0
  115. package/server/modules/orchestration/a2a/adapters/cursor.adapter.ts +249 -0
  116. package/server/modules/orchestration/a2a/adapters/gemini.adapter.ts +248 -0
  117. package/server/modules/orchestration/a2a/adapters/opencode.adapter.ts +248 -0
  118. package/server/modules/orchestration/a2a/adapters/qwen.adapter.ts +248 -0
  119. package/server/modules/orchestration/a2a/agent-card.ts +55 -0
  120. package/server/modules/orchestration/a2a/auth.middleware.ts +29 -0
  121. package/server/modules/orchestration/a2a/bus.ts +46 -0
  122. package/server/modules/orchestration/a2a/routes.ts +577 -0
  123. package/server/modules/orchestration/a2a/task-store.ts +178 -0
  124. package/server/modules/orchestration/a2a/types.ts +125 -0
  125. package/server/modules/orchestration/a2a/validator.ts +113 -0
  126. package/server/modules/orchestration/index.ts +66 -0
  127. package/server/modules/orchestration/preview/port-watcher.ts +112 -0
  128. package/server/modules/orchestration/preview/preview-proxy.ts +60 -0
  129. package/server/modules/orchestration/preview/types.ts +19 -0
  130. package/server/modules/orchestration/tasks/orchestration-task-store.ts +45 -0
  131. package/server/modules/orchestration/tasks/orchestration-task.routes.ts +73 -0
  132. package/server/modules/orchestration/tasks/orchestration-task.service.ts +145 -0
  133. package/server/modules/orchestration/tasks/orchestration-task.types.ts +29 -0
  134. package/server/modules/orchestration/workflows/built-in-workflows.ts +127 -0
  135. package/server/modules/orchestration/workflows/workflow-runner.ts +1206 -0
  136. package/server/modules/orchestration/workflows/workflow-store.ts +97 -0
  137. package/server/modules/orchestration/workflows/workflow.routes.ts +169 -0
  138. package/server/modules/orchestration/workflows/workflow.types.ts +70 -0
  139. package/server/modules/orchestration/workflows/workspace-target.ts +120 -0
  140. package/server/modules/orchestration/workspace/docker-workspace.ts +135 -0
  141. package/server/modules/orchestration/workspace/path-safety.ts +55 -0
  142. package/server/modules/orchestration/workspace/types.ts +52 -0
  143. package/server/modules/orchestration/workspace/workspace-manager.ts +97 -0
  144. package/server/modules/orchestration/workspace/worktree-workspace.ts +125 -0
  145. package/server/modules/providers/index.ts +2 -0
  146. package/server/modules/providers/list/opencode/opencode-auth.provider.ts +130 -130
  147. package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +126 -126
  148. package/server/modules/providers/list/opencode/opencode.provider.ts +29 -29
  149. package/server/modules/providers/list/qwen/qwen-auth.provider.ts +145 -145
  150. package/server/modules/providers/list/qwen/qwen-mcp.provider.ts +114 -114
  151. package/server/modules/providers/list/qwen/qwen.provider.ts +21 -21
  152. package/server/modules/providers/shared/provider-configs.ts +142 -142
  153. package/server/openai-codex.js +40 -4
  154. package/server/qwen-code-cli.js +395 -395
  155. package/server/qwen-response-handler.js +73 -73
  156. package/server/routes/qwen.js +27 -27
  157. package/server/routes/taskmaster.js +116 -91
  158. package/server/services/external-access.js +171 -171
  159. package/server/services/provider-models.js +381 -381
  160. package/server/services/telegram/telegram-http-client.js +130 -130
  161. package/server/services/vapid-keys.js +36 -36
  162. package/server/utils/port-access.js +209 -209
  163. package/dist/assets/index-B1ghfb4w.css +0 -32
  164. package/dist/assets/index-oLYHJ2X5.js +0 -852
@@ -12,12 +12,13 @@
12
12
  * - WebSocket message streaming
13
13
  */
14
14
 
15
- import { query } from '@anthropic-ai/claude-agent-sdk';
16
15
  import crypto from 'crypto';
17
- import { promises as fs } from 'fs';
16
+ import { existsSync, readFileSync, promises as fs } from 'fs';
18
17
  import path from 'path';
19
18
  import os from 'os';
20
- import { CLAUDE_MODELS } from '../shared/modelConstants.js';
19
+
20
+ import { query } from '@anthropic-ai/claude-agent-sdk';
21
+
21
22
  import {
22
23
  createNotificationEvent,
23
24
  notifyRunFailed,
@@ -140,6 +141,43 @@ function matchesToolPermission(entry, toolName, input) {
140
141
  return false;
141
142
  }
142
143
 
144
+ function readClaudeSettingsEnv(filePath) {
145
+ try {
146
+ if (!existsSync(filePath)) return {};
147
+ const parsed = JSON.parse(readFileSync(filePath, 'utf8'));
148
+ const env = parsed?.env;
149
+ if (!env || typeof env !== 'object') return {};
150
+
151
+ return Object.fromEntries(
152
+ Object.entries(env)
153
+ .filter(([key, value]) =>
154
+ typeof value === 'string' &&
155
+ value.trim() &&
156
+ !key.startsWith('//'),
157
+ ),
158
+ );
159
+ } catch {
160
+ return {};
161
+ }
162
+ }
163
+
164
+ function loadClaudeSettingsEnv(cwd) {
165
+ const files = [
166
+ path.join(os.homedir(), '.claude', 'settings.json'),
167
+ ];
168
+ if (cwd) {
169
+ files.push(
170
+ path.join(cwd, '.claude', 'settings.json'),
171
+ path.join(cwd, '.claude', 'settings.local.json'),
172
+ );
173
+ }
174
+
175
+ return files.reduce((env, filePath) => ({
176
+ ...env,
177
+ ...readClaudeSettingsEnv(filePath),
178
+ }), {});
179
+ }
180
+
143
181
  /**
144
182
  * Maps CLI options to SDK-compatible options format
145
183
  * @param {Object} options - CLI options
@@ -154,7 +192,7 @@ function mapCliOptionsToSDK(options = {}) {
154
192
  // Since claude-agent-sdk 0.2.113+, options.env REPLACES process.env in the subprocess
155
193
  // instead of overlaying it. Without spreading process.env here, users who rely on
156
194
  // ANTHROPIC_BASE_URL, HTTP(S)_PROXY, etc. would silently lose those settings.
157
- sdkOptions.env = { ...process.env };
195
+ sdkOptions.env = { ...process.env, ...loadClaudeSettingsEnv(cwd) };
158
196
 
159
197
  // Claude Code on Windows hard-requires a POSIX bash (typically from Git
160
198
  // for Windows) and reads its path from CLAUDE_CODE_GIT_BASH_PATH. If the
@@ -228,9 +266,12 @@ function mapCliOptionsToSDK(options = {}) {
228
266
 
229
267
  sdkOptions.disallowedTools = settings.disallowedTools || [];
230
268
 
231
- // Map model (default to sonnet)
232
- // Valid models: sonnet, opus, haiku, opusplan, sonnet[1m]
233
- sdkOptions.model = options.model || CLAUDE_MODELS.DEFAULT;
269
+ // Map model only when Pixcode explicitly passes one. If omitted, let
270
+ // Claude Code load the user's own project/user/local settings, including
271
+ // ~/.claude/settings.json "model".
272
+ if (typeof options.model === 'string' && options.model.trim()) {
273
+ sdkOptions.model = options.model.trim();
274
+ }
234
275
  // Model logged at query start below
235
276
 
236
277
  // Map system prompt configuration
package/server/cli.js CHANGED
@@ -185,7 +185,7 @@ Examples:
185
185
  $ pixcode --port 8080 # Start on port 8080
186
186
  $ pixcode --no-daemon # Force foreground mode
187
187
  $ sudo pixcode daemon install --mode system --port 3001
188
- $ pixcode daemon install --mode user --port 3001 --frontend-port 5173
188
+ $ pixcode daemon install --mode user --port 3001 --single-port
189
189
  $ pixcode daemon doctor --mode system
190
190
  $ pixcode update --restart-daemon
191
191
  $ pixcode sandbox ~/my-project # Run in a Docker sandbox
@@ -696,9 +696,8 @@ function printSystemDaemonActiveNotice(port) {
696
696
  console.log(`${c.info('[INFO]')} Logs: ${c.bright(logsCommand)}`);
697
697
  }
698
698
 
699
- function printUserDaemonActiveNotice(port, frontendPort) {
699
+ function printUserDaemonActiveNotice(port) {
700
700
  const effectivePort = Number(port) || 3001;
701
- const effectiveFrontendPort = Number(frontendPort) || 5173;
702
701
  const statusCommand = buildDaemonCliCommand(
703
702
  { subcommand: 'status', mode: 'user' },
704
703
  DAEMON_COMMAND_CONTEXT
@@ -712,8 +711,7 @@ function printUserDaemonActiveNotice(port, frontendPort) {
712
711
  DAEMON_COMMAND_CONTEXT
713
712
  );
714
713
  console.log(`${c.ok('[OK]')} User daemon is active for this account.`);
715
- console.log(`${c.info('[INFO]')} Backend: ${c.bright(`http://localhost:${effectivePort}`)}`);
716
- console.log(`${c.info('[INFO]')} Frontend: ${c.bright(`http://localhost:${effectiveFrontendPort}`)}`);
714
+ console.log(`${c.info('[INFO]')} UI: ${c.bright(`http://localhost:${effectivePort}`)}`);
717
715
  console.log(`${c.info('[INFO]')} Status: ${c.bright(statusCommand)}`);
718
716
  console.log(`${c.info('[INFO]')} Stop: ${c.bright(stopCommand)}`);
719
717
  console.log(`${c.info('[INFO]')} Logs: ${c.bright(logsCommand)}`);
@@ -725,7 +723,7 @@ function isSystemPermissionError(error) {
725
723
  return /(access denied|permission denied|must be root|interactive authentication required|not permitted|failed to connect to bus|operation not permitted|authentication is required|polkit)/i.test(message);
726
724
  }
727
725
 
728
- function buildAutoInstallArgs(mode, options, frontendPort) {
726
+ function buildAutoInstallArgs(mode, options) {
729
727
  const args = ['install', `--mode=${mode}`];
730
728
  if (options.serverPort) {
731
729
  args.push('--port', String(options.serverPort));
@@ -733,9 +731,7 @@ function buildAutoInstallArgs(mode, options, frontendPort) {
733
731
  if (options.databasePath) {
734
732
  args.push('--database-path', String(options.databasePath));
735
733
  }
736
- if (frontendPort) {
737
- args.push('--frontend-port', String(frontendPort));
738
- }
734
+ args.push('--single-port');
739
735
  return args;
740
736
  }
741
737
 
@@ -748,9 +744,8 @@ async function maybeAutoDaemonStart(options = {}) {
748
744
 
749
745
  process.env.PIXCODE_DAEMON_ATTEMPTED = '1';
750
746
  const daemonPort = Number(options.serverPort || process.env.SERVER_PORT || process.env.PORT || '3001');
751
- const frontendPort = Number(process.env.VITE_PORT || '5173');
752
- const systemArgs = buildAutoInstallArgs('system', options, frontendPort);
753
- const userArgs = buildAutoInstallArgs('user', options, frontendPort);
747
+ const systemArgs = buildAutoInstallArgs('system', options);
748
+ const userArgs = buildAutoInstallArgs('user', options);
754
749
 
755
750
  try {
756
751
  console.log(`${c.info('[INFO]')} Linux detected. Enforcing system daemon mode for Pixcode...`);
@@ -773,7 +768,7 @@ async function maybeAutoDaemonStart(options = {}) {
773
768
  {
774
769
  subcommand: 'install',
775
770
  mode: 'system',
776
- extraArgs: ['--port', String(daemonPort), '--frontend-port', String(frontendPort)],
771
+ extraArgs: ['--port', String(daemonPort), '--single-port'],
777
772
  },
778
773
  DAEMON_COMMAND_CONTEXT
779
774
  );
@@ -793,20 +788,20 @@ async function maybeAutoDaemonStart(options = {}) {
793
788
  defaultPort: process.env.SERVER_PORT || process.env.PORT || '3001',
794
789
  color: c,
795
790
  });
796
- printUserDaemonActiveNotice(daemonPort, frontendPort);
791
+ printUserDaemonActiveNotice(daemonPort);
797
792
  return true;
798
793
  } catch (userError) {
799
794
  const userHealthySoon = await waitForPortOpen(daemonPort);
800
795
  if (userHealthySoon) {
801
796
  console.log(`${c.warn('[WARN]')} User daemon health check was delayed, but port ${daemonPort} is now reachable.`);
802
- printUserDaemonActiveNotice(daemonPort, frontendPort);
797
+ printUserDaemonActiveNotice(daemonPort);
803
798
  return true;
804
799
  }
805
800
  const installSystemCommand = buildDaemonCliCommand(
806
801
  {
807
802
  subcommand: 'install',
808
803
  mode: 'system',
809
- extraArgs: ['--port', String(daemonPort), '--frontend-port', String(frontendPort)],
804
+ extraArgs: ['--port', String(daemonPort), '--single-port'],
810
805
  },
811
806
  DAEMON_COMMAND_CONTEXT
812
807
  );
@@ -814,7 +809,7 @@ async function maybeAutoDaemonStart(options = {}) {
814
809
  {
815
810
  subcommand: 'install',
816
811
  mode: 'user',
817
- extraArgs: ['--port', String(daemonPort), '--frontend-port', String(frontendPort)],
812
+ extraArgs: ['--port', String(daemonPort), '--single-port'],
818
813
  },
819
814
  DAEMON_COMMAND_CONTEXT
820
815
  );
@@ -187,6 +187,8 @@ function parseDaemonArgs(args) {
187
187
  parsed.options.frontendPort = args[++i];
188
188
  } else if (arg.startsWith('--frontend-port=')) {
189
189
  parsed.options.frontendPort = arg.split('=')[1];
190
+ } else if (arg === '--no-frontend' || arg === '--single-port') {
191
+ parsed.options.noFrontend = true;
190
192
  } else if (arg === '--mode' || arg === '-m') {
191
193
  parsed.options.mode = (args[++i] || '').toLowerCase();
192
194
  } else if (arg.startsWith('--mode=')) {
@@ -512,7 +514,7 @@ function showDaemonHelp(c, context = {}) {
512
514
  {
513
515
  subcommand: 'install',
514
516
  mode: 'system',
515
- extraArgs: ['--port', '3001', '--frontend-port', '5173'],
517
+ extraArgs: ['--port', '3001', '--single-port'],
516
518
  },
517
519
  context
518
520
  );
@@ -540,7 +542,9 @@ Subcommands:
540
542
 
541
543
  Options:
542
544
  -p, --port <port> Set service server port (default: 3001)
543
- --frontend-port <port> Set frontend Vite port (default: 5173)
545
+ --frontend-port <port> Start a separate frontend Vite service on this port (legacy/dev)
546
+ --single-port, --no-frontend
547
+ Serve the built UI from the backend port only (default)
544
548
  -m, --mode <mode> Service mode: user | system | auto (default: system)
545
549
  --database-path <path> Set service database path
546
550
 
@@ -592,6 +596,8 @@ export async function handleDaemonCommand(args, context = {}) {
592
596
  const defaultFrontendPort = context.defaultFrontendPort || process.env.VITE_PORT || String(DEFAULT_FRONTEND_PORT);
593
597
  const configuredPort = parsed.options.serverPort || defaultPort;
594
598
  const configuredFrontendPort = parsed.options.frontendPort || defaultFrontendPort;
599
+ const frontendEnabled = parsed.options.noFrontend !== true &&
600
+ (Boolean(parsed.options.frontendPort) || process.env.PIXCODE_SEPARATE_FRONTEND === '1');
595
601
  const databasePath = parsed.options.databasePath || process.env.DATABASE_PATH || '';
596
602
 
597
603
  const portNum = Number(configuredPort);
@@ -599,7 +605,7 @@ export async function handleDaemonCommand(args, context = {}) {
599
605
  throw new Error(`Invalid port "${configuredPort}". Expected an integer between 1 and 65535.`);
600
606
  }
601
607
  const frontendPortNum = Number(configuredFrontendPort);
602
- if (!Number.isInteger(frontendPortNum) || frontendPortNum < 1 || frontendPortNum > 65535) {
608
+ if (frontendEnabled && (!Number.isInteger(frontendPortNum) || frontendPortNum < 1 || frontendPortNum > 65535)) {
603
609
  throw new Error(`Invalid frontend port "${configuredFrontendPort}". Expected an integer between 1 and 65535.`);
604
610
  }
605
611
 
@@ -632,9 +638,13 @@ export async function handleDaemonCommand(args, context = {}) {
632
638
  const systemUnitInstalled = fs.existsSync(getDaemonServicePath('system'));
633
639
  const systemFrontendUnitInstalled = fs.existsSync(getFrontendServicePath('system'));
634
640
  const selectedModePort = getPortFromServiceUnit(servicePath) || portNum;
635
- const selectedModeFrontendPort = getPortFromServiceUnit(frontendServicePath) || frontendPortNum;
641
+ const selectedModeFrontendPort = frontendEnabled
642
+ ? getPortFromServiceUnit(frontendServicePath) || frontendPortNum
643
+ : undefined;
636
644
  const portReachable = await isPortReachable(selectedModePort);
637
- const frontendPortReachable = await isPortReachable(selectedModeFrontendPort);
645
+ const frontendPortReachable = selectedModeFrontendPort
646
+ ? await isPortReachable(selectedModeFrontendPort)
647
+ : false;
638
648
  const userState = userBus.ok
639
649
  ? {
640
650
  backend: getServiceState('user', DAEMON_SERVICE_NAME),
@@ -667,7 +677,10 @@ export async function handleDaemonCommand(args, context = {}) {
667
677
  console.log(`${c.info('[INFO]')} user state: backend active=${c.bright(userState.backend.active)} enabled=${c.bright(userState.backend.enabled)} | frontend active=${c.bright(userState.frontend.active)} enabled=${c.bright(userState.frontend.enabled)}`);
668
678
  console.log(`${c.info('[INFO]')} system state: backend active=${c.bright(systemState.backend.active)} enabled=${c.bright(systemState.backend.enabled)} | frontend active=${c.bright(systemState.frontend.active)} enabled=${c.bright(systemState.frontend.enabled)}`);
669
679
  console.log(`${c.info('[INFO]')} backend port: ${c.bright(String(selectedModePort))} (${portReachable ? c.ok('reachable') : c.warn('not reachable')})`);
670
- console.log(`${c.info('[INFO]')} frontend port: ${c.bright(String(selectedModeFrontendPort))} (${frontendPortReachable ? c.ok('reachable') : c.warn('not reachable')})`);
680
+ console.log(`${c.info('[INFO]')} frontend mode: ${frontendEnabled ? c.bright('separate Vite service') : c.bright('single backend port')}`);
681
+ if (frontendEnabled) {
682
+ console.log(`${c.info('[INFO]')} frontend port: ${c.bright(String(selectedModeFrontendPort))} (${frontendPortReachable ? c.ok('reachable') : c.warn('not reachable')})`);
683
+ }
671
684
  if (lastErrorLine) {
672
685
  console.log(`${c.warn('[WARN]')} Latest error: ${lastErrorLine}`);
673
686
  }
@@ -691,20 +704,17 @@ export async function handleDaemonCommand(args, context = {}) {
691
704
  console.log(`SYSTEM_FRONTEND_ENABLED=${systemState.frontend.enabled}`);
692
705
  console.log(`BACKEND_PORT=${selectedModePort}`);
693
706
  console.log(`BACKEND_PORT_REACHABLE=${portReachable}`);
694
- console.log(`FRONTEND_PORT=${selectedModeFrontendPort}`);
707
+ console.log(`FRONTEND_ENABLED=${frontendEnabled}`);
708
+ console.log(`FRONTEND_PORT=${selectedModeFrontendPort ?? ''}`);
695
709
  console.log(`FRONTEND_PORT_REACHABLE=${frontendPortReachable}`);
696
710
  console.log(`LAST_ERROR_LINE=${JSON.stringify(lastErrorLine || '')}\n`);
697
711
  return;
698
712
  }
699
713
 
700
- const serviceDefs = [
701
- {
702
- servicePath,
703
- },
704
- {
705
- servicePath: frontendServicePath,
706
- },
707
- ];
714
+ const serviceDefs = [{ servicePath }];
715
+ if (frontendEnabled) {
716
+ serviceDefs.push({ servicePath: frontendServicePath });
717
+ }
708
718
 
709
719
  switch (parsed.subcommand) {
710
720
  case 'install': {
@@ -714,7 +724,9 @@ export async function handleDaemonCommand(args, context = {}) {
714
724
 
715
725
  try {
716
726
  fs.mkdirSync(path.dirname(servicePath), { recursive: true });
717
- fs.mkdirSync(path.dirname(frontendServicePath), { recursive: true });
727
+ if (frontendEnabled) {
728
+ fs.mkdirSync(path.dirname(frontendServicePath), { recursive: true });
729
+ }
718
730
 
719
731
  const backendUnitContent = buildDaemonServiceUnit({
720
732
  appRoot,
@@ -725,20 +737,24 @@ export async function handleDaemonCommand(args, context = {}) {
725
737
  });
726
738
  fs.writeFileSync(servicePath, backendUnitContent, 'utf8');
727
739
 
728
- const frontendUnitContent = buildFrontendDaemonServiceUnit({
729
- appRoot,
730
- frontendPort: frontendPortNum,
731
- nodeExecPath: context.nodeExecPath,
732
- cliEntry: context.cliEntry,
733
- });
734
- fs.writeFileSync(frontendServicePath, frontendUnitContent, 'utf8');
740
+ if (frontendEnabled) {
741
+ const frontendUnitContent = buildFrontendDaemonServiceUnit({
742
+ appRoot,
743
+ frontendPort: frontendPortNum,
744
+ nodeExecPath: context.nodeExecPath,
745
+ cliEntry: context.cliEntry,
746
+ });
747
+ fs.writeFileSync(frontendServicePath, frontendUnitContent, 'utf8');
748
+ }
735
749
  } catch (fileError) {
736
750
  if (mode === 'system' && (fileError.code === 'EACCES' || fileError.code === 'EPERM')) {
737
751
  const installHint = buildDaemonCliCommand(
738
752
  {
739
753
  subcommand: 'install',
740
754
  mode: 'system',
741
- extraArgs: ['--port', String(portNum), '--frontend-port', String(frontendPortNum)],
755
+ extraArgs: frontendEnabled
756
+ ? ['--port', String(portNum), '--frontend-port', String(frontendPortNum)]
757
+ : ['--port', String(portNum), '--single-port'],
742
758
  },
743
759
  daemonCommandContext
744
760
  );
@@ -752,7 +768,12 @@ export async function handleDaemonCommand(args, context = {}) {
752
768
 
753
769
  runSystemctl(mode, ['daemon-reload']);
754
770
  runSystemctl(mode, ['enable', '--now', DAEMON_SERVICE_NAME]);
755
- runSystemctl(mode, ['enable', '--now', FRONTEND_DAEMON_SERVICE_NAME]);
771
+ if (frontendEnabled) {
772
+ runSystemctl(mode, ['enable', '--now', FRONTEND_DAEMON_SERVICE_NAME]);
773
+ } else {
774
+ runSystemctl(mode, ['stop', FRONTEND_DAEMON_SERVICE_NAME], { allowFailure: true });
775
+ runSystemctl(mode, ['disable', FRONTEND_DAEMON_SERVICE_NAME], { allowFailure: true });
776
+ }
756
777
 
757
778
  if (mode === 'user') {
758
779
  const lingerResult = runCommand('loginctl', ['enable-linger', os.userInfo().username]);
@@ -764,22 +785,32 @@ export async function handleDaemonCommand(args, context = {}) {
764
785
  }
765
786
 
766
787
  const installedPort = getPortFromServiceUnit(servicePath) || portNum;
767
- const installedFrontendPort = getPortFromServiceUnit(frontendServicePath) || frontendPortNum;
768
788
  await healthCheckOrThrow(mode, DAEMON_SERVICE_NAME, installedPort, c);
769
- await healthCheckOrThrow(mode, FRONTEND_DAEMON_SERVICE_NAME, installedFrontendPort, c);
789
+ const installedFrontendPort = frontendEnabled
790
+ ? getPortFromServiceUnit(frontendServicePath) || frontendPortNum
791
+ : undefined;
792
+ if (frontendEnabled && installedFrontendPort) {
793
+ await healthCheckOrThrow(mode, FRONTEND_DAEMON_SERVICE_NAME, installedFrontendPort, c);
794
+ }
770
795
 
771
796
  const backendState = getServiceState(mode, DAEMON_SERVICE_NAME);
772
- const frontendState = getServiceState(mode, FRONTEND_DAEMON_SERVICE_NAME);
797
+ const frontendState = frontendEnabled
798
+ ? getServiceState(mode, FRONTEND_DAEMON_SERVICE_NAME)
799
+ : { active: 'disabled', enabled: 'disabled' };
773
800
  console.log(`\n${c.ok('✔')} Daemon installed and started.`);
774
801
  console.log(` Mode: ${c.bright(mode)}`);
775
802
  console.log(` Backend Unit: ${c.dim(servicePath)}`);
776
- console.log(` Frontend Unit: ${c.dim(frontendServicePath)}`);
803
+ console.log(` Frontend Mode: ${c.bright(frontendEnabled ? 'separate service' : 'single backend port')}`);
777
804
  console.log(` Backend Active: ${c.bright(backendState.active)}`);
778
805
  console.log(` Backend Enabled:${c.bright(backendState.enabled)}`);
779
- console.log(` Frontend Active:${c.bright(frontendState.active)}`);
780
- console.log(` Frontend Enabled:${c.bright(frontendState.enabled)}`);
781
806
  console.log(` Backend URL: ${c.bright(`http://localhost:${installedPort}`)}`);
782
- console.log(` Frontend URL: ${c.bright(`http://localhost:${installedFrontendPort}`)}\n`);
807
+ if (frontendEnabled) {
808
+ console.log(` Frontend Unit: ${c.dim(frontendServicePath)}`);
809
+ console.log(` Frontend Active:${c.bright(frontendState.active)}`);
810
+ console.log(` Frontend Enabled:${c.bright(frontendState.enabled)}`);
811
+ console.log(` Frontend URL: ${c.bright(`http://localhost:${installedFrontendPort}`)}`);
812
+ }
813
+ console.log('');
783
814
  if (mode === 'system') {
784
815
  const statusCommand = buildDaemonCliCommand(
785
816
  { subcommand: 'status', mode: 'system' },
@@ -793,9 +824,9 @@ export async function handleDaemonCommand(args, context = {}) {
793
824
  { subcommand: 'logs', mode: 'system' },
794
825
  daemonCommandContext
795
826
  );
796
- console.log(`${c.ok('[OK]')} System daemon is active for backend and frontend.`);
827
+ console.log(`${c.ok('[OK]')} System daemon is active.`);
797
828
  console.log(`${c.info('[INFO]')} Backend health: ${c.bright(`http://localhost:${installedPort}/health`)}`);
798
- console.log(`${c.info('[INFO]')} Frontend: ${c.bright(`http://localhost:${installedFrontendPort}/`)}`);
829
+ console.log(`${c.info('[INFO]')} UI: ${c.bright(`http://localhost:${installedPort}/`)}`);
799
830
  console.log(`${c.info('[INFO]')} Status: ${c.bright(statusCommand)}`);
800
831
  console.log(`${c.info('[INFO]')} Stop: ${c.bright(stopCommand)}`);
801
832
  console.log(`${c.info('[INFO]')} Logs: ${c.bright(logsCommand)}\n`);
@@ -813,9 +844,9 @@ export async function handleDaemonCommand(args, context = {}) {
813
844
  { subcommand: 'logs', mode: 'user' },
814
845
  daemonCommandContext
815
846
  );
816
- console.log(`${c.ok('[OK]')} User daemon is active for backend and frontend.`);
847
+ console.log(`${c.ok('[OK]')} User daemon is active.`);
817
848
  console.log(`${c.info('[INFO]')} Backend health: ${c.bright(`http://localhost:${installedPort}/health`)}`);
818
- console.log(`${c.info('[INFO]')} Frontend: ${c.bright(`http://localhost:${installedFrontendPort}/`)}`);
849
+ console.log(`${c.info('[INFO]')} UI: ${c.bright(`http://localhost:${installedPort}/`)}`);
819
850
  console.log(`${c.info('[INFO]')} Status: ${c.bright(statusCommand)}`);
820
851
  console.log(`${c.info('[INFO]')} Stop: ${c.bright(stopCommand)}`);
821
852
  console.log(`${c.info('[INFO]')} Logs: ${c.bright(logsCommand)}`);
@@ -830,36 +861,36 @@ export async function handleDaemonCommand(args, context = {}) {
830
861
 
831
862
  case 'start':
832
863
  runSystemctl(mode, ['start', DAEMON_SERVICE_NAME]);
833
- runSystemctl(mode, ['start', FRONTEND_DAEMON_SERVICE_NAME]);
864
+ if (frontendEnabled) runSystemctl(mode, ['start', FRONTEND_DAEMON_SERVICE_NAME]);
834
865
  await healthCheckOrThrow(mode, DAEMON_SERVICE_NAME, effectivePort, c);
835
- await healthCheckOrThrow(mode, FRONTEND_DAEMON_SERVICE_NAME, effectiveFrontendPort, c);
836
- console.log(`${c.ok('[OK]')} Backend and frontend services started.`);
866
+ if (frontendEnabled) await healthCheckOrThrow(mode, FRONTEND_DAEMON_SERVICE_NAME, effectiveFrontendPort, c);
867
+ console.log(`${c.ok('[OK]')} Pixcode service started.`);
837
868
  break;
838
869
 
839
870
  case 'stop':
840
871
  runSystemctl(mode, ['stop', FRONTEND_DAEMON_SERVICE_NAME], { allowFailure: true });
841
872
  runSystemctl(mode, ['stop', DAEMON_SERVICE_NAME]);
842
- console.log(`${c.ok('[OK]')} Backend and frontend services stopped (auto-start remains enabled).`);
873
+ console.log(`${c.ok('[OK]')} Pixcode service stopped (auto-start remains enabled).`);
843
874
  break;
844
875
 
845
876
  case 'restart':
846
877
  runSystemctl(mode, ['restart', DAEMON_SERVICE_NAME]);
847
- runSystemctl(mode, ['restart', FRONTEND_DAEMON_SERVICE_NAME]);
878
+ if (frontendEnabled) runSystemctl(mode, ['restart', FRONTEND_DAEMON_SERVICE_NAME]);
848
879
  await healthCheckOrThrow(mode, DAEMON_SERVICE_NAME, effectivePort, c);
849
- await healthCheckOrThrow(mode, FRONTEND_DAEMON_SERVICE_NAME, effectiveFrontendPort, c);
850
- console.log(`${c.ok('[OK]')} Backend and frontend services restarted.`);
880
+ if (frontendEnabled) await healthCheckOrThrow(mode, FRONTEND_DAEMON_SERVICE_NAME, effectiveFrontendPort, c);
881
+ console.log(`${c.ok('[OK]')} Pixcode service restarted.`);
851
882
  break;
852
883
 
853
884
  case 'enable':
854
885
  runSystemctl(mode, ['enable', DAEMON_SERVICE_NAME]);
855
- runSystemctl(mode, ['enable', FRONTEND_DAEMON_SERVICE_NAME]);
856
- console.log(`${c.ok('[OK]')} Backend and frontend services enabled for auto-start.`);
886
+ if (frontendEnabled) runSystemctl(mode, ['enable', FRONTEND_DAEMON_SERVICE_NAME]);
887
+ console.log(`${c.ok('[OK]')} Pixcode service enabled for auto-start.`);
857
888
  break;
858
889
 
859
890
  case 'disable':
860
891
  runSystemctl(mode, ['disable', DAEMON_SERVICE_NAME]);
861
892
  runSystemctl(mode, ['disable', FRONTEND_DAEMON_SERVICE_NAME]);
862
- console.log(`${c.ok('[OK]')} Backend and frontend services disabled for auto-start.`);
893
+ console.log(`${c.ok('[OK]')} Pixcode service disabled for auto-start.`);
863
894
  break;
864
895
 
865
896
  case 'logs': {
@@ -903,17 +934,25 @@ export async function handleDaemonCommand(args, context = {}) {
903
934
  const backendUnitExists = fs.existsSync(servicePath);
904
935
  const frontendUnitExists = fs.existsSync(frontendServicePath);
905
936
  const selectedPort = getPortFromServiceUnit(servicePath) || portNum;
906
- const selectedFrontendPort = getPortFromServiceUnit(frontendServicePath) || frontendPortNum;
937
+ const selectedFrontendPort = frontendEnabled
938
+ ? getPortFromServiceUnit(frontendServicePath) || frontendPortNum
939
+ : undefined;
907
940
  console.log(`\n${c.bright('Pixcode Daemon Status')}\n`);
908
941
  console.log(`${c.info('[INFO]')} Mode: ${c.bright(mode)} ${parsed.options.mode === 'auto' ? c.dim('(resolved from auto)') : ''}`);
909
942
  console.log(`${c.info('[INFO]')} Backend Unit: ${c.dim(servicePath)} ${backendUnitExists ? c.ok('[OK]') : c.warn('[MISSING]')}`);
910
943
  console.log(`${c.info('[INFO]')} Backend Active: ${c.bright(backendState.active)}`);
911
944
  console.log(`${c.info('[INFO]')} Backend Enabled:${c.bright(backendState.enabled)}`);
912
945
  console.log(`${c.info('[INFO]')} Backend Port: ${c.bright(String(selectedPort))}`);
913
- console.log(`${c.info('[INFO]')} Frontend Unit: ${c.dim(frontendServicePath)} ${frontendUnitExists ? c.ok('[OK]') : c.warn('[MISSING]')}`);
914
- console.log(`${c.info('[INFO]')} Frontend Active:${c.bright(frontendState.active)}`);
915
- console.log(`${c.info('[INFO]')} Frontend Enabled:${c.bright(frontendState.enabled)}`);
916
- console.log(`${c.info('[INFO]')} Frontend Port: ${c.bright(String(selectedFrontendPort))}`);
946
+ console.log(`${c.info('[INFO]')} UI URL: ${c.bright(`http://localhost:${selectedPort}/`)}`);
947
+ console.log(`${c.info('[INFO]')} Frontend Mode: ${frontendEnabled ? c.bright('separate Vite service') : c.bright('single backend port')}`);
948
+ if (frontendEnabled) {
949
+ console.log(`${c.info('[INFO]')} Frontend Unit: ${c.dim(frontendServicePath)} ${frontendUnitExists ? c.ok('[OK]') : c.warn('[MISSING]')}`);
950
+ console.log(`${c.info('[INFO]')} Frontend Active:${c.bright(frontendState.active)}`);
951
+ console.log(`${c.info('[INFO]')} Frontend Enabled:${c.bright(frontendState.enabled)}`);
952
+ console.log(`${c.info('[INFO]')} Frontend Port: ${c.bright(String(selectedFrontendPort))}`);
953
+ } else if (frontendUnitExists) {
954
+ console.log(`${c.info('[INFO]')} Legacy Frontend:${c.dim(frontendServicePath)} active=${c.bright(frontendState.active)} enabled=${c.bright(frontendState.enabled)}`);
955
+ }
917
956
  console.log('');
918
957
  }
919
958
  }