@pixelbyte-software/pixcode 1.51.2 → 1.51.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CODE_OF_CONDUCT.md +41 -41
- package/CONTRIBUTING.md +155 -155
- package/LICENSE +718 -718
- package/README.de.md +169 -169
- package/README.ja.md +167 -167
- package/README.ko.md +167 -167
- package/README.md +419 -419
- package/README.ru.md +169 -169
- package/README.tr.md +298 -298
- package/README.zh-CN.md +167 -167
- package/SECURITY.md +46 -46
- package/dist/api-automation.html +110 -110
- package/dist/api-docs.html +548 -548
- package/dist/assets/index-B9N-gfOQ.css +32 -0
- package/dist/assets/{index-EN9ngyxf.js → index-HfGHXhD6.js} +175 -175
- package/dist/clear-cache.html +85 -85
- package/dist/convert-icons.md +52 -52
- package/dist/docs.html +308 -308
- package/dist/favicon.svg +8 -8
- package/dist/features.html +133 -133
- package/dist/generate-icons.js +48 -48
- package/dist/humans.txt +15 -15
- package/dist/icons/codex-white.svg +3 -3
- package/dist/icons/codex.svg +3 -3
- package/dist/icons/cursor-white.svg +11 -11
- package/dist/icons/icon-128x128.svg +9 -9
- package/dist/icons/icon-144x144.svg +9 -9
- package/dist/icons/icon-152x152.svg +9 -9
- package/dist/icons/icon-192x192.svg +9 -9
- package/dist/icons/icon-384x384.svg +9 -9
- package/dist/icons/icon-512x512.svg +9 -9
- package/dist/icons/icon-72x72.svg +9 -9
- package/dist/icons/icon-96x96.svg +9 -9
- package/dist/icons/icon-template.svg +9 -9
- package/dist/icons/qwen-logo.svg +14 -14
- package/dist/index.html +59 -59
- package/dist/landing.html +268 -268
- package/dist/llms-full.txt +119 -119
- package/dist/llms.txt +53 -53
- package/dist/logo.svg +12 -12
- package/dist/manifest.json +60 -60
- package/dist/openapi.yaml +1696 -1696
- package/dist/orchestration.html +125 -125
- package/dist/robots.txt +4 -4
- package/dist/site.css +692 -692
- package/dist/sitemap.xml +51 -51
- package/dist/sw.js +132 -132
- package/dist-server/server/cli.js +96 -96
- package/dist-server/server/daemon/manager.js +33 -33
- package/dist-server/server/daemon-manager.js +64 -64
- package/dist-server/server/database/db.js +14 -2
- package/dist-server/server/database/db.js.map +1 -1
- package/dist-server/server/index.js +191 -31
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/middleware/auth.js +16 -5
- package/dist-server/server/middleware/auth.js.map +1 -1
- package/dist-server/server/modules/orchestration/a2a/adapters/json-event.adapter.js +84 -0
- package/dist-server/server/modules/orchestration/a2a/adapters/json-event.adapter.js.map +1 -0
- package/dist-server/server/modules/orchestration/a2a/adapters/json-event.adapter.test.js +43 -0
- package/dist-server/server/modules/orchestration/a2a/adapters/json-event.adapter.test.js.map +1 -0
- package/dist-server/server/modules/orchestration/hermes/hermes.routes.js +55 -1
- package/dist-server/server/modules/orchestration/hermes/hermes.routes.js.map +1 -1
- package/dist-server/server/modules/orchestration/index.js +1 -0
- package/dist-server/server/modules/orchestration/index.js.map +1 -1
- package/dist-server/server/routes/auth.js +12 -5
- package/dist-server/server/routes/auth.js.map +1 -1
- package/dist-server/server/routes/commands.js +25 -25
- package/dist-server/server/routes/git.js +29 -17
- package/dist-server/server/routes/git.js.map +1 -1
- package/dist-server/server/routes/live-view.js +46 -46
- package/dist-server/server/routes/platformization.js +7 -6
- package/dist-server/server/routes/platformization.js.map +1 -1
- package/dist-server/server/services/hermes-gateway.js +310 -0
- package/dist-server/server/services/hermes-gateway.js.map +1 -1
- package/dist-server/server/services/platformization.js +58 -2
- package/dist-server/server/services/platformization.js.map +1 -1
- package/dist-server/server/services/public-api-manifest.js +59 -51
- package/dist-server/server/services/public-api-manifest.js.map +1 -1
- package/package.json +222 -222
- package/scripts/fix-node-pty.js +67 -67
- package/scripts/github/create-v1.38-issues.mjs +351 -351
- package/scripts/github/create-vscode-workbench-issues.mjs +121 -121
- package/scripts/hermes/configure-pixcode-mcp.mjs +165 -163
- package/scripts/hermes/pixcode-mcp-server.mjs +1009 -958
- package/scripts/smoke/changes-panel-layout.mjs +48 -48
- package/scripts/smoke/chat-composer-fixed-layout.mjs +55 -55
- package/scripts/smoke/chat-message-timeline-order.mjs +41 -41
- package/scripts/smoke/chat-realtime-hydration.mjs +44 -44
- package/scripts/smoke/chat-session-provider-pools.mjs +35 -35
- package/scripts/smoke/chat-session-state.mjs +19 -19
- package/scripts/smoke/code-editor-theme.mjs +55 -55
- package/scripts/smoke/code-editor-vscode-engine.mjs +91 -91
- package/scripts/smoke/command-center-agent-writes.mjs +79 -79
- package/scripts/smoke/command-center-non-git.mjs +46 -46
- package/scripts/smoke/context-packet.mjs +43 -43
- package/scripts/smoke/control-room-ux-redesign.mjs +91 -91
- package/scripts/smoke/daemon-entrypoint.mjs +20 -20
- package/scripts/smoke/default-landing-routing.mjs +33 -33
- package/scripts/smoke/desktop-native-notifications.mjs +30 -30
- package/scripts/smoke/desktop-tray-icon.mjs +33 -33
- package/scripts/smoke/discord-release-workflow.mjs +24 -24
- package/scripts/smoke/git-install-update.mjs +255 -255
- package/scripts/smoke/handoff-artifact-protocol.mjs +50 -50
- package/scripts/smoke/hermes-api-install.mjs +56 -56
- package/scripts/smoke/hermes-gateway-persistence.mjs +104 -104
- package/scripts/smoke/hermes-mcp-pixcode-roundtrip.mjs +426 -367
- package/scripts/smoke/hermes-rest-chat-api.mjs +162 -162
- package/scripts/smoke/hermes-rest-chat-live.mjs +45 -45
- package/scripts/smoke/hermes-rest-codex-launch.mjs +209 -209
- package/scripts/smoke/hermes-rest-gateway.mjs +79 -70
- package/scripts/smoke/hermes-rest-live.mjs +42 -42
- package/scripts/smoke/hermes-roundtrip.mjs +167 -167
- package/scripts/smoke/hermes-settings-commands.mjs +349 -346
- package/scripts/smoke/hermes-smoke-launcher-guard.mjs +34 -34
- package/scripts/smoke/live-view-diagnostics.mjs +53 -53
- package/scripts/smoke/live-view-environment.mjs +92 -92
- package/scripts/smoke/live-view-integration.mjs +450 -450
- package/scripts/smoke/mac-desktop-runtime.mjs +37 -37
- package/scripts/smoke/mobile-tunnel-guidance.mjs +29 -29
- package/scripts/smoke/model-registry.mjs +36 -36
- package/scripts/smoke/multi-project-ui.mjs +45 -45
- package/scripts/smoke/multi-worker-slots.mjs +42 -42
- package/scripts/smoke/notification-center.mjs +87 -87
- package/scripts/smoke/notification-inapp-preference.mjs +23 -23
- package/scripts/smoke/notification-taxonomy.mjs +58 -58
- package/scripts/smoke/orchestration-api.mjs +172 -172
- package/scripts/smoke/orchestration-execution-dashboard.mjs +33 -33
- package/scripts/smoke/orchestration-live-run.mjs +176 -176
- package/scripts/smoke/orchestration-mobile-scroll.mjs +29 -29
- package/scripts/smoke/orchestration-model-sync.mjs +30 -30
- package/scripts/smoke/orchestration-permission-fallback.mjs +34 -34
- package/scripts/smoke/orchestration-runtime-guards.mjs +48 -48
- package/scripts/smoke/orchestration-user-facing-output.mjs +25 -25
- package/scripts/smoke/permission-policy.mjs +50 -50
- package/scripts/smoke/pixcode-workbench-1-48.mjs +167 -167
- package/scripts/smoke/provider-models-opencode-live.mjs +66 -66
- package/scripts/smoke/provider-rest-api.mjs +124 -124
- package/scripts/smoke/provider-selection-status.mjs +52 -52
- package/scripts/smoke/run-state-refresh.mjs +52 -52
- package/scripts/smoke/runtime-manager.mjs +99 -99
- package/scripts/smoke/shell-manual-disconnect.mjs +30 -30
- package/scripts/smoke/side-panel-editor-layout.mjs +34 -34
- package/scripts/smoke/static-root-routing.mjs +21 -21
- package/scripts/smoke/strict-handoff-compact.mjs +60 -60
- package/scripts/smoke/taskmaster-config.mjs +24 -24
- package/scripts/smoke/taskmaster-execution-telegram.mjs +3 -3
- package/scripts/smoke/taskmaster-onboarding.mjs +3 -3
- package/scripts/smoke/taskmaster-run-graph.mjs +3 -3
- package/scripts/smoke/telegram-control.mjs +242 -242
- package/scripts/smoke/tunnel-persistence.mjs +56 -56
- package/scripts/smoke/update-issue-progress.mjs +69 -69
- package/scripts/smoke/update-ux.mjs +55 -55
- package/scripts/smoke/v138-completion.mjs +132 -132
- package/scripts/smoke/v138-desktop-release-hardening.mjs +69 -69
- package/scripts/smoke/v138-diagnostics.mjs +63 -63
- package/scripts/smoke/v138-issue-planner.mjs +33 -33
- package/scripts/smoke/v143-remote-control.mjs +76 -76
- package/scripts/smoke/v144-production-loop.mjs +47 -47
- package/scripts/smoke/v145-platformization.mjs +46 -46
- package/scripts/smoke/v146-control-room-ui.mjs +150 -150
- package/scripts/smoke/version-modal-autoshow.mjs +29 -29
- package/scripts/smoke/vscode-workbench-layout.mjs +63 -63
- package/scripts/smoke/vscode-workbench-polish.mjs +461 -436
- package/scripts/smoke/workflow-fallback-replay.mjs +56 -56
- package/scripts/smoke/workflow-templates.mjs +43 -43
- package/scripts/smoke/workflow-trace-timeline.mjs +46 -46
- package/scripts/update-git-install.mjs +293 -293
- package/server/claude-sdk.js +920 -920
- package/server/cli.js +1039 -1039
- package/server/constants/config.js +4 -4
- package/server/cursor-cli.js +344 -344
- package/server/daemon/manager.js +563 -563
- package/server/daemon-manager.js +964 -964
- package/server/database/db.js +908 -895
- package/server/database/json-store.js +197 -197
- package/server/gemini-cli.js +550 -550
- package/server/gemini-response-handler.js +79 -79
- package/server/index.js +201 -30
- package/server/load-env.js +35 -35
- package/server/middleware/auth.js +171 -156
- package/server/modules/orchestration/a2a/adapter-registry.ts +108 -108
- package/server/modules/orchestration/a2a/adapters/abstract-a2a.adapter.ts +63 -63
- package/server/modules/orchestration/a2a/adapters/claude-code.adapter.ts +286 -286
- package/server/modules/orchestration/a2a/adapters/codex.adapter.ts +244 -244
- package/server/modules/orchestration/a2a/adapters/cursor.adapter.ts +249 -249
- package/server/modules/orchestration/a2a/adapters/gemini.adapter.ts +248 -248
- package/server/modules/orchestration/a2a/adapters/json-event.adapter.test.ts +60 -0
- package/server/modules/orchestration/a2a/adapters/json-event.adapter.ts +101 -0
- package/server/modules/orchestration/a2a/adapters/opencode.adapter.ts +248 -248
- package/server/modules/orchestration/a2a/adapters/qwen.adapter.ts +248 -248
- package/server/modules/orchestration/a2a/agent-card.ts +55 -55
- package/server/modules/orchestration/a2a/routes.ts +590 -590
- package/server/modules/orchestration/a2a/task-store.ts +178 -178
- package/server/modules/orchestration/a2a/types.ts +126 -126
- package/server/modules/orchestration/a2a/validator.ts +113 -113
- package/server/modules/orchestration/hermes/hermes.routes.ts +642 -583
- package/server/modules/orchestration/index.ts +101 -100
- package/server/modules/orchestration/preview/port-watcher.ts +112 -112
- package/server/modules/orchestration/preview/preview-proxy.ts +60 -60
- package/server/modules/orchestration/preview/types.ts +19 -19
- package/server/modules/orchestration/security/permission-policy.ts +401 -401
- package/server/modules/orchestration/tasks/orchestration-task-store.ts +41 -41
- package/server/modules/orchestration/tasks/orchestration-task.routes.ts +64 -64
- package/server/modules/orchestration/tasks/orchestration-task.service.ts +209 -209
- package/server/modules/orchestration/tasks/orchestration-task.types.ts +40 -40
- package/server/modules/orchestration/tasks/task-run-graph.ts +155 -155
- package/server/modules/orchestration/workflows/approval-queue.ts +106 -106
- package/server/modules/orchestration/workflows/built-in-workflows.ts +127 -127
- package/server/modules/orchestration/workflows/context-packet.ts +186 -186
- package/server/modules/orchestration/workflows/handoff-artifact.ts +175 -175
- package/server/modules/orchestration/workflows/workflow-fallback-policy.ts +161 -161
- package/server/modules/orchestration/workflows/workflow-replay.ts +254 -254
- package/server/modules/orchestration/workflows/workflow-runner.ts +2070 -2070
- package/server/modules/orchestration/workflows/workflow-store.ts +97 -97
- package/server/modules/orchestration/workflows/workflow-templates.ts +272 -272
- package/server/modules/orchestration/workflows/workflow-trace.ts +424 -424
- package/server/modules/orchestration/workflows/workflow.routes.ts +586 -586
- package/server/modules/orchestration/workflows/workflow.types.ts +111 -111
- package/server/modules/orchestration/workflows/workspace-target.ts +122 -122
- package/server/modules/orchestration/workspace/docker-workspace.ts +136 -136
- package/server/modules/orchestration/workspace/path-safety.ts +55 -55
- package/server/modules/orchestration/workspace/types.ts +52 -52
- package/server/modules/orchestration/workspace/workspace-manager.ts +102 -102
- package/server/modules/orchestration/workspace/worktree-workspace.ts +126 -126
- package/server/modules/providers/index.ts +2 -2
- package/server/modules/providers/list/claude/claude-auth.provider.ts +146 -146
- package/server/modules/providers/list/claude/claude-mcp.provider.ts +135 -135
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +306 -306
- package/server/modules/providers/list/claude/claude.provider.ts +15 -15
- package/server/modules/providers/list/codex/codex-auth.provider.ts +117 -117
- package/server/modules/providers/list/codex/codex-mcp.provider.ts +135 -135
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +319 -319
- package/server/modules/providers/list/codex/codex.provider.ts +15 -15
- package/server/modules/providers/list/cursor/cursor-auth.provider.ts +147 -147
- package/server/modules/providers/list/cursor/cursor-mcp.provider.ts +108 -108
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +421 -421
- package/server/modules/providers/list/cursor/cursor.provider.ts +15 -15
- package/server/modules/providers/list/gemini/gemini-auth.provider.ts +173 -173
- package/server/modules/providers/list/gemini/gemini-mcp.provider.ts +110 -110
- package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +227 -227
- package/server/modules/providers/list/gemini/gemini.provider.ts +15 -15
- package/server/modules/providers/list/opencode/opencode-auth.provider.ts +131 -131
- package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +126 -126
- package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +286 -286
- package/server/modules/providers/list/opencode/opencode.provider.ts +29 -29
- package/server/modules/providers/list/qwen/qwen-auth.provider.ts +146 -146
- package/server/modules/providers/list/qwen/qwen-mcp.provider.ts +114 -114
- package/server/modules/providers/list/qwen/qwen-sessions.provider.ts +265 -265
- package/server/modules/providers/list/qwen/qwen.provider.ts +21 -21
- package/server/modules/providers/provider.registry.ts +40 -40
- package/server/modules/providers/provider.routes.ts +944 -944
- package/server/modules/providers/services/mcp.service.ts +86 -86
- package/server/modules/providers/services/provider-auth.service.ts +26 -26
- package/server/modules/providers/services/sessions.service.ts +45 -45
- package/server/modules/providers/shared/base/abstract.provider.ts +20 -20
- package/server/modules/providers/shared/mcp/mcp.provider.ts +151 -151
- package/server/modules/providers/shared/provider-configs.ts +142 -142
- package/server/modules/providers/tests/mcp.test.ts +293 -293
- package/server/openai-codex.js +462 -462
- package/server/opencode-cli.js +491 -491
- package/server/opencode-response-handler.js +111 -111
- package/server/projects.js +3008 -3008
- package/server/qwen-code-cli.js +410 -410
- package/server/qwen-response-handler.js +73 -73
- package/server/routes/agent.js +1435 -1435
- package/server/routes/auth.js +154 -146
- package/server/routes/codex.js +20 -20
- package/server/routes/commands.js +570 -570
- package/server/routes/cursor.js +61 -61
- package/server/routes/diagnostics.js +41 -41
- package/server/routes/gemini.js +25 -25
- package/server/routes/git.js +1650 -1635
- package/server/routes/live-view.js +411 -411
- package/server/routes/mcp-utils.js +13 -13
- package/server/routes/messages.js +62 -62
- package/server/routes/network.js +125 -125
- package/server/routes/platformization.js +198 -197
- package/server/routes/plugins.js +320 -320
- package/server/routes/production-agent-loop.js +90 -90
- package/server/routes/projects.js +917 -917
- package/server/routes/public-api.js +34 -34
- package/server/routes/qwen.js +27 -27
- package/server/routes/remote.js +55 -55
- package/server/routes/settings.js +321 -321
- package/server/routes/telegram.js +140 -140
- package/server/routes/user.js +125 -125
- package/server/routes/webhooks.js +63 -63
- package/server/services/control-room.js +102 -102
- package/server/services/diagnostics.js +165 -165
- package/server/services/external-access.js +375 -375
- package/server/services/hermes-gateway.js +1562 -1247
- package/server/services/hermes-install-jobs.js +729 -729
- package/server/services/install-jobs.js +715 -715
- package/server/services/live-view.js +956 -956
- package/server/services/managed-runtimes.js +493 -493
- package/server/services/model-registry.js +144 -144
- package/server/services/notification-orchestrator.js +365 -365
- package/server/services/notification-taxonomy.js +204 -204
- package/server/services/platformization.js +844 -779
- package/server/services/production-agent-loop.js +248 -248
- package/server/services/provider-cli-versions.js +149 -149
- package/server/services/provider-credentials.js +189 -189
- package/server/services/provider-models.js +396 -396
- package/server/services/public-api-manifest.js +190 -182
- package/server/services/remote-connection.js +127 -127
- package/server/services/runtime-manager.js +323 -323
- package/server/services/startup-update.js +234 -234
- package/server/services/telegram/bot.js +331 -331
- package/server/services/telegram/control-center.js +979 -979
- package/server/services/telegram/telegram-http-client.js +151 -151
- package/server/services/telegram/translations.js +340 -340
- package/server/services/vapid-keys.js +36 -36
- package/server/services/webhooks.js +216 -216
- package/server/sessionManager.js +225 -225
- package/server/shared/interfaces.ts +54 -54
- package/server/shared/types.ts +172 -172
- package/server/shared/utils.ts +193 -193
- package/server/tsconfig.json +36 -36
- package/server/utils/colors.js +21 -21
- package/server/utils/commandParser.js +305 -305
- package/server/utils/frontmatter.js +18 -18
- package/server/utils/gitConfig.js +34 -34
- package/server/utils/plugin-loader.js +457 -457
- package/server/utils/plugin-process-manager.js +185 -185
- package/server/utils/port-access.js +209 -209
- package/server/utils/runtime-paths.js +37 -37
- package/server/utils/url-detection.js +71 -71
- package/server/vite-daemon.js +79 -79
- package/shared/modelConstants.js +161 -161
- package/shared/networkHosts.js +22 -22
- package/dist/assets/index-DMz0zv6T.css +0 -32
|
@@ -1,209 +1,209 @@
|
|
|
1
|
-
import os from 'os';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import readline from 'readline';
|
|
5
|
-
import { execSync } from 'child_process';
|
|
6
|
-
|
|
7
|
-
import { c } from './colors.js';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Tracks inbound port access decisions across runs so the server doesn't
|
|
11
|
-
* re-prompt the user (on Windows/macOS) every start, and can skip work on
|
|
12
|
-
* Linux where a rule has already been applied.
|
|
13
|
-
*/
|
|
14
|
-
const STATE_FILE = path.join(os.homedir(), '.pixcode', 'port-access.json');
|
|
15
|
-
|
|
16
|
-
function loadState() {
|
|
17
|
-
try {
|
|
18
|
-
return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
|
|
19
|
-
} catch {
|
|
20
|
-
return {};
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function saveState(state) {
|
|
25
|
-
try {
|
|
26
|
-
fs.mkdirSync(path.dirname(STATE_FILE), { recursive: true });
|
|
27
|
-
fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
28
|
-
} catch {
|
|
29
|
-
// Persisting the decision is a nice-to-have; failure shouldn't block start.
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** Return all non-loopback IPv4 addresses assigned to this host. */
|
|
34
|
-
export function getLanIps() {
|
|
35
|
-
const ifs = os.networkInterfaces();
|
|
36
|
-
const ips = [];
|
|
37
|
-
for (const name of Object.keys(ifs)) {
|
|
38
|
-
for (const ni of ifs[name] || []) {
|
|
39
|
-
if (ni.family === 'IPv4' && !ni.internal) ips.push(ni.address);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return ips;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function askYN(question) {
|
|
46
|
-
return new Promise((resolve) => {
|
|
47
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
48
|
-
resolve(null); // headless / daemon context — skip
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
52
|
-
rl.question(question, (answer) => {
|
|
53
|
-
rl.close();
|
|
54
|
-
const normalized = answer.trim().toLowerCase();
|
|
55
|
-
resolve(['y', 'yes', 'e', 'evet'].includes(normalized));
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// ---------------- Linux ----------------
|
|
61
|
-
|
|
62
|
-
function tryUfw(port) {
|
|
63
|
-
try {
|
|
64
|
-
execSync('command -v ufw', { stdio: 'pipe' });
|
|
65
|
-
const status = execSync('ufw status', { stdio: 'pipe', encoding: 'utf8' });
|
|
66
|
-
// UFW inactive = no rule needed; the port is already reachable.
|
|
67
|
-
if (!/active/i.test(status)) return 'inactive';
|
|
68
|
-
execSync(`sudo -n ufw allow ${port}/tcp`, { stdio: 'pipe' });
|
|
69
|
-
return 'applied';
|
|
70
|
-
} catch {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function tryFirewalld(port) {
|
|
76
|
-
try {
|
|
77
|
-
execSync('command -v firewall-cmd', { stdio: 'pipe' });
|
|
78
|
-
const state = execSync('firewall-cmd --state', { stdio: 'pipe', encoding: 'utf8' });
|
|
79
|
-
if (!/running/i.test(state)) return 'inactive';
|
|
80
|
-
execSync(`sudo -n firewall-cmd --add-port=${port}/tcp --permanent`, { stdio: 'pipe' });
|
|
81
|
-
execSync('sudo -n firewall-cmd --reload', { stdio: 'pipe' });
|
|
82
|
-
return 'applied';
|
|
83
|
-
} catch {
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// ---------------- Windows ----------------
|
|
89
|
-
|
|
90
|
-
function applyWindowsRule(port) {
|
|
91
|
-
const ruleName = `Pixcode-${port}`;
|
|
92
|
-
const cmd = `netsh advfirewall firewall add rule name="${ruleName}" dir=in action=allow protocol=TCP localport=${port}`;
|
|
93
|
-
try {
|
|
94
|
-
execSync(cmd, { stdio: 'pipe' });
|
|
95
|
-
return { ok: true, cmd, ruleName };
|
|
96
|
-
} catch (error) {
|
|
97
|
-
return { ok: false, cmd, ruleName, error: error.message };
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// ---------------- macOS ----------------
|
|
102
|
-
|
|
103
|
-
function macFirewallHint(port) {
|
|
104
|
-
return [
|
|
105
|
-
`${c.tip('[TIP]')} macOS Application Firewall is per-app, not per-port.`,
|
|
106
|
-
' If other devices can\'t connect, open:',
|
|
107
|
-
' System Settings > Network > Firewall > Options',
|
|
108
|
-
` and add Node.js to "Allow incoming connections" (port ${port}).`,
|
|
109
|
-
].join('\n');
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// ---------------- Main entry ----------------
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Make sure inbound traffic can reach `port` from the local network.
|
|
116
|
-
*
|
|
117
|
-
* - Linux: silently try ufw/firewalld with `sudo -n` (passwordless). Skips
|
|
118
|
-
* cleanly when no firewall is running or sudo is gated — LAN access
|
|
119
|
-
* already works on most desktop distros.
|
|
120
|
-
* - Windows/macOS: ask the user once; remember the decision in
|
|
121
|
-
* ~/.pixcode/port-access.json so subsequent starts stay quiet.
|
|
122
|
-
*
|
|
123
|
-
* All failure paths are non-fatal: the server is already listening when
|
|
124
|
-
* we get here, and LAN clients on the same subnet often can reach it
|
|
125
|
-
* without any firewall change.
|
|
126
|
-
*/
|
|
127
|
-
export async function ensurePortOpen(port) {
|
|
128
|
-
const state = loadState();
|
|
129
|
-
const key = `port:${port}`;
|
|
130
|
-
const entry = state[key];
|
|
131
|
-
|
|
132
|
-
const lanIps = getLanIps();
|
|
133
|
-
if (lanIps.length) {
|
|
134
|
-
console.log(`${c.info('[INFO]')} LAN access: ${c.bright(`http://${lanIps[0]}:${port}`)}`);
|
|
135
|
-
if (lanIps.length > 1) {
|
|
136
|
-
for (const extra of lanIps.slice(1)) {
|
|
137
|
-
console.log(`${c.dim(' also:')} http://${extra}:${port}`);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (entry && entry.decision === 'deny') {
|
|
143
|
-
console.log(`${c.dim('[INFO]')} Firewall prompt suppressed (previously declined). Re-enable via ~/.pixcode/port-access.json`);
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const platform = process.platform;
|
|
148
|
-
|
|
149
|
-
if (platform === 'linux') {
|
|
150
|
-
if (entry && entry.status === 'applied') return;
|
|
151
|
-
const result = tryUfw(port) || tryFirewalld(port);
|
|
152
|
-
if (result === 'applied') {
|
|
153
|
-
console.log(`${c.ok('[OK]')} Inbound firewall rule added for port ${port}.`);
|
|
154
|
-
saveState({ ...state, [key]: { decision: 'allow', status: 'applied', via: 'linux' } });
|
|
155
|
-
} else if (result === 'inactive') {
|
|
156
|
-
console.log(`${c.dim('[INFO]')} No active firewall detected — port ${port} should be reachable.`);
|
|
157
|
-
} else {
|
|
158
|
-
console.log(`${c.tip('[TIP]')} Couldn't auto-open port (no sudo / unsupported firewall).`);
|
|
159
|
-
console.log(` Manual: ${c.bright(`sudo ufw allow ${port}/tcp`)} ${c.dim('(or firewall-cmd equivalent)')}`);
|
|
160
|
-
}
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (platform === 'win32') {
|
|
165
|
-
if (entry && entry.status === 'applied') return;
|
|
166
|
-
const approved = await askYN(
|
|
167
|
-
`${c.tip('[?]')} Open port ${port} in Windows Firewall so other devices on your network can connect? [y/N] `,
|
|
168
|
-
);
|
|
169
|
-
if (approved === null) {
|
|
170
|
-
// No TTY — just show the manual command and move on.
|
|
171
|
-
console.log(
|
|
172
|
-
`${c.tip('[TIP]')} To allow LAN access, run this in an elevated PowerShell:\n ${c.bright(
|
|
173
|
-
`netsh advfirewall firewall add rule name="Pixcode-${port}" dir=in action=allow protocol=TCP localport=${port}`,
|
|
174
|
-
)}`,
|
|
175
|
-
);
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
if (!approved) {
|
|
179
|
-
console.log(`${c.dim('[INFO]')} Skipping firewall change.`);
|
|
180
|
-
saveState({ ...state, [key]: { decision: 'deny' } });
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
const result = applyWindowsRule(port);
|
|
184
|
-
if (result.ok) {
|
|
185
|
-
console.log(`${c.ok('[OK]')} Firewall rule "${result.ruleName}" added.`);
|
|
186
|
-
saveState({ ...state, [key]: { decision: 'allow', status: 'applied', via: 'windows' } });
|
|
187
|
-
} else {
|
|
188
|
-
console.log(`${c.tip('[TIP]')} Adding the rule needs Administrator. Run this in an elevated PowerShell:`);
|
|
189
|
-
console.log(` ${c.bright(result.cmd)}`);
|
|
190
|
-
saveState({ ...state, [key]: { decision: 'allow', status: 'manual' } });
|
|
191
|
-
}
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (platform === 'darwin') {
|
|
196
|
-
if (entry && entry.status) return;
|
|
197
|
-
const approved = await askYN(
|
|
198
|
-
`${c.tip('[?]')} Allow inbound connections on port ${port} through macOS firewall? [y/N] `,
|
|
199
|
-
);
|
|
200
|
-
if (approved === null) return;
|
|
201
|
-
if (!approved) {
|
|
202
|
-
console.log(`${c.dim('[INFO]')} Skipping firewall hint.`);
|
|
203
|
-
saveState({ ...state, [key]: { decision: 'deny' } });
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
console.log(macFirewallHint(port));
|
|
207
|
-
saveState({ ...state, [key]: { decision: 'allow', status: 'manual', via: 'macos' } });
|
|
208
|
-
}
|
|
209
|
-
}
|
|
1
|
+
import os from 'os';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import readline from 'readline';
|
|
5
|
+
import { execSync } from 'child_process';
|
|
6
|
+
|
|
7
|
+
import { c } from './colors.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Tracks inbound port access decisions across runs so the server doesn't
|
|
11
|
+
* re-prompt the user (on Windows/macOS) every start, and can skip work on
|
|
12
|
+
* Linux where a rule has already been applied.
|
|
13
|
+
*/
|
|
14
|
+
const STATE_FILE = path.join(os.homedir(), '.pixcode', 'port-access.json');
|
|
15
|
+
|
|
16
|
+
function loadState() {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
|
|
19
|
+
} catch {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function saveState(state) {
|
|
25
|
+
try {
|
|
26
|
+
fs.mkdirSync(path.dirname(STATE_FILE), { recursive: true });
|
|
27
|
+
fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
28
|
+
} catch {
|
|
29
|
+
// Persisting the decision is a nice-to-have; failure shouldn't block start.
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Return all non-loopback IPv4 addresses assigned to this host. */
|
|
34
|
+
export function getLanIps() {
|
|
35
|
+
const ifs = os.networkInterfaces();
|
|
36
|
+
const ips = [];
|
|
37
|
+
for (const name of Object.keys(ifs)) {
|
|
38
|
+
for (const ni of ifs[name] || []) {
|
|
39
|
+
if (ni.family === 'IPv4' && !ni.internal) ips.push(ni.address);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return ips;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function askYN(question) {
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
48
|
+
resolve(null); // headless / daemon context — skip
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
52
|
+
rl.question(question, (answer) => {
|
|
53
|
+
rl.close();
|
|
54
|
+
const normalized = answer.trim().toLowerCase();
|
|
55
|
+
resolve(['y', 'yes', 'e', 'evet'].includes(normalized));
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ---------------- Linux ----------------
|
|
61
|
+
|
|
62
|
+
function tryUfw(port) {
|
|
63
|
+
try {
|
|
64
|
+
execSync('command -v ufw', { stdio: 'pipe' });
|
|
65
|
+
const status = execSync('ufw status', { stdio: 'pipe', encoding: 'utf8' });
|
|
66
|
+
// UFW inactive = no rule needed; the port is already reachable.
|
|
67
|
+
if (!/active/i.test(status)) return 'inactive';
|
|
68
|
+
execSync(`sudo -n ufw allow ${port}/tcp`, { stdio: 'pipe' });
|
|
69
|
+
return 'applied';
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function tryFirewalld(port) {
|
|
76
|
+
try {
|
|
77
|
+
execSync('command -v firewall-cmd', { stdio: 'pipe' });
|
|
78
|
+
const state = execSync('firewall-cmd --state', { stdio: 'pipe', encoding: 'utf8' });
|
|
79
|
+
if (!/running/i.test(state)) return 'inactive';
|
|
80
|
+
execSync(`sudo -n firewall-cmd --add-port=${port}/tcp --permanent`, { stdio: 'pipe' });
|
|
81
|
+
execSync('sudo -n firewall-cmd --reload', { stdio: 'pipe' });
|
|
82
|
+
return 'applied';
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---------------- Windows ----------------
|
|
89
|
+
|
|
90
|
+
function applyWindowsRule(port) {
|
|
91
|
+
const ruleName = `Pixcode-${port}`;
|
|
92
|
+
const cmd = `netsh advfirewall firewall add rule name="${ruleName}" dir=in action=allow protocol=TCP localport=${port}`;
|
|
93
|
+
try {
|
|
94
|
+
execSync(cmd, { stdio: 'pipe' });
|
|
95
|
+
return { ok: true, cmd, ruleName };
|
|
96
|
+
} catch (error) {
|
|
97
|
+
return { ok: false, cmd, ruleName, error: error.message };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ---------------- macOS ----------------
|
|
102
|
+
|
|
103
|
+
function macFirewallHint(port) {
|
|
104
|
+
return [
|
|
105
|
+
`${c.tip('[TIP]')} macOS Application Firewall is per-app, not per-port.`,
|
|
106
|
+
' If other devices can\'t connect, open:',
|
|
107
|
+
' System Settings > Network > Firewall > Options',
|
|
108
|
+
` and add Node.js to "Allow incoming connections" (port ${port}).`,
|
|
109
|
+
].join('\n');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ---------------- Main entry ----------------
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Make sure inbound traffic can reach `port` from the local network.
|
|
116
|
+
*
|
|
117
|
+
* - Linux: silently try ufw/firewalld with `sudo -n` (passwordless). Skips
|
|
118
|
+
* cleanly when no firewall is running or sudo is gated — LAN access
|
|
119
|
+
* already works on most desktop distros.
|
|
120
|
+
* - Windows/macOS: ask the user once; remember the decision in
|
|
121
|
+
* ~/.pixcode/port-access.json so subsequent starts stay quiet.
|
|
122
|
+
*
|
|
123
|
+
* All failure paths are non-fatal: the server is already listening when
|
|
124
|
+
* we get here, and LAN clients on the same subnet often can reach it
|
|
125
|
+
* without any firewall change.
|
|
126
|
+
*/
|
|
127
|
+
export async function ensurePortOpen(port) {
|
|
128
|
+
const state = loadState();
|
|
129
|
+
const key = `port:${port}`;
|
|
130
|
+
const entry = state[key];
|
|
131
|
+
|
|
132
|
+
const lanIps = getLanIps();
|
|
133
|
+
if (lanIps.length) {
|
|
134
|
+
console.log(`${c.info('[INFO]')} LAN access: ${c.bright(`http://${lanIps[0]}:${port}`)}`);
|
|
135
|
+
if (lanIps.length > 1) {
|
|
136
|
+
for (const extra of lanIps.slice(1)) {
|
|
137
|
+
console.log(`${c.dim(' also:')} http://${extra}:${port}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (entry && entry.decision === 'deny') {
|
|
143
|
+
console.log(`${c.dim('[INFO]')} Firewall prompt suppressed (previously declined). Re-enable via ~/.pixcode/port-access.json`);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const platform = process.platform;
|
|
148
|
+
|
|
149
|
+
if (platform === 'linux') {
|
|
150
|
+
if (entry && entry.status === 'applied') return;
|
|
151
|
+
const result = tryUfw(port) || tryFirewalld(port);
|
|
152
|
+
if (result === 'applied') {
|
|
153
|
+
console.log(`${c.ok('[OK]')} Inbound firewall rule added for port ${port}.`);
|
|
154
|
+
saveState({ ...state, [key]: { decision: 'allow', status: 'applied', via: 'linux' } });
|
|
155
|
+
} else if (result === 'inactive') {
|
|
156
|
+
console.log(`${c.dim('[INFO]')} No active firewall detected — port ${port} should be reachable.`);
|
|
157
|
+
} else {
|
|
158
|
+
console.log(`${c.tip('[TIP]')} Couldn't auto-open port (no sudo / unsupported firewall).`);
|
|
159
|
+
console.log(` Manual: ${c.bright(`sudo ufw allow ${port}/tcp`)} ${c.dim('(or firewall-cmd equivalent)')}`);
|
|
160
|
+
}
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (platform === 'win32') {
|
|
165
|
+
if (entry && entry.status === 'applied') return;
|
|
166
|
+
const approved = await askYN(
|
|
167
|
+
`${c.tip('[?]')} Open port ${port} in Windows Firewall so other devices on your network can connect? [y/N] `,
|
|
168
|
+
);
|
|
169
|
+
if (approved === null) {
|
|
170
|
+
// No TTY — just show the manual command and move on.
|
|
171
|
+
console.log(
|
|
172
|
+
`${c.tip('[TIP]')} To allow LAN access, run this in an elevated PowerShell:\n ${c.bright(
|
|
173
|
+
`netsh advfirewall firewall add rule name="Pixcode-${port}" dir=in action=allow protocol=TCP localport=${port}`,
|
|
174
|
+
)}`,
|
|
175
|
+
);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (!approved) {
|
|
179
|
+
console.log(`${c.dim('[INFO]')} Skipping firewall change.`);
|
|
180
|
+
saveState({ ...state, [key]: { decision: 'deny' } });
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const result = applyWindowsRule(port);
|
|
184
|
+
if (result.ok) {
|
|
185
|
+
console.log(`${c.ok('[OK]')} Firewall rule "${result.ruleName}" added.`);
|
|
186
|
+
saveState({ ...state, [key]: { decision: 'allow', status: 'applied', via: 'windows' } });
|
|
187
|
+
} else {
|
|
188
|
+
console.log(`${c.tip('[TIP]')} Adding the rule needs Administrator. Run this in an elevated PowerShell:`);
|
|
189
|
+
console.log(` ${c.bright(result.cmd)}`);
|
|
190
|
+
saveState({ ...state, [key]: { decision: 'allow', status: 'manual' } });
|
|
191
|
+
}
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (platform === 'darwin') {
|
|
196
|
+
if (entry && entry.status) return;
|
|
197
|
+
const approved = await askYN(
|
|
198
|
+
`${c.tip('[?]')} Allow inbound connections on port ${port} through macOS firewall? [y/N] `,
|
|
199
|
+
);
|
|
200
|
+
if (approved === null) return;
|
|
201
|
+
if (!approved) {
|
|
202
|
+
console.log(`${c.dim('[INFO]')} Skipping firewall hint.`);
|
|
203
|
+
saveState({ ...state, [key]: { decision: 'deny' } });
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
console.log(macFirewallHint(port));
|
|
207
|
+
saveState({ ...state, [key]: { decision: 'allow', status: 'manual', via: 'macos' } });
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import { fileURLToPath } from 'url';
|
|
3
|
-
|
|
4
|
-
export function getModuleDir(importMetaUrl) {
|
|
5
|
-
return path.dirname(fileURLToPath(importMetaUrl));
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function findServerRoot(startDir) {
|
|
9
|
-
// Source files live under /server, while compiled files live under /dist-server/server.
|
|
10
|
-
// Walking up to the nearest "server" folder gives every backend module one stable anchor
|
|
11
|
-
// that works in both layouts instead of relying on fragile "../.." assumptions.
|
|
12
|
-
let currentDir = startDir;
|
|
13
|
-
|
|
14
|
-
while (path.basename(currentDir) !== 'server') {
|
|
15
|
-
const parentDir = path.dirname(currentDir);
|
|
16
|
-
|
|
17
|
-
if (parentDir === currentDir) {
|
|
18
|
-
throw new Error(`Could not resolve the backend server root from "${startDir}".`);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
currentDir = parentDir;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return currentDir;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function findAppRoot(startDir) {
|
|
28
|
-
const serverRoot = findServerRoot(startDir);
|
|
29
|
-
const parentOfServerRoot = path.dirname(serverRoot);
|
|
30
|
-
|
|
31
|
-
// Source files live at <app>/server, while compiled files live at <app>/dist-server/server.
|
|
32
|
-
// When the nearest server folder sits inside dist-server we need to hop one extra level up
|
|
33
|
-
// so repo-level files still resolve from the real app root instead of the build directory.
|
|
34
|
-
return path.basename(parentOfServerRoot) === 'dist-server'
|
|
35
|
-
? path.dirname(parentOfServerRoot)
|
|
36
|
-
: parentOfServerRoot;
|
|
37
|
-
}
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
|
|
4
|
+
export function getModuleDir(importMetaUrl) {
|
|
5
|
+
return path.dirname(fileURLToPath(importMetaUrl));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function findServerRoot(startDir) {
|
|
9
|
+
// Source files live under /server, while compiled files live under /dist-server/server.
|
|
10
|
+
// Walking up to the nearest "server" folder gives every backend module one stable anchor
|
|
11
|
+
// that works in both layouts instead of relying on fragile "../.." assumptions.
|
|
12
|
+
let currentDir = startDir;
|
|
13
|
+
|
|
14
|
+
while (path.basename(currentDir) !== 'server') {
|
|
15
|
+
const parentDir = path.dirname(currentDir);
|
|
16
|
+
|
|
17
|
+
if (parentDir === currentDir) {
|
|
18
|
+
throw new Error(`Could not resolve the backend server root from "${startDir}".`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
currentDir = parentDir;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return currentDir;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function findAppRoot(startDir) {
|
|
28
|
+
const serverRoot = findServerRoot(startDir);
|
|
29
|
+
const parentOfServerRoot = path.dirname(serverRoot);
|
|
30
|
+
|
|
31
|
+
// Source files live at <app>/server, while compiled files live at <app>/dist-server/server.
|
|
32
|
+
// When the nearest server folder sits inside dist-server we need to hop one extra level up
|
|
33
|
+
// so repo-level files still resolve from the real app root instead of the build directory.
|
|
34
|
+
return path.basename(parentOfServerRoot) === 'dist-server'
|
|
35
|
+
? path.dirname(parentOfServerRoot)
|
|
36
|
+
: parentOfServerRoot;
|
|
37
|
+
}
|
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
const ANSI_ESCAPE_SEQUENCE_REGEX = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\))/g;
|
|
2
|
-
const TRAILING_URL_PUNCTUATION_REGEX = /[)\]}>.,;:!?]+$/;
|
|
3
|
-
|
|
4
|
-
function stripAnsiSequences(value = '') {
|
|
5
|
-
return value.replace(ANSI_ESCAPE_SEQUENCE_REGEX, '');
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
function normalizeDetectedUrl(url) {
|
|
9
|
-
if (!url || typeof url !== 'string') return null;
|
|
10
|
-
|
|
11
|
-
const cleaned = url.trim().replace(TRAILING_URL_PUNCTUATION_REGEX, '');
|
|
12
|
-
if (!cleaned) return null;
|
|
13
|
-
|
|
14
|
-
try {
|
|
15
|
-
const parsed = new URL(cleaned);
|
|
16
|
-
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
return parsed.toString();
|
|
20
|
-
} catch {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function extractUrlsFromText(value = '') {
|
|
26
|
-
const directMatches = value.match(/https?:\/\/[^\s<>"'`\\\x1b\x07]+/gi) || [];
|
|
27
|
-
|
|
28
|
-
// Handle wrapped terminal URLs split across lines by terminal width.
|
|
29
|
-
const wrappedMatches = [];
|
|
30
|
-
const continuationRegex = /^[A-Za-z0-9\-._~:/?#\[\]@!$&'()*+,;=%]+$/;
|
|
31
|
-
const lines = value.split(/\r?\n/);
|
|
32
|
-
for (let i = 0; i < lines.length; i++) {
|
|
33
|
-
const line = lines[i].trim();
|
|
34
|
-
const startMatch = line.match(/https?:\/\/[^\s<>"'`\\\x1b\x07]+/i);
|
|
35
|
-
if (!startMatch) continue;
|
|
36
|
-
|
|
37
|
-
let combined = startMatch[0];
|
|
38
|
-
let j = i + 1;
|
|
39
|
-
while (j < lines.length) {
|
|
40
|
-
const continuation = lines[j].trim();
|
|
41
|
-
if (!continuation) break;
|
|
42
|
-
if (!continuationRegex.test(continuation)) break;
|
|
43
|
-
combined += continuation;
|
|
44
|
-
j++;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
wrappedMatches.push(combined.replace(/\r?\n\s*/g, ''));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return Array.from(new Set([...directMatches, ...wrappedMatches]));
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function shouldAutoOpenUrlFromOutput(value = '') {
|
|
54
|
-
const normalized = value.toLowerCase();
|
|
55
|
-
return (
|
|
56
|
-
normalized.includes('browser didn\'t open') ||
|
|
57
|
-
normalized.includes('open this url') ||
|
|
58
|
-
normalized.includes('continue in your browser') ||
|
|
59
|
-
normalized.includes('press enter to open') ||
|
|
60
|
-
normalized.includes('open_url:')
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export {
|
|
65
|
-
ANSI_ESCAPE_SEQUENCE_REGEX,
|
|
66
|
-
TRAILING_URL_PUNCTUATION_REGEX,
|
|
67
|
-
stripAnsiSequences,
|
|
68
|
-
normalizeDetectedUrl,
|
|
69
|
-
extractUrlsFromText,
|
|
70
|
-
shouldAutoOpenUrlFromOutput
|
|
71
|
-
};
|
|
1
|
+
const ANSI_ESCAPE_SEQUENCE_REGEX = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\))/g;
|
|
2
|
+
const TRAILING_URL_PUNCTUATION_REGEX = /[)\]}>.,;:!?]+$/;
|
|
3
|
+
|
|
4
|
+
function stripAnsiSequences(value = '') {
|
|
5
|
+
return value.replace(ANSI_ESCAPE_SEQUENCE_REGEX, '');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function normalizeDetectedUrl(url) {
|
|
9
|
+
if (!url || typeof url !== 'string') return null;
|
|
10
|
+
|
|
11
|
+
const cleaned = url.trim().replace(TRAILING_URL_PUNCTUATION_REGEX, '');
|
|
12
|
+
if (!cleaned) return null;
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const parsed = new URL(cleaned);
|
|
16
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return parsed.toString();
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function extractUrlsFromText(value = '') {
|
|
26
|
+
const directMatches = value.match(/https?:\/\/[^\s<>"'`\\\x1b\x07]+/gi) || [];
|
|
27
|
+
|
|
28
|
+
// Handle wrapped terminal URLs split across lines by terminal width.
|
|
29
|
+
const wrappedMatches = [];
|
|
30
|
+
const continuationRegex = /^[A-Za-z0-9\-._~:/?#\[\]@!$&'()*+,;=%]+$/;
|
|
31
|
+
const lines = value.split(/\r?\n/);
|
|
32
|
+
for (let i = 0; i < lines.length; i++) {
|
|
33
|
+
const line = lines[i].trim();
|
|
34
|
+
const startMatch = line.match(/https?:\/\/[^\s<>"'`\\\x1b\x07]+/i);
|
|
35
|
+
if (!startMatch) continue;
|
|
36
|
+
|
|
37
|
+
let combined = startMatch[0];
|
|
38
|
+
let j = i + 1;
|
|
39
|
+
while (j < lines.length) {
|
|
40
|
+
const continuation = lines[j].trim();
|
|
41
|
+
if (!continuation) break;
|
|
42
|
+
if (!continuationRegex.test(continuation)) break;
|
|
43
|
+
combined += continuation;
|
|
44
|
+
j++;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
wrappedMatches.push(combined.replace(/\r?\n\s*/g, ''));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return Array.from(new Set([...directMatches, ...wrappedMatches]));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function shouldAutoOpenUrlFromOutput(value = '') {
|
|
54
|
+
const normalized = value.toLowerCase();
|
|
55
|
+
return (
|
|
56
|
+
normalized.includes('browser didn\'t open') ||
|
|
57
|
+
normalized.includes('open this url') ||
|
|
58
|
+
normalized.includes('continue in your browser') ||
|
|
59
|
+
normalized.includes('press enter to open') ||
|
|
60
|
+
normalized.includes('open_url:')
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export {
|
|
65
|
+
ANSI_ESCAPE_SEQUENCE_REGEX,
|
|
66
|
+
TRAILING_URL_PUNCTUATION_REGEX,
|
|
67
|
+
stripAnsiSequences,
|
|
68
|
+
normalizeDetectedUrl,
|
|
69
|
+
extractUrlsFromText,
|
|
70
|
+
shouldAutoOpenUrlFromOutput
|
|
71
|
+
};
|