@pixelbyte-software/pixcode 1.54.9 → 1.54.11
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 +156 -156
- package/LICENSE +21 -718
- package/README.de.md +169 -169
- package/README.ja.md +167 -167
- package/README.ko.md +167 -167
- package/README.md +435 -418
- 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-Bm6uAqKU.js → index-Bcqs_1vc.js} +114 -114
- package/dist/clear-cache.html +85 -85
- package/dist/convert-icons.md +52 -52
- package/dist/docs.html +308 -308
- 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/qwen-logo.svg +14 -14
- package/dist/index.html +58 -58
- package/dist/landing.html +268 -268
- package/dist/llms-full.txt +119 -119
- package/dist/llms.txt +53 -53
- 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 +100 -100
- package/dist-server/server/daemon/manager.js +33 -33
- package/dist-server/server/daemon-manager.js +64 -64
- package/dist-server/server/index.js +13 -0
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/routes/agent.js +10 -1
- package/dist-server/server/routes/agent.js.map +1 -1
- package/dist-server/server/routes/commands.js +25 -25
- package/dist-server/server/routes/git.js +17 -17
- package/dist-server/server/routes/live-view.js +46 -46
- package/dist-server/server/services/public-api-manifest.js +51 -51
- package/dist-server/server/sessionManager.js +5 -5
- package/dist-server/server/sessionManager.js.map +1 -1
- package/package.json +224 -224
- 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/smoke/backend-resource-bounds.mjs +152 -152
- 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/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/mobile-ux.mjs +76 -76
- 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 +331 -331
- 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 -461
- 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 +298 -298
- package/server/claude-sdk.js +927 -927
- package/server/cli.js +1106 -1106
- 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 +988 -988
- 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 +5671 -5659
- package/server/load-env.js +35 -35
- package/server/middleware/account-lockout.js +112 -112
- package/server/middleware/auth.js +277 -277
- package/server/middleware/rate-limiter.js +87 -87
- 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 -60
- package/server/modules/orchestration/a2a/adapters/json-event.adapter.ts +101 -101
- 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/auth.middleware.ts +29 -29
- package/server/modules/orchestration/a2a/bus.ts +46 -46
- package/server/modules/orchestration/a2a/routes.ts +586 -586
- package/server/modules/orchestration/a2a/task-dispatcher.ts +345 -345
- 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/index.ts +99 -99
- 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 +81 -81
- package/server/modules/orchestration/tasks/orchestration-task.service.ts +201 -201
- 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 +2062 -2062
- 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-sessions.provider.ts +286 -286
- package/server/modules/providers/list/qwen/qwen-auth.provider.ts +146 -146
- package/server/modules/providers/list/qwen/qwen-sessions.provider.ts +265 -265
- package/server/modules/providers/provider.registry.ts +40 -40
- package/server/modules/providers/provider.routes.ts +982 -982
- 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/tests/mcp.test.ts +293 -293
- package/server/openai-codex.js +468 -468
- package/server/opencode-cli.js +491 -491
- package/server/opencode-response-handler.js +111 -111
- package/server/projects.js +3135 -3135
- package/server/qwen-code-cli.js +410 -410
- package/server/routes/agent.js +1462 -1451
- package/server/routes/auth.js +298 -298
- package/server/routes/codex.js +20 -20
- package/server/routes/commands.js +581 -581
- package/server/routes/cursor.js +61 -61
- package/server/routes/diagnostics.js +44 -44
- package/server/routes/gemini.js +25 -25
- package/server/routes/git.js +1822 -1822
- package/server/routes/live-view.js +434 -434
- package/server/routes/mcp-utils.js +13 -13
- package/server/routes/messages.js +97 -97
- package/server/routes/network.js +143 -143
- package/server/routes/platformization.js +231 -231
- package/server/routes/plugins.js +690 -690
- package/server/routes/production-agent-loop.js +90 -90
- package/server/routes/projects.js +918 -918
- package/server/routes/public-api.js +34 -34
- package/server/routes/qwen.js +27 -27
- package/server/routes/remote.js +56 -56
- package/server/routes/settings.js +321 -321
- package/server/routes/telegram.js +163 -163
- 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 +403 -403
- 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 +1052 -1052
- 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 +181 -181
- package/server/services/qr-login.js +126 -126
- package/server/services/remote-connection.js +127 -127
- package/server/services/runtime-manager.js +323 -323
- package/server/services/startup-update.js +259 -259
- package/server/services/telegram/bot.js +393 -393
- package/server/services/telegram/control-center.js +2453 -2453
- package/server/services/telegram/telegram-gateway.js +314 -314
- package/server/services/telegram/telegram-http-client.js +201 -201
- package/server/services/telegram/translations.js +470 -470
- package/server/services/webhooks.js +216 -216
- package/server/sessionManager.js +225 -225
- package/server/setup-wizard.js +283 -283
- 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 +461 -461
- package/server/utils/plugin-process-manager.js +185 -185
- package/server/utils/port-access.js +227 -227
- package/server/utils/runtime-paths.js +37 -37
- package/server/utils/security-log.js +84 -84
- 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/server/setup-wizard.js
CHANGED
|
@@ -1,283 +1,283 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import os from 'os';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import net from 'node:net';
|
|
5
|
-
import readline from 'readline';
|
|
6
|
-
import { spawn } from 'node:child_process';
|
|
7
|
-
|
|
8
|
-
import { c } from './utils/colors.js';
|
|
9
|
-
|
|
10
|
-
const PIXCODE_DIR = path.join(os.homedir(), '.pixcode');
|
|
11
|
-
const SETUP_MARKER = path.join(PIXCODE_DIR, '.setup-complete');
|
|
12
|
-
const DEFAULT_PORT = 3001;
|
|
13
|
-
|
|
14
|
-
function isFirstRun() {
|
|
15
|
-
return !fs.existsSync(SETUP_MARKER);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function markSetupComplete(config) {
|
|
19
|
-
try {
|
|
20
|
-
fs.mkdirSync(PIXCODE_DIR, { recursive: true });
|
|
21
|
-
fs.writeFileSync(SETUP_MARKER, JSON.stringify({
|
|
22
|
-
completedAt: new Date().toISOString(),
|
|
23
|
-
...config,
|
|
24
|
-
}, null, 2));
|
|
25
|
-
} catch {
|
|
26
|
-
// Non-fatal — setup can complete without persistence
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function readSetupConfig() {
|
|
31
|
-
try {
|
|
32
|
-
return JSON.parse(fs.readFileSync(SETUP_MARKER, 'utf8'));
|
|
33
|
-
} catch {
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function isPortAvailable(port) {
|
|
39
|
-
return new Promise((resolve) => {
|
|
40
|
-
const tester = net.createServer();
|
|
41
|
-
tester.once('error', () => resolve(false));
|
|
42
|
-
tester.once('listening', () => {
|
|
43
|
-
tester.close(() => resolve(true));
|
|
44
|
-
});
|
|
45
|
-
tester.listen(Number(port), '0.0.0.0');
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async function findAvailablePort(startPort, maxAttempts = 10) {
|
|
50
|
-
for (let port = startPort; port < startPort + maxAttempts; port++) {
|
|
51
|
-
if (await isPortAvailable(port)) return port;
|
|
52
|
-
}
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function isPortOpen(port, timeoutMs = 800) {
|
|
57
|
-
return new Promise((resolve) => {
|
|
58
|
-
const socket = net.createConnection({ host: '127.0.0.1', port: Number(port) });
|
|
59
|
-
let settled = false;
|
|
60
|
-
const done = (value) => {
|
|
61
|
-
if (settled) return;
|
|
62
|
-
settled = true;
|
|
63
|
-
socket.destroy();
|
|
64
|
-
resolve(value);
|
|
65
|
-
};
|
|
66
|
-
socket.setTimeout(timeoutMs);
|
|
67
|
-
socket.once('connect', () => done(true));
|
|
68
|
-
socket.once('timeout', () => done(false));
|
|
69
|
-
socket.once('error', () => done(false));
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async function waitForServerReady(port, timeoutMs = 30000) {
|
|
74
|
-
const deadline = Date.now() + timeoutMs;
|
|
75
|
-
while (Date.now() < deadline) {
|
|
76
|
-
if (await isPortOpen(port)) return true;
|
|
77
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
78
|
-
}
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function ask(question) {
|
|
83
|
-
return new Promise((resolve) => {
|
|
84
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
85
|
-
resolve(null);
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
89
|
-
rl.question(question, (answer) => {
|
|
90
|
-
rl.close();
|
|
91
|
-
resolve(answer?.trim() || '');
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async function askSelect(question, options) {
|
|
97
|
-
while (true) {
|
|
98
|
-
const answer = await ask(question);
|
|
99
|
-
if (answer === null) return null;
|
|
100
|
-
const lower = answer.toLowerCase();
|
|
101
|
-
for (const opt of options) {
|
|
102
|
-
if (lower === opt.key || lower === String(opt.key)) return opt;
|
|
103
|
-
if (opt.aliases?.some(a => lower === a)) return opt;
|
|
104
|
-
}
|
|
105
|
-
console.log(`${c.warn(' Invalid choice. Please try again.')}`);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function openBrowser(url) {
|
|
110
|
-
const platform = process.platform;
|
|
111
|
-
// Skip on headless Linux (no DISPLAY or xdg-open not installed)
|
|
112
|
-
if (platform === 'linux') {
|
|
113
|
-
if (!process.env.DISPLAY || process.env.DISPLAY === '') return false;
|
|
114
|
-
try {
|
|
115
|
-
const { execSync } = require('child_process');
|
|
116
|
-
execSync('command -v xdg-open', { stdio: 'pipe' });
|
|
117
|
-
} catch {
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
let bin;
|
|
122
|
-
let args;
|
|
123
|
-
if (platform === 'darwin') {
|
|
124
|
-
bin = 'open';
|
|
125
|
-
args = [url];
|
|
126
|
-
} else if (platform === 'win32') {
|
|
127
|
-
bin = 'cmd';
|
|
128
|
-
args = ['/c', 'start', '', url];
|
|
129
|
-
} else {
|
|
130
|
-
bin = 'xdg-open';
|
|
131
|
-
args = [url];
|
|
132
|
-
}
|
|
133
|
-
try {
|
|
134
|
-
const child = spawn(bin, args, { stdio: 'ignore', timeout: 3000, detached: true, shell: false });
|
|
135
|
-
child.on('error', () => {});
|
|
136
|
-
child.unref();
|
|
137
|
-
return true;
|
|
138
|
-
} catch {
|
|
139
|
-
return false;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function printBanner() {
|
|
144
|
-
console.log('');
|
|
145
|
-
console.log(c.dim(' ╔═══════════════════════════════════════════════════════════════╗'));
|
|
146
|
-
console.log(c.dim(' ║') + c.bright(' Pixcode Setup Wizard ') + c.dim('║'));
|
|
147
|
-
console.log(c.dim(' ╠═══════════════════════════════════════════════════════════════╣'));
|
|
148
|
-
console.log(c.dim(' ║') + c.info(' Self-hosted AI coding agent control room ') + c.dim('║'));
|
|
149
|
-
console.log(c.dim(' ║') + c.dim(' Claude Code, Cursor, Codex, Gemini, Qwen... ') + c.dim('║'));
|
|
150
|
-
console.log(c.dim(' ╚═══════════════════════════════════════════════════════════════╝'));
|
|
151
|
-
console.log('');
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function printReady(port, displayIp = 'localhost') {
|
|
155
|
-
const url = `http://${displayIp}:${port}`;
|
|
156
|
-
console.log('');
|
|
157
|
-
console.log(c.dim(' ╔═══════════════════════════════════════════════════════════════╗'));
|
|
158
|
-
console.log(c.dim(' ║') + c.bright(' Pixcode is Ready! ') + c.dim('║'));
|
|
159
|
-
console.log(c.dim(' ╠═══════════════════════════════════════════════════════════════╣'));
|
|
160
|
-
console.log(c.dim(' ║') + ` Open ${c.bright(url)} in your browser ` + c.dim('║'));
|
|
161
|
-
console.log(c.dim(' ╚═══════════════════════════════════════════════════════════════╝'));
|
|
162
|
-
console.log('');
|
|
163
|
-
console.log(` ${c.info('[INFO]')} Web UI: ${c.bright(url)}`);
|
|
164
|
-
if (displayIp !== 'localhost') {
|
|
165
|
-
console.log(` ${c.info('[INFO]')} Local: ${c.dim(`http://localhost:${port}`)}`);
|
|
166
|
-
}
|
|
167
|
-
console.log(` ${c.info('[INFO]')} Health: ${c.dim(url + '/health')}`);
|
|
168
|
-
console.log('');
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export async function runSetupWizard(existingOptions = {}) {
|
|
172
|
-
if (!isFirstRun()) return null;
|
|
173
|
-
|
|
174
|
-
printBanner();
|
|
175
|
-
|
|
176
|
-
console.log(` ${c.info('Welcome!')} Let's get Pixcode configured.\n`);
|
|
177
|
-
|
|
178
|
-
// Step 1: Port selection
|
|
179
|
-
let port = existingOptions.serverPort || process.env.SERVER_PORT || DEFAULT_PORT;
|
|
180
|
-
const portAvailable = await isPortAvailable(port);
|
|
181
|
-
|
|
182
|
-
if (!portAvailable) {
|
|
183
|
-
console.log(` ${c.warn('[WARN]')} Port ${c.bright(String(port))} is already in use.`);
|
|
184
|
-
const altPort = await findAvailablePort(Number(port) + 1);
|
|
185
|
-
if (altPort) {
|
|
186
|
-
console.log(` ${c.info('[INFO]')} Port ${c.bright(String(altPort))} is available.`);
|
|
187
|
-
const choice = await askSelect(
|
|
188
|
-
` Use port ${altPort}? [Y/n] `,
|
|
189
|
-
[
|
|
190
|
-
{ key: 'y', label: 'yes', aliases: ['yes', '', 'e', 'evet'] },
|
|
191
|
-
{ key: 'n', label: 'no', aliases: ['no', 'n', 'h', 'hayir'] },
|
|
192
|
-
],
|
|
193
|
-
);
|
|
194
|
-
if (choice?.key !== 'n') {
|
|
195
|
-
port = altPort;
|
|
196
|
-
} else {
|
|
197
|
-
const customPort = await ask(` Enter a port number (or press Enter for ${altPort}): `);
|
|
198
|
-
if (customPort && /^\d+$/.test(customPort)) {
|
|
199
|
-
port = Number(customPort);
|
|
200
|
-
} else {
|
|
201
|
-
port = altPort;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
} else {
|
|
205
|
-
const customPort = await ask(` No free ports found near ${port}. Enter a port manually: `);
|
|
206
|
-
if (customPort && /^\d+$/.test(customPort)) {
|
|
207
|
-
port = Number(customPort);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
} else {
|
|
211
|
-
console.log(` ${c.ok('[OK]')} Port ${c.bright(String(port))} is available.`);
|
|
212
|
-
const customPort = await ask(` Press Enter to use port ${port}, or enter a different port: `);
|
|
213
|
-
if (customPort && /^\d+$/.test(customPort)) {
|
|
214
|
-
const newPort = Number(customPort);
|
|
215
|
-
if (await isPortAvailable(newPort)) {
|
|
216
|
-
port = newPort;
|
|
217
|
-
console.log(` ${c.ok('[OK]')} Port ${c.bright(String(port))} is available.`);
|
|
218
|
-
} else {
|
|
219
|
-
console.log(` ${c.warn('[WARN]')} Port ${newPort} is in use. Keeping port ${port}.`);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
process.env.SERVER_PORT = String(port);
|
|
225
|
-
|
|
226
|
-
// Step 2: Mode selection (Linux only)
|
|
227
|
-
let useDaemon = existingOptions.noDaemon ? false : (process.platform === 'linux');
|
|
228
|
-
|
|
229
|
-
if (process.platform === 'linux' && !existingOptions.noDaemon) {
|
|
230
|
-
console.log('');
|
|
231
|
-
const modeChoice = await askSelect(
|
|
232
|
-
` ${c.info('Run mode:')} Keep running in background (daemon) or foreground?\n` +
|
|
233
|
-
` ${c.dim(' [1]')} Daemon (recommended — survives terminal close)\n` +
|
|
234
|
-
` ${c.dim(' [2]')} Foreground (stops when terminal closes)\n` +
|
|
235
|
-
` Choose [1/2] (default 1): `,
|
|
236
|
-
[
|
|
237
|
-
{ key: '1', label: 'daemon', aliases: ['d', 'daemon', ''] },
|
|
238
|
-
{ key: '2', label: 'foreground', aliases: ['f', 'fg', 'foreground'] },
|
|
239
|
-
],
|
|
240
|
-
);
|
|
241
|
-
useDaemon = modeChoice?.key !== '2';
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Step 3: Summary
|
|
245
|
-
console.log('');
|
|
246
|
-
console.log(` ${c.info('[Setup]')} Configuration:`);
|
|
247
|
-
console.log(` Port: ${c.bright(String(port))}`);
|
|
248
|
-
if (process.platform === 'linux') {
|
|
249
|
-
console.log(` Mode: ${c.bright(useDaemon ? 'Daemon (background)' : 'Foreground')}`);
|
|
250
|
-
}
|
|
251
|
-
console.log('');
|
|
252
|
-
|
|
253
|
-
markSetupComplete({ port, mode: useDaemon ? 'daemon' : 'foreground' });
|
|
254
|
-
|
|
255
|
-
return { port, useDaemon };
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
export async function postStartupGuidance(port) {
|
|
259
|
-
const effectivePort = Number(port) || DEFAULT_PORT;
|
|
260
|
-
|
|
261
|
-
// Detect LAN/public IP for VPS display
|
|
262
|
-
let displayIp = 'localhost';
|
|
263
|
-
try {
|
|
264
|
-
const { getLanIps } = await import('./utils/port-access.js');
|
|
265
|
-
const ips = getLanIps();
|
|
266
|
-
if (ips.length > 0) displayIp = ips[0];
|
|
267
|
-
} catch { /* fallback to localhost */ }
|
|
268
|
-
|
|
269
|
-
const url = `http://${displayIp}:${effectivePort}`;
|
|
270
|
-
const localUrl = `http://localhost:${effectivePort}`;
|
|
271
|
-
|
|
272
|
-
printReady(effectivePort, displayIp);
|
|
273
|
-
|
|
274
|
-
console.log(` ${c.tip('[TIP]')} Open ${c.bright(url)} in your browser to start using Pixcode.`);
|
|
275
|
-
if (displayIp !== 'localhost') {
|
|
276
|
-
console.log(` ${c.dim(' Local: ' + localUrl)}`);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
console.log(` ${c.dim('Tip: Run "pixcode status" to see full configuration.')}`);
|
|
280
|
-
console.log('');
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
export { isFirstRun, isPortAvailable, findAvailablePort, openBrowser, waitForServerReady, isPortOpen };
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import net from 'node:net';
|
|
5
|
+
import readline from 'readline';
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
|
|
8
|
+
import { c } from './utils/colors.js';
|
|
9
|
+
|
|
10
|
+
const PIXCODE_DIR = path.join(os.homedir(), '.pixcode');
|
|
11
|
+
const SETUP_MARKER = path.join(PIXCODE_DIR, '.setup-complete');
|
|
12
|
+
const DEFAULT_PORT = 3001;
|
|
13
|
+
|
|
14
|
+
function isFirstRun() {
|
|
15
|
+
return !fs.existsSync(SETUP_MARKER);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function markSetupComplete(config) {
|
|
19
|
+
try {
|
|
20
|
+
fs.mkdirSync(PIXCODE_DIR, { recursive: true });
|
|
21
|
+
fs.writeFileSync(SETUP_MARKER, JSON.stringify({
|
|
22
|
+
completedAt: new Date().toISOString(),
|
|
23
|
+
...config,
|
|
24
|
+
}, null, 2));
|
|
25
|
+
} catch {
|
|
26
|
+
// Non-fatal — setup can complete without persistence
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function readSetupConfig() {
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(fs.readFileSync(SETUP_MARKER, 'utf8'));
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isPortAvailable(port) {
|
|
39
|
+
return new Promise((resolve) => {
|
|
40
|
+
const tester = net.createServer();
|
|
41
|
+
tester.once('error', () => resolve(false));
|
|
42
|
+
tester.once('listening', () => {
|
|
43
|
+
tester.close(() => resolve(true));
|
|
44
|
+
});
|
|
45
|
+
tester.listen(Number(port), '0.0.0.0');
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function findAvailablePort(startPort, maxAttempts = 10) {
|
|
50
|
+
for (let port = startPort; port < startPort + maxAttempts; port++) {
|
|
51
|
+
if (await isPortAvailable(port)) return port;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isPortOpen(port, timeoutMs = 800) {
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
const socket = net.createConnection({ host: '127.0.0.1', port: Number(port) });
|
|
59
|
+
let settled = false;
|
|
60
|
+
const done = (value) => {
|
|
61
|
+
if (settled) return;
|
|
62
|
+
settled = true;
|
|
63
|
+
socket.destroy();
|
|
64
|
+
resolve(value);
|
|
65
|
+
};
|
|
66
|
+
socket.setTimeout(timeoutMs);
|
|
67
|
+
socket.once('connect', () => done(true));
|
|
68
|
+
socket.once('timeout', () => done(false));
|
|
69
|
+
socket.once('error', () => done(false));
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function waitForServerReady(port, timeoutMs = 30000) {
|
|
74
|
+
const deadline = Date.now() + timeoutMs;
|
|
75
|
+
while (Date.now() < deadline) {
|
|
76
|
+
if (await isPortOpen(port)) return true;
|
|
77
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function ask(question) {
|
|
83
|
+
return new Promise((resolve) => {
|
|
84
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
85
|
+
resolve(null);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
89
|
+
rl.question(question, (answer) => {
|
|
90
|
+
rl.close();
|
|
91
|
+
resolve(answer?.trim() || '');
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function askSelect(question, options) {
|
|
97
|
+
while (true) {
|
|
98
|
+
const answer = await ask(question);
|
|
99
|
+
if (answer === null) return null;
|
|
100
|
+
const lower = answer.toLowerCase();
|
|
101
|
+
for (const opt of options) {
|
|
102
|
+
if (lower === opt.key || lower === String(opt.key)) return opt;
|
|
103
|
+
if (opt.aliases?.some(a => lower === a)) return opt;
|
|
104
|
+
}
|
|
105
|
+
console.log(`${c.warn(' Invalid choice. Please try again.')}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function openBrowser(url) {
|
|
110
|
+
const platform = process.platform;
|
|
111
|
+
// Skip on headless Linux (no DISPLAY or xdg-open not installed)
|
|
112
|
+
if (platform === 'linux') {
|
|
113
|
+
if (!process.env.DISPLAY || process.env.DISPLAY === '') return false;
|
|
114
|
+
try {
|
|
115
|
+
const { execSync } = require('child_process');
|
|
116
|
+
execSync('command -v xdg-open', { stdio: 'pipe' });
|
|
117
|
+
} catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
let bin;
|
|
122
|
+
let args;
|
|
123
|
+
if (platform === 'darwin') {
|
|
124
|
+
bin = 'open';
|
|
125
|
+
args = [url];
|
|
126
|
+
} else if (platform === 'win32') {
|
|
127
|
+
bin = 'cmd';
|
|
128
|
+
args = ['/c', 'start', '', url];
|
|
129
|
+
} else {
|
|
130
|
+
bin = 'xdg-open';
|
|
131
|
+
args = [url];
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const child = spawn(bin, args, { stdio: 'ignore', timeout: 3000, detached: true, shell: false });
|
|
135
|
+
child.on('error', () => {});
|
|
136
|
+
child.unref();
|
|
137
|
+
return true;
|
|
138
|
+
} catch {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function printBanner() {
|
|
144
|
+
console.log('');
|
|
145
|
+
console.log(c.dim(' ╔═══════════════════════════════════════════════════════════════╗'));
|
|
146
|
+
console.log(c.dim(' ║') + c.bright(' Pixcode Setup Wizard ') + c.dim('║'));
|
|
147
|
+
console.log(c.dim(' ╠═══════════════════════════════════════════════════════════════╣'));
|
|
148
|
+
console.log(c.dim(' ║') + c.info(' Self-hosted AI coding agent control room ') + c.dim('║'));
|
|
149
|
+
console.log(c.dim(' ║') + c.dim(' Claude Code, Cursor, Codex, Gemini, Qwen... ') + c.dim('║'));
|
|
150
|
+
console.log(c.dim(' ╚═══════════════════════════════════════════════════════════════╝'));
|
|
151
|
+
console.log('');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function printReady(port, displayIp = 'localhost') {
|
|
155
|
+
const url = `http://${displayIp}:${port}`;
|
|
156
|
+
console.log('');
|
|
157
|
+
console.log(c.dim(' ╔═══════════════════════════════════════════════════════════════╗'));
|
|
158
|
+
console.log(c.dim(' ║') + c.bright(' Pixcode is Ready! ') + c.dim('║'));
|
|
159
|
+
console.log(c.dim(' ╠═══════════════════════════════════════════════════════════════╣'));
|
|
160
|
+
console.log(c.dim(' ║') + ` Open ${c.bright(url)} in your browser ` + c.dim('║'));
|
|
161
|
+
console.log(c.dim(' ╚═══════════════════════════════════════════════════════════════╝'));
|
|
162
|
+
console.log('');
|
|
163
|
+
console.log(` ${c.info('[INFO]')} Web UI: ${c.bright(url)}`);
|
|
164
|
+
if (displayIp !== 'localhost') {
|
|
165
|
+
console.log(` ${c.info('[INFO]')} Local: ${c.dim(`http://localhost:${port}`)}`);
|
|
166
|
+
}
|
|
167
|
+
console.log(` ${c.info('[INFO]')} Health: ${c.dim(url + '/health')}`);
|
|
168
|
+
console.log('');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export async function runSetupWizard(existingOptions = {}) {
|
|
172
|
+
if (!isFirstRun()) return null;
|
|
173
|
+
|
|
174
|
+
printBanner();
|
|
175
|
+
|
|
176
|
+
console.log(` ${c.info('Welcome!')} Let's get Pixcode configured.\n`);
|
|
177
|
+
|
|
178
|
+
// Step 1: Port selection
|
|
179
|
+
let port = existingOptions.serverPort || process.env.SERVER_PORT || DEFAULT_PORT;
|
|
180
|
+
const portAvailable = await isPortAvailable(port);
|
|
181
|
+
|
|
182
|
+
if (!portAvailable) {
|
|
183
|
+
console.log(` ${c.warn('[WARN]')} Port ${c.bright(String(port))} is already in use.`);
|
|
184
|
+
const altPort = await findAvailablePort(Number(port) + 1);
|
|
185
|
+
if (altPort) {
|
|
186
|
+
console.log(` ${c.info('[INFO]')} Port ${c.bright(String(altPort))} is available.`);
|
|
187
|
+
const choice = await askSelect(
|
|
188
|
+
` Use port ${altPort}? [Y/n] `,
|
|
189
|
+
[
|
|
190
|
+
{ key: 'y', label: 'yes', aliases: ['yes', '', 'e', 'evet'] },
|
|
191
|
+
{ key: 'n', label: 'no', aliases: ['no', 'n', 'h', 'hayir'] },
|
|
192
|
+
],
|
|
193
|
+
);
|
|
194
|
+
if (choice?.key !== 'n') {
|
|
195
|
+
port = altPort;
|
|
196
|
+
} else {
|
|
197
|
+
const customPort = await ask(` Enter a port number (or press Enter for ${altPort}): `);
|
|
198
|
+
if (customPort && /^\d+$/.test(customPort)) {
|
|
199
|
+
port = Number(customPort);
|
|
200
|
+
} else {
|
|
201
|
+
port = altPort;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
const customPort = await ask(` No free ports found near ${port}. Enter a port manually: `);
|
|
206
|
+
if (customPort && /^\d+$/.test(customPort)) {
|
|
207
|
+
port = Number(customPort);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
console.log(` ${c.ok('[OK]')} Port ${c.bright(String(port))} is available.`);
|
|
212
|
+
const customPort = await ask(` Press Enter to use port ${port}, or enter a different port: `);
|
|
213
|
+
if (customPort && /^\d+$/.test(customPort)) {
|
|
214
|
+
const newPort = Number(customPort);
|
|
215
|
+
if (await isPortAvailable(newPort)) {
|
|
216
|
+
port = newPort;
|
|
217
|
+
console.log(` ${c.ok('[OK]')} Port ${c.bright(String(port))} is available.`);
|
|
218
|
+
} else {
|
|
219
|
+
console.log(` ${c.warn('[WARN]')} Port ${newPort} is in use. Keeping port ${port}.`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
process.env.SERVER_PORT = String(port);
|
|
225
|
+
|
|
226
|
+
// Step 2: Mode selection (Linux only)
|
|
227
|
+
let useDaemon = existingOptions.noDaemon ? false : (process.platform === 'linux');
|
|
228
|
+
|
|
229
|
+
if (process.platform === 'linux' && !existingOptions.noDaemon) {
|
|
230
|
+
console.log('');
|
|
231
|
+
const modeChoice = await askSelect(
|
|
232
|
+
` ${c.info('Run mode:')} Keep running in background (daemon) or foreground?\n` +
|
|
233
|
+
` ${c.dim(' [1]')} Daemon (recommended — survives terminal close)\n` +
|
|
234
|
+
` ${c.dim(' [2]')} Foreground (stops when terminal closes)\n` +
|
|
235
|
+
` Choose [1/2] (default 1): `,
|
|
236
|
+
[
|
|
237
|
+
{ key: '1', label: 'daemon', aliases: ['d', 'daemon', ''] },
|
|
238
|
+
{ key: '2', label: 'foreground', aliases: ['f', 'fg', 'foreground'] },
|
|
239
|
+
],
|
|
240
|
+
);
|
|
241
|
+
useDaemon = modeChoice?.key !== '2';
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Step 3: Summary
|
|
245
|
+
console.log('');
|
|
246
|
+
console.log(` ${c.info('[Setup]')} Configuration:`);
|
|
247
|
+
console.log(` Port: ${c.bright(String(port))}`);
|
|
248
|
+
if (process.platform === 'linux') {
|
|
249
|
+
console.log(` Mode: ${c.bright(useDaemon ? 'Daemon (background)' : 'Foreground')}`);
|
|
250
|
+
}
|
|
251
|
+
console.log('');
|
|
252
|
+
|
|
253
|
+
markSetupComplete({ port, mode: useDaemon ? 'daemon' : 'foreground' });
|
|
254
|
+
|
|
255
|
+
return { port, useDaemon };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export async function postStartupGuidance(port) {
|
|
259
|
+
const effectivePort = Number(port) || DEFAULT_PORT;
|
|
260
|
+
|
|
261
|
+
// Detect LAN/public IP for VPS display
|
|
262
|
+
let displayIp = 'localhost';
|
|
263
|
+
try {
|
|
264
|
+
const { getLanIps } = await import('./utils/port-access.js');
|
|
265
|
+
const ips = getLanIps();
|
|
266
|
+
if (ips.length > 0) displayIp = ips[0];
|
|
267
|
+
} catch { /* fallback to localhost */ }
|
|
268
|
+
|
|
269
|
+
const url = `http://${displayIp}:${effectivePort}`;
|
|
270
|
+
const localUrl = `http://localhost:${effectivePort}`;
|
|
271
|
+
|
|
272
|
+
printReady(effectivePort, displayIp);
|
|
273
|
+
|
|
274
|
+
console.log(` ${c.tip('[TIP]')} Open ${c.bright(url)} in your browser to start using Pixcode.`);
|
|
275
|
+
if (displayIp !== 'localhost') {
|
|
276
|
+
console.log(` ${c.dim(' Local: ' + localUrl)}`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
console.log(` ${c.dim('Tip: Run "pixcode status" to see full configuration.')}`);
|
|
280
|
+
console.log('');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export { isFirstRun, isPortAvailable, findAvailablePort, openBrowser, waitForServerReady, isPortOpen };
|