@pixelbyte-software/pixcode 1.35.0 → 1.35.1

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 (150) 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 +303 -303
  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 +548 -548
  10. package/dist/assets/{index-Djuh0wHV.js → index-CBdsvGSR.js} +133 -133
  11. package/dist/clear-cache.html +85 -85
  12. package/dist/convert-icons.md +52 -52
  13. package/dist/generate-icons.js +48 -48
  14. package/dist/icons/codex-white.svg +3 -3
  15. package/dist/icons/codex.svg +3 -3
  16. package/dist/icons/cursor-white.svg +11 -11
  17. package/dist/icons/qwen-logo.svg +14 -14
  18. package/dist/index.html +58 -58
  19. package/dist/manifest.json +60 -60
  20. package/dist/openapi.yaml +1693 -1693
  21. package/dist/sw.js +124 -124
  22. package/dist-server/server/cli.js +96 -96
  23. package/dist-server/server/daemon/manager.js +33 -33
  24. package/dist-server/server/daemon-manager.js +64 -64
  25. package/dist-server/server/modules/orchestration/preview/preview-proxy.js +3 -3
  26. package/dist-server/server/modules/orchestration/preview/preview-proxy.js.map +1 -1
  27. package/dist-server/server/routes/commands.js +25 -25
  28. package/dist-server/server/routes/git.js +17 -17
  29. package/dist-server/server/routes/taskmaster.js +419 -419
  30. package/package.json +180 -180
  31. package/scripts/fix-node-pty.js +67 -67
  32. package/scripts/smoke/a2a-roundtrip.mjs +167 -167
  33. package/scripts/smoke/orchestration-api.mjs +172 -172
  34. package/scripts/smoke/orchestration-live-run.mjs +176 -176
  35. package/server/claude-sdk.js +898 -898
  36. package/server/cli.js +935 -935
  37. package/server/constants/config.js +4 -4
  38. package/server/cursor-cli.js +342 -342
  39. package/server/daemon/manager.js +564 -564
  40. package/server/daemon-manager.js +959 -959
  41. package/server/database/json-store.js +197 -197
  42. package/server/gemini-cli.js +535 -535
  43. package/server/gemini-response-handler.js +79 -79
  44. package/server/index.js +3135 -3135
  45. package/server/load-env.js +34 -34
  46. package/server/middleware/auth.js +173 -173
  47. package/server/modules/orchestration/a2a/adapter-registry.ts +108 -108
  48. package/server/modules/orchestration/a2a/adapters/abstract-a2a.adapter.ts +55 -55
  49. package/server/modules/orchestration/a2a/adapters/claude-code.adapter.ts +284 -284
  50. package/server/modules/orchestration/a2a/adapters/codex.adapter.ts +244 -244
  51. package/server/modules/orchestration/a2a/adapters/cursor.adapter.ts +249 -249
  52. package/server/modules/orchestration/a2a/adapters/gemini.adapter.ts +248 -248
  53. package/server/modules/orchestration/a2a/adapters/opencode.adapter.ts +248 -248
  54. package/server/modules/orchestration/a2a/adapters/qwen.adapter.ts +248 -248
  55. package/server/modules/orchestration/a2a/agent-card.ts +55 -55
  56. package/server/modules/orchestration/a2a/auth.middleware.ts +29 -29
  57. package/server/modules/orchestration/a2a/bus.ts +46 -46
  58. package/server/modules/orchestration/a2a/routes.ts +577 -577
  59. package/server/modules/orchestration/a2a/task-store.ts +178 -178
  60. package/server/modules/orchestration/a2a/types.ts +125 -125
  61. package/server/modules/orchestration/a2a/validator.ts +113 -113
  62. package/server/modules/orchestration/index.ts +66 -66
  63. package/server/modules/orchestration/preview/port-watcher.ts +112 -112
  64. package/server/modules/orchestration/preview/preview-proxy.ts +60 -60
  65. package/server/modules/orchestration/preview/types.ts +19 -19
  66. package/server/modules/orchestration/tasks/orchestration-task-store.ts +45 -45
  67. package/server/modules/orchestration/tasks/orchestration-task.routes.ts +73 -73
  68. package/server/modules/orchestration/tasks/orchestration-task.service.ts +145 -145
  69. package/server/modules/orchestration/tasks/orchestration-task.types.ts +29 -29
  70. package/server/modules/orchestration/workflows/built-in-workflows.ts +127 -127
  71. package/server/modules/orchestration/workflows/workflow-runner.ts +1206 -1206
  72. package/server/modules/orchestration/workflows/workflow-store.ts +97 -97
  73. package/server/modules/orchestration/workflows/workflow.routes.ts +169 -169
  74. package/server/modules/orchestration/workflows/workflow.types.ts +70 -70
  75. package/server/modules/orchestration/workflows/workspace-target.ts +120 -120
  76. package/server/modules/orchestration/workspace/docker-workspace.ts +135 -135
  77. package/server/modules/orchestration/workspace/path-safety.ts +55 -55
  78. package/server/modules/orchestration/workspace/types.ts +52 -52
  79. package/server/modules/orchestration/workspace/workspace-manager.ts +97 -97
  80. package/server/modules/orchestration/workspace/worktree-workspace.ts +125 -125
  81. package/server/modules/providers/index.ts +2 -2
  82. package/server/modules/providers/list/claude/claude-auth.provider.ts +145 -145
  83. package/server/modules/providers/list/claude/claude-mcp.provider.ts +135 -135
  84. package/server/modules/providers/list/claude/claude-sessions.provider.ts +306 -306
  85. package/server/modules/providers/list/claude/claude.provider.ts +15 -15
  86. package/server/modules/providers/list/codex/codex-auth.provider.ts +115 -115
  87. package/server/modules/providers/list/codex/codex-mcp.provider.ts +135 -135
  88. package/server/modules/providers/list/codex/codex-sessions.provider.ts +319 -319
  89. package/server/modules/providers/list/codex/codex.provider.ts +15 -15
  90. package/server/modules/providers/list/cursor/cursor-auth.provider.ts +143 -143
  91. package/server/modules/providers/list/cursor/cursor-mcp.provider.ts +108 -108
  92. package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +421 -421
  93. package/server/modules/providers/list/cursor/cursor.provider.ts +15 -15
  94. package/server/modules/providers/list/gemini/gemini-auth.provider.ts +163 -163
  95. package/server/modules/providers/list/gemini/gemini-mcp.provider.ts +110 -110
  96. package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +227 -227
  97. package/server/modules/providers/list/gemini/gemini.provider.ts +15 -15
  98. package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +232 -232
  99. package/server/modules/providers/list/qwen/qwen-sessions.provider.ts +265 -265
  100. package/server/modules/providers/provider.registry.ts +40 -40
  101. package/server/modules/providers/provider.routes.ts +819 -819
  102. package/server/modules/providers/services/mcp.service.ts +86 -86
  103. package/server/modules/providers/services/provider-auth.service.ts +26 -26
  104. package/server/modules/providers/services/sessions.service.ts +45 -45
  105. package/server/modules/providers/shared/base/abstract.provider.ts +20 -20
  106. package/server/modules/providers/shared/mcp/mcp.provider.ts +151 -151
  107. package/server/modules/providers/tests/mcp.test.ts +293 -293
  108. package/server/openai-codex.js +462 -462
  109. package/server/opencode-cli.js +459 -459
  110. package/server/opencode-response-handler.js +107 -107
  111. package/server/projects.js +3105 -3105
  112. package/server/routes/agent.js +1365 -1365
  113. package/server/routes/auth.js +138 -138
  114. package/server/routes/codex.js +19 -19
  115. package/server/routes/commands.js +554 -554
  116. package/server/routes/cursor.js +52 -52
  117. package/server/routes/gemini.js +24 -24
  118. package/server/routes/git.js +1488 -1488
  119. package/server/routes/mcp-utils.js +31 -31
  120. package/server/routes/messages.js +61 -61
  121. package/server/routes/network.js +120 -120
  122. package/server/routes/plugins.js +318 -318
  123. package/server/routes/projects.js +915 -915
  124. package/server/routes/settings.js +286 -286
  125. package/server/routes/taskmaster.js +1496 -1496
  126. package/server/routes/telegram.js +125 -125
  127. package/server/routes/user.js +123 -123
  128. package/server/services/install-jobs.js +571 -571
  129. package/server/services/notification-orchestrator.js +242 -242
  130. package/server/services/provider-credentials.js +189 -189
  131. package/server/services/telegram/bot.js +279 -279
  132. package/server/services/telegram/translations.js +170 -170
  133. package/server/sessionManager.js +225 -225
  134. package/server/shared/interfaces.ts +54 -54
  135. package/server/shared/types.ts +172 -172
  136. package/server/shared/utils.ts +193 -193
  137. package/server/tsconfig.json +36 -36
  138. package/server/utils/colors.js +21 -21
  139. package/server/utils/commandParser.js +303 -303
  140. package/server/utils/frontmatter.js +18 -18
  141. package/server/utils/gitConfig.js +34 -34
  142. package/server/utils/mcp-detector.js +147 -147
  143. package/server/utils/plugin-loader.js +457 -457
  144. package/server/utils/plugin-process-manager.js +184 -184
  145. package/server/utils/runtime-paths.js +37 -37
  146. package/server/utils/taskmaster-websocket.js +128 -128
  147. package/server/utils/url-detection.js +71 -71
  148. package/server/vite-daemon.js +78 -78
  149. package/shared/modelConstants.js +162 -162
  150. package/shared/networkHosts.js +22 -22
@@ -1,31 +1,31 @@
1
- /**
2
- * MCP UTILITIES API ROUTES
3
- * ========================
4
- *
5
- * API endpoints for MCP server detection and configuration utilities.
6
- * These endpoints expose centralized MCP detection functionality.
7
- */
8
-
9
- import express from 'express';
10
- import { detectTaskMasterMCPServer } from '../utils/mcp-detector.js';
11
-
12
- const router = express.Router();
13
-
14
- /**
15
- * GET /api/mcp-utils/taskmaster-server
16
- * Check if TaskMaster MCP server is configured
17
- */
18
- router.get('/taskmaster-server', async (req, res) => {
19
- try {
20
- const result = await detectTaskMasterMCPServer();
21
- res.json(result);
22
- } catch (error) {
23
- console.error('TaskMaster MCP detection error:', error);
24
- res.status(500).json({
25
- error: 'Failed to detect TaskMaster MCP server',
26
- message: error.message
27
- });
28
- }
29
- });
30
-
31
- export default router;
1
+ /**
2
+ * MCP UTILITIES API ROUTES
3
+ * ========================
4
+ *
5
+ * API endpoints for MCP server detection and configuration utilities.
6
+ * These endpoints expose centralized MCP detection functionality.
7
+ */
8
+
9
+ import express from 'express';
10
+ import { detectTaskMasterMCPServer } from '../utils/mcp-detector.js';
11
+
12
+ const router = express.Router();
13
+
14
+ /**
15
+ * GET /api/mcp-utils/taskmaster-server
16
+ * Check if TaskMaster MCP server is configured
17
+ */
18
+ router.get('/taskmaster-server', async (req, res) => {
19
+ try {
20
+ const result = await detectTaskMasterMCPServer();
21
+ res.json(result);
22
+ } catch (error) {
23
+ console.error('TaskMaster MCP detection error:', error);
24
+ res.status(500).json({
25
+ error: 'Failed to detect TaskMaster MCP server',
26
+ message: error.message
27
+ });
28
+ }
29
+ });
30
+
31
+ export default router;
@@ -1,61 +1,61 @@
1
- /**
2
- * Unified messages endpoint.
3
- *
4
- * GET /api/sessions/:sessionId/messages?provider=claude&projectName=foo&limit=50&offset=0
5
- *
6
- * Replaces the four provider-specific session message endpoints with a single route
7
- * that delegates to the appropriate adapter via the provider registry.
8
- *
9
- * @module routes/messages
10
- */
11
-
12
- import express from 'express';
13
- import { sessionsService } from '../modules/providers/services/sessions.service.js';
14
-
15
- const router = express.Router();
16
-
17
- /**
18
- * GET /api/sessions/:sessionId/messages
19
- *
20
- * Auth: authenticateToken applied at mount level in index.js
21
- *
22
- * Query params:
23
- * provider - 'claude' | 'cursor' | 'codex' | 'gemini' (default: 'claude')
24
- * projectName - required for claude provider
25
- * projectPath - required for cursor provider (absolute path used for cwdId hash)
26
- * limit - page size (omit or null for all)
27
- * offset - pagination offset (default: 0)
28
- */
29
- router.get('/:sessionId/messages', async (req, res) => {
30
- try {
31
- const { sessionId } = req.params;
32
- const provider = String(req.query.provider || 'claude').trim().toLowerCase();
33
- const projectName = req.query.projectName || '';
34
- const projectPath = req.query.projectPath || '';
35
- const limitParam = req.query.limit;
36
- const limit = limitParam !== undefined && limitParam !== null && limitParam !== ''
37
- ? parseInt(limitParam, 10)
38
- : null;
39
- const offset = parseInt(req.query.offset || '0', 10);
40
-
41
- const availableProviders = sessionsService.listProviderIds();
42
- if (!availableProviders.includes(provider)) {
43
- const available = availableProviders.join(', ');
44
- return res.status(400).json({ error: `Unknown provider: ${provider}. Available: ${available}` });
45
- }
46
-
47
- const result = await sessionsService.fetchHistory(provider, sessionId, {
48
- projectName,
49
- projectPath,
50
- limit,
51
- offset,
52
- });
53
-
54
- return res.json(result);
55
- } catch (error) {
56
- console.error('Error fetching unified messages:', error);
57
- return res.status(500).json({ error: 'Failed to fetch messages' });
58
- }
59
- });
60
-
61
- export default router;
1
+ /**
2
+ * Unified messages endpoint.
3
+ *
4
+ * GET /api/sessions/:sessionId/messages?provider=claude&projectName=foo&limit=50&offset=0
5
+ *
6
+ * Replaces the four provider-specific session message endpoints with a single route
7
+ * that delegates to the appropriate adapter via the provider registry.
8
+ *
9
+ * @module routes/messages
10
+ */
11
+
12
+ import express from 'express';
13
+ import { sessionsService } from '../modules/providers/services/sessions.service.js';
14
+
15
+ const router = express.Router();
16
+
17
+ /**
18
+ * GET /api/sessions/:sessionId/messages
19
+ *
20
+ * Auth: authenticateToken applied at mount level in index.js
21
+ *
22
+ * Query params:
23
+ * provider - 'claude' | 'cursor' | 'codex' | 'gemini' (default: 'claude')
24
+ * projectName - required for claude provider
25
+ * projectPath - required for cursor provider (absolute path used for cwdId hash)
26
+ * limit - page size (omit or null for all)
27
+ * offset - pagination offset (default: 0)
28
+ */
29
+ router.get('/:sessionId/messages', async (req, res) => {
30
+ try {
31
+ const { sessionId } = req.params;
32
+ const provider = String(req.query.provider || 'claude').trim().toLowerCase();
33
+ const projectName = req.query.projectName || '';
34
+ const projectPath = req.query.projectPath || '';
35
+ const limitParam = req.query.limit;
36
+ const limit = limitParam !== undefined && limitParam !== null && limitParam !== ''
37
+ ? parseInt(limitParam, 10)
38
+ : null;
39
+ const offset = parseInt(req.query.offset || '0', 10);
40
+
41
+ const availableProviders = sessionsService.listProviderIds();
42
+ if (!availableProviders.includes(provider)) {
43
+ const available = availableProviders.join(', ');
44
+ return res.status(400).json({ error: `Unknown provider: ${provider}. Available: ${available}` });
45
+ }
46
+
47
+ const result = await sessionsService.fetchHistory(provider, sessionId, {
48
+ projectName,
49
+ projectPath,
50
+ limit,
51
+ offset,
52
+ });
53
+
54
+ return res.json(result);
55
+ } catch (error) {
56
+ console.error('Error fetching unified messages:', error);
57
+ return res.status(500).json({ error: 'Failed to fetch messages' });
58
+ }
59
+ });
60
+
61
+ export default router;
@@ -1,120 +1,120 @@
1
- import express from 'express';
2
- import os from 'os';
3
-
4
- import {
5
- getTunnelState,
6
- getUpnpState,
7
- startTunnel,
8
- stopTunnel,
9
- } from '../services/external-access.js';
10
-
11
- const router = express.Router();
12
-
13
- // Accept any env key the rest of the server honors. Keep this aligned with server/cli.js.
14
- const resolveServerPort = () => {
15
- const raw = process.env.SERVER_PORT || process.env.PORT || '3001';
16
- const port = Number(raw);
17
- return Number.isFinite(port) && port > 0 ? port : 3001;
18
- };
19
-
20
- // Filter to usable LAN addresses. We skip:
21
- // - internal (loopback) addresses, since QR-ing "127.0.0.1" is useless for a phone
22
- // - link-local IPv6, since pasting "fe80::…%iface" into a phone browser won't resolve
23
- // - IPv6 addresses in general for the first pass (most users want the v4 QR)
24
- const listLanEndpoints = () => {
25
- const interfaces = os.networkInterfaces();
26
- const endpoints = [];
27
-
28
- for (const [ifaceName, addrs] of Object.entries(interfaces)) {
29
- if (!addrs) continue;
30
- for (const addr of addrs) {
31
- if (addr.internal) continue;
32
- if (addr.family !== 'IPv4' && addr.family !== 4) continue;
33
- endpoints.push({
34
- host: addr.address,
35
- label: ifaceName,
36
- family: 'IPv4',
37
- });
38
- }
39
- }
40
-
41
- return endpoints;
42
- };
43
-
44
- router.get('/endpoints', (req, res) => {
45
- try {
46
- const port = resolveServerPort();
47
- const endpoints = listLanEndpoints().map((entry) => ({
48
- ...entry,
49
- url: `http://${entry.host}:${port}`,
50
- }));
51
-
52
- res.json({
53
- port,
54
- hostname: os.hostname(),
55
- endpoints,
56
- });
57
- } catch (error) {
58
- console.error('Error listing network endpoints:', error);
59
- res.status(500).json({ error: 'Failed to list network endpoints' });
60
- }
61
- });
62
-
63
- // ============================================================================
64
- // External access (UPnP + tunnel)
65
- // ============================================================================
66
-
67
- router.get('/external', (req, res) => {
68
- try {
69
- res.json({
70
- upnp: getUpnpState(),
71
- tunnel: getTunnelState(),
72
- });
73
- } catch (error) {
74
- console.error('Error reading external-access state:', error);
75
- res.status(500).json({ error: 'Failed to read external access state' });
76
- }
77
- });
78
-
79
- // UPnP endpoints removed in v1.32 (see external-access.js for rationale).
80
- // Clients hitting /upnp get a 410 so the UI can show a clear "moved to
81
- // tunnel" hint without mistaking the absence for a transient 404.
82
- router.post('/upnp', (_req, res) => {
83
- res.status(410).json({
84
- error: 'UPnP removed in v1.32 — use cloudflared or ngrok tunnels instead',
85
- upnp: getUpnpState(),
86
- });
87
- });
88
- router.delete('/upnp', (_req, res) => {
89
- res.status(410).json({
90
- error: 'UPnP removed in v1.32 — use cloudflared or ngrok tunnels instead',
91
- upnp: getUpnpState(),
92
- });
93
- });
94
-
95
- router.post('/tunnel', async (req, res) => {
96
- const port = resolveServerPort();
97
- try {
98
- const state = await startTunnel({ port });
99
- res.json({ success: true, tunnel: state });
100
- } catch (error) {
101
- console.error('Tunnel start failed:', error);
102
- // 424 Failed Dependency is the best match for "we can't start because a
103
- // required external binary is missing" — it tells the UI to show the
104
- // "install cloudflared/ngrok" hint rather than a generic server error.
105
- const status = error?.code === 'ENOENT_TUNNEL' ? 424 : 502;
106
- res.status(status).json({ error: error?.message || 'Tunnel start failed', tunnel: getTunnelState() });
107
- }
108
- });
109
-
110
- router.delete('/tunnel', async (req, res) => {
111
- try {
112
- const state = await stopTunnel();
113
- res.json({ success: true, tunnel: state });
114
- } catch (error) {
115
- console.error('Tunnel stop failed:', error);
116
- res.status(502).json({ error: error?.message || 'Tunnel stop failed', tunnel: getTunnelState() });
117
- }
118
- });
119
-
120
- export default router;
1
+ import express from 'express';
2
+ import os from 'os';
3
+
4
+ import {
5
+ getTunnelState,
6
+ getUpnpState,
7
+ startTunnel,
8
+ stopTunnel,
9
+ } from '../services/external-access.js';
10
+
11
+ const router = express.Router();
12
+
13
+ // Accept any env key the rest of the server honors. Keep this aligned with server/cli.js.
14
+ const resolveServerPort = () => {
15
+ const raw = process.env.SERVER_PORT || process.env.PORT || '3001';
16
+ const port = Number(raw);
17
+ return Number.isFinite(port) && port > 0 ? port : 3001;
18
+ };
19
+
20
+ // Filter to usable LAN addresses. We skip:
21
+ // - internal (loopback) addresses, since QR-ing "127.0.0.1" is useless for a phone
22
+ // - link-local IPv6, since pasting "fe80::…%iface" into a phone browser won't resolve
23
+ // - IPv6 addresses in general for the first pass (most users want the v4 QR)
24
+ const listLanEndpoints = () => {
25
+ const interfaces = os.networkInterfaces();
26
+ const endpoints = [];
27
+
28
+ for (const [ifaceName, addrs] of Object.entries(interfaces)) {
29
+ if (!addrs) continue;
30
+ for (const addr of addrs) {
31
+ if (addr.internal) continue;
32
+ if (addr.family !== 'IPv4' && addr.family !== 4) continue;
33
+ endpoints.push({
34
+ host: addr.address,
35
+ label: ifaceName,
36
+ family: 'IPv4',
37
+ });
38
+ }
39
+ }
40
+
41
+ return endpoints;
42
+ };
43
+
44
+ router.get('/endpoints', (req, res) => {
45
+ try {
46
+ const port = resolveServerPort();
47
+ const endpoints = listLanEndpoints().map((entry) => ({
48
+ ...entry,
49
+ url: `http://${entry.host}:${port}`,
50
+ }));
51
+
52
+ res.json({
53
+ port,
54
+ hostname: os.hostname(),
55
+ endpoints,
56
+ });
57
+ } catch (error) {
58
+ console.error('Error listing network endpoints:', error);
59
+ res.status(500).json({ error: 'Failed to list network endpoints' });
60
+ }
61
+ });
62
+
63
+ // ============================================================================
64
+ // External access (UPnP + tunnel)
65
+ // ============================================================================
66
+
67
+ router.get('/external', (req, res) => {
68
+ try {
69
+ res.json({
70
+ upnp: getUpnpState(),
71
+ tunnel: getTunnelState(),
72
+ });
73
+ } catch (error) {
74
+ console.error('Error reading external-access state:', error);
75
+ res.status(500).json({ error: 'Failed to read external access state' });
76
+ }
77
+ });
78
+
79
+ // UPnP endpoints removed in v1.32 (see external-access.js for rationale).
80
+ // Clients hitting /upnp get a 410 so the UI can show a clear "moved to
81
+ // tunnel" hint without mistaking the absence for a transient 404.
82
+ router.post('/upnp', (_req, res) => {
83
+ res.status(410).json({
84
+ error: 'UPnP removed in v1.32 — use cloudflared or ngrok tunnels instead',
85
+ upnp: getUpnpState(),
86
+ });
87
+ });
88
+ router.delete('/upnp', (_req, res) => {
89
+ res.status(410).json({
90
+ error: 'UPnP removed in v1.32 — use cloudflared or ngrok tunnels instead',
91
+ upnp: getUpnpState(),
92
+ });
93
+ });
94
+
95
+ router.post('/tunnel', async (req, res) => {
96
+ const port = resolveServerPort();
97
+ try {
98
+ const state = await startTunnel({ port });
99
+ res.json({ success: true, tunnel: state });
100
+ } catch (error) {
101
+ console.error('Tunnel start failed:', error);
102
+ // 424 Failed Dependency is the best match for "we can't start because a
103
+ // required external binary is missing" — it tells the UI to show the
104
+ // "install cloudflared/ngrok" hint rather than a generic server error.
105
+ const status = error?.code === 'ENOENT_TUNNEL' ? 424 : 502;
106
+ res.status(status).json({ error: error?.message || 'Tunnel start failed', tunnel: getTunnelState() });
107
+ }
108
+ });
109
+
110
+ router.delete('/tunnel', async (req, res) => {
111
+ try {
112
+ const state = await stopTunnel();
113
+ res.json({ success: true, tunnel: state });
114
+ } catch (error) {
115
+ console.error('Tunnel stop failed:', error);
116
+ res.status(502).json({ error: error?.message || 'Tunnel stop failed', tunnel: getTunnelState() });
117
+ }
118
+ });
119
+
120
+ export default router;