@castlekit/castle 0.4.0 → 0.4.2
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/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +2 -2
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error.html +2 -2
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/api/avatars/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/agents/[id]/avatar/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/agents/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/agents/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/chat/attachments/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/chat/channels/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/chat/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/chat/search/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/chat/storage/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/config/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/events/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/logs/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/ping/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/session/context/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/session/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/sessions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/avatar/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/chat.html +1 -1
- package/.next/standalone/.next/server/app/chat.rsc +1 -1
- package/.next/standalone/.next/server/app/chat.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/chat.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/chat.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/chat.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/chat.segments/chat/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/chat.segments/chat.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings.html +1 -1
- package/.next/standalone/.next/server/app/settings.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/settings.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/ui-kit.html +1 -1
- package/.next/standalone/.next/server/app/ui-kit.rsc +1 -1
- package/.next/standalone/.next/server/app/ui-kit.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/ui-kit.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/ui-kit.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/ui-kit.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/ui-kit.segments/ui-kit/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/ui-kit.segments/ui-kit.segment.rsc +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_7a67de23._.js +1 -1
- package/.next/standalone/.next/server/middleware-manifest.json +5 -5
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +2 -2
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/{static/chunks/2b48886a383c2e37.js → standalone/.next/static/chunks/8c691cfaccdbb9ee.js} +1 -1
- package/.next/standalone/CHANGELOG.md +28 -0
- package/.next/standalone/bin/castle.js +2 -2
- package/.next/standalone/install.ps1 +437 -0
- package/.next/standalone/install.sh +4 -0
- package/.next/standalone/package.json +2 -1
- package/.next/standalone/src/app/page.tsx +3 -3
- package/.next/standalone/src/cli/onboarding.ts +178 -102
- package/.next/{standalone/.next/static/chunks/2b48886a383c2e37.js → static/chunks/8c691cfaccdbb9ee.js} +1 -1
- package/bin/castle.js +2 -2
- package/install.ps1 +437 -0
- package/install.sh +4 -0
- package/package.json +2 -1
- package/src/app/page.tsx +3 -3
- package/src/cli/onboarding.ts +178 -102
- /package/.next/standalone/.next/static/{R01Vc6rRTS_XwAg0Y8ERm → -R3HvmmPKzJ9Jfjg7hRqd}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{R01Vc6rRTS_XwAg0Y8ERm → -R3HvmmPKzJ9Jfjg7hRqd}/_clientMiddlewareManifest.json +0 -0
- /package/.next/standalone/.next/static/{R01Vc6rRTS_XwAg0Y8ERm → -R3HvmmPKzJ9Jfjg7hRqd}/_ssgManifest.js +0 -0
- /package/.next/static/{R01Vc6rRTS_XwAg0Y8ERm → -R3HvmmPKzJ9Jfjg7hRqd}/_buildManifest.js +0 -0
- /package/.next/static/{R01Vc6rRTS_XwAg0Y8ERm → -R3HvmmPKzJ9Jfjg7hRqd}/_clientMiddlewareManifest.json +0 -0
- /package/.next/static/{R01Vc6rRTS_XwAg0Y8ERm → -R3HvmmPKzJ9Jfjg7hRqd}/_ssgManifest.js +0 -0
package/src/cli/onboarding.ts
CHANGED
|
@@ -329,6 +329,7 @@ export async function runOnboarding(): Promise<void> {
|
|
|
329
329
|
}
|
|
330
330
|
|
|
331
331
|
// Step 1: Check for OpenClaw
|
|
332
|
+
let openclawSkipped = false;
|
|
332
333
|
const openclawSpinner = p.spinner();
|
|
333
334
|
openclawSpinner.start("Checking for OpenClaw...");
|
|
334
335
|
|
|
@@ -355,32 +356,30 @@ export async function runOnboarding(): Promise<void> {
|
|
|
355
356
|
const installSpinner = p.spinner();
|
|
356
357
|
installSpinner.start("Installing OpenClaw...");
|
|
357
358
|
|
|
359
|
+
const installCmd = process.platform === "win32"
|
|
360
|
+
? 'powershell -NoProfile -ExecutionPolicy Bypass -Command "iwr -useb https://openclaw.ai/install.ps1 -OutFile $env:TEMP\\openclaw-install.ps1; & $env:TEMP\\openclaw-install.ps1 -NoOnboard"'
|
|
361
|
+
: 'curl -fsSL --proto "=https" --tlsv1.2 https://openclaw.ai/install.sh | bash -s -- --no-onboard --no-prompt';
|
|
362
|
+
|
|
358
363
|
try {
|
|
359
|
-
execSync(
|
|
360
|
-
'curl -fsSL --proto "=https" --tlsv1.2 https://openclaw.ai/install.sh | bash -s -- --no-onboard --no-prompt',
|
|
361
|
-
{ stdio: "pipe", timeout: 120000 }
|
|
362
|
-
);
|
|
364
|
+
execSync(installCmd, { stdio: "inherit", timeout: 300000 });
|
|
363
365
|
installSpinner.stop(BLUE("✔ OpenClaw installed"));
|
|
364
366
|
} catch (error) {
|
|
367
|
+
openclawSkipped = true;
|
|
365
368
|
installSpinner.stop(pc.red("OpenClaw installation failed"));
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
369
|
+
const manualCmd = process.platform === "win32"
|
|
370
|
+
? "iwr -useb https://openclaw.ai/install.ps1 | iex"
|
|
371
|
+
: "curl -fsSL https://openclaw.ai/install.sh | bash";
|
|
372
|
+
p.log.warn(
|
|
373
|
+
`You can install OpenClaw later:\n${BLUE_LIGHT(manualCmd)}`
|
|
371
374
|
);
|
|
372
|
-
p.outro("Come back when OpenClaw is installed!");
|
|
373
|
-
process.exit(1);
|
|
374
375
|
}
|
|
375
376
|
} else {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
);
|
|
382
|
-
p.outro("See you soon!");
|
|
383
|
-
process.exit(0);
|
|
377
|
+
openclawSkipped = true;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (openclawSkipped && !isOpenClawInstalled()) {
|
|
381
|
+
// Skip Gateway config — use defaults and continue to build/start Castle
|
|
382
|
+
p.log.message(pc.dim("Using default settings — you can reconfigure later with castle setup"));
|
|
384
383
|
}
|
|
385
384
|
} else {
|
|
386
385
|
// Auto-detect token and agents in one go
|
|
@@ -413,34 +412,49 @@ export async function runOnboarding(): Promise<void> {
|
|
|
413
412
|
let token = readOpenClawToken();
|
|
414
413
|
let gatewayUrl: string | undefined;
|
|
415
414
|
let isRemote = false;
|
|
415
|
+
let primaryAgent = "assistant";
|
|
416
|
+
let agents: DiscoveredAgent[] = [];
|
|
417
|
+
|
|
418
|
+
if (!openclawSkipped) {
|
|
419
|
+
// If we have auto-detected config, offer a choice
|
|
420
|
+
const hasLocalConfig = !!readOpenClawPort() || isOpenClawInstalled();
|
|
421
|
+
|
|
422
|
+
if (hasLocalConfig && token) {
|
|
423
|
+
// Both auto-detect and manual are available
|
|
424
|
+
const connectionMode = await p.select({
|
|
425
|
+
message: "How would you like to connect?",
|
|
426
|
+
options: [
|
|
427
|
+
{
|
|
428
|
+
value: "auto",
|
|
429
|
+
label: `Auto-detected local Gateway ${pc.dim(`(port ${port})`)}`,
|
|
430
|
+
hint: "Recommended for local setups",
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
value: "manual",
|
|
434
|
+
label: "Enter Gateway details manually",
|
|
435
|
+
hint: "For remote, Tailscale, or custom setups",
|
|
436
|
+
},
|
|
437
|
+
],
|
|
438
|
+
});
|
|
416
439
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
// Both auto-detect and manual are available
|
|
422
|
-
const connectionMode = await p.select({
|
|
423
|
-
message: "How would you like to connect?",
|
|
424
|
-
options: [
|
|
425
|
-
{
|
|
426
|
-
value: "auto",
|
|
427
|
-
label: `Auto-detected local Gateway ${pc.dim(`(port ${port})`)}`,
|
|
428
|
-
hint: "Recommended for local setups",
|
|
429
|
-
},
|
|
430
|
-
{
|
|
431
|
-
value: "manual",
|
|
432
|
-
label: "Enter Gateway details manually",
|
|
433
|
-
hint: "For remote, Tailscale, or custom setups",
|
|
434
|
-
},
|
|
435
|
-
],
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
if (p.isCancel(connectionMode)) {
|
|
439
|
-
p.cancel("Setup cancelled.");
|
|
440
|
-
process.exit(0);
|
|
441
|
-
}
|
|
440
|
+
if (p.isCancel(connectionMode)) {
|
|
441
|
+
p.cancel("Setup cancelled.");
|
|
442
|
+
process.exit(0);
|
|
443
|
+
}
|
|
442
444
|
|
|
443
|
-
|
|
445
|
+
if (connectionMode === "manual") {
|
|
446
|
+
const manualResult = await promptManualGateway();
|
|
447
|
+
if (!manualResult) {
|
|
448
|
+
p.cancel("Setup cancelled.");
|
|
449
|
+
process.exit(0);
|
|
450
|
+
}
|
|
451
|
+
port = manualResult.port;
|
|
452
|
+
token = manualResult.token;
|
|
453
|
+
gatewayUrl = manualResult.gatewayUrl;
|
|
454
|
+
isRemote = manualResult.isRemote;
|
|
455
|
+
}
|
|
456
|
+
} else if (!token) {
|
|
457
|
+
// No auto-detected token — fall through to manual entry
|
|
444
458
|
const manualResult = await promptManualGateway();
|
|
445
459
|
if (!manualResult) {
|
|
446
460
|
p.cancel("Setup cancelled.");
|
|
@@ -451,57 +465,44 @@ export async function runOnboarding(): Promise<void> {
|
|
|
451
465
|
gatewayUrl = manualResult.gatewayUrl;
|
|
452
466
|
isRemote = manualResult.isRemote;
|
|
453
467
|
}
|
|
454
|
-
} else if (!token) {
|
|
455
|
-
// No auto-detected token — fall through to manual entry
|
|
456
|
-
const manualResult = await promptManualGateway();
|
|
457
|
-
if (!manualResult) {
|
|
458
|
-
p.cancel("Setup cancelled.");
|
|
459
|
-
process.exit(0);
|
|
460
|
-
}
|
|
461
|
-
port = manualResult.port;
|
|
462
|
-
token = manualResult.token;
|
|
463
|
-
gatewayUrl = manualResult.gatewayUrl;
|
|
464
|
-
isRemote = manualResult.isRemote;
|
|
465
|
-
}
|
|
466
468
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
469
|
+
// Step 3: Agent Discovery (use URL if remote, port if local)
|
|
470
|
+
const agentTarget = gatewayUrl || port;
|
|
471
|
+
agents = await discoverAgents(agentTarget, token);
|
|
470
472
|
|
|
471
|
-
|
|
473
|
+
if (agents.length > 0) {
|
|
472
474
|
|
|
473
|
-
|
|
475
|
+
const selectedAgent = await p.select({
|
|
476
|
+
message: "Choose your primary agent",
|
|
477
|
+
options: agents.map((a) => ({
|
|
478
|
+
value: a.id,
|
|
479
|
+
label: `${a.name} ${pc.dim(`<${a.id}>`)}`,
|
|
480
|
+
hint: a.description || undefined,
|
|
481
|
+
})),
|
|
482
|
+
});
|
|
474
483
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
label: `${a.name} ${pc.dim(`<${a.id}>`)}`,
|
|
480
|
-
hint: a.description || undefined,
|
|
481
|
-
})),
|
|
482
|
-
});
|
|
484
|
+
if (p.isCancel(selectedAgent)) {
|
|
485
|
+
p.cancel("Setup cancelled.");
|
|
486
|
+
process.exit(0);
|
|
487
|
+
}
|
|
483
488
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
489
|
+
primaryAgent = selectedAgent as string;
|
|
490
|
+
} else {
|
|
491
|
+
const setPrimary = await p.text({
|
|
492
|
+
message: "Enter the name of your primary agent",
|
|
493
|
+
initialValue: "assistant",
|
|
494
|
+
validate(value: string | undefined) {
|
|
495
|
+
if (!value?.trim()) return "Agent name is required";
|
|
496
|
+
},
|
|
497
|
+
});
|
|
488
498
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
initialValue: "assistant",
|
|
494
|
-
validate(value: string | undefined) {
|
|
495
|
-
if (!value?.trim()) return "Agent name is required";
|
|
496
|
-
},
|
|
497
|
-
});
|
|
499
|
+
if (p.isCancel(setPrimary)) {
|
|
500
|
+
p.cancel("Setup cancelled.");
|
|
501
|
+
process.exit(0);
|
|
502
|
+
}
|
|
498
503
|
|
|
499
|
-
|
|
500
|
-
p.cancel("Setup cancelled.");
|
|
501
|
-
process.exit(0);
|
|
504
|
+
primaryAgent = setPrimary as string;
|
|
502
505
|
}
|
|
503
|
-
|
|
504
|
-
primaryAgent = setPrimary as string;
|
|
505
506
|
}
|
|
506
507
|
|
|
507
508
|
// Step 5: Create Castle config
|
|
@@ -619,12 +620,37 @@ export async function runOnboarding(): Promise<void> {
|
|
|
619
620
|
|
|
620
621
|
// Write PID file helper
|
|
621
622
|
const pidFile = join(castleDir, "server.pid");
|
|
623
|
+
const isWin = process.platform === "win32";
|
|
624
|
+
|
|
625
|
+
// Stop existing service FIRST — otherwise the service manager respawns
|
|
626
|
+
// the old server immediately after we kill it, stealing the port.
|
|
627
|
+
if (process.platform === "darwin") {
|
|
628
|
+
const plistPath = join(home(), "Library", "LaunchAgents", "com.castlekit.castle.plist");
|
|
629
|
+
try {
|
|
630
|
+
execSyncChild(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "ignore", timeout: 10000 });
|
|
631
|
+
} catch { /* no existing service */ }
|
|
632
|
+
} else if (process.platform === "linux") {
|
|
633
|
+
try {
|
|
634
|
+
execSyncChild("systemctl --user stop castle.service 2>/dev/null", { stdio: "ignore", timeout: 10000 });
|
|
635
|
+
} catch { /* no existing service */ }
|
|
636
|
+
} else if (isWin) {
|
|
637
|
+
try {
|
|
638
|
+
execSyncChild('schtasks /End /TN "CastleServer" 2>nul', { stdio: "ignore", timeout: 10000 });
|
|
639
|
+
} catch { /* no existing task */ }
|
|
640
|
+
try {
|
|
641
|
+
execSyncChild('schtasks /Delete /TN "CastleServer" /F 2>nul', { stdio: "ignore", timeout: 10000 });
|
|
642
|
+
} catch { /* no existing task */ }
|
|
643
|
+
}
|
|
622
644
|
|
|
623
645
|
// Kill any existing Castle server (by PID file)
|
|
624
646
|
try {
|
|
625
647
|
const existingPid = parseInt(readF(pidFile, "utf-8").trim(), 10);
|
|
626
648
|
if (Number.isInteger(existingPid) && existingPid > 0) {
|
|
627
|
-
|
|
649
|
+
if (isWin) {
|
|
650
|
+
try { execSyncChild(`taskkill /PID ${existingPid} /F 2>nul`, { stdio: "ignore", timeout: 5000 }); } catch { /* ignore */ }
|
|
651
|
+
} else {
|
|
652
|
+
process.kill(existingPid);
|
|
653
|
+
}
|
|
628
654
|
for (let i = 0; i < 30; i++) {
|
|
629
655
|
try {
|
|
630
656
|
process.kill(existingPid, 0);
|
|
@@ -639,14 +665,35 @@ export async function runOnboarding(): Promise<void> {
|
|
|
639
665
|
}
|
|
640
666
|
|
|
641
667
|
// Kill anything else on the target port
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
668
|
+
if (isWin) {
|
|
669
|
+
try {
|
|
670
|
+
const netstatOut = execSyncChild(
|
|
671
|
+
`netstat -ano | findstr ":${castlePort} " | findstr "LISTENING"`,
|
|
672
|
+
{ encoding: "utf-8", timeout: 5000 }
|
|
673
|
+
).toString();
|
|
674
|
+
const pids = new Set<string>();
|
|
675
|
+
for (const line of netstatOut.split("\n")) {
|
|
676
|
+
const parts = line.trim().split(/\s+/);
|
|
677
|
+
const pid = parts[parts.length - 1];
|
|
678
|
+
if (pid && /^\d+$/.test(pid) && pid !== "0") pids.add(pid);
|
|
679
|
+
}
|
|
680
|
+
for (const pid of pids) {
|
|
681
|
+
try { execSyncChild(`taskkill /PID ${pid} /F 2>nul`, { stdio: "ignore", timeout: 5000 }); } catch { /* ignore */ }
|
|
682
|
+
}
|
|
683
|
+
if (pids.size > 0) await new Promise((r) => setTimeout(r, 500));
|
|
684
|
+
} catch {
|
|
685
|
+
// Nothing on port or netstat not available
|
|
686
|
+
}
|
|
687
|
+
} else {
|
|
688
|
+
try {
|
|
689
|
+
execSyncChild(`lsof -ti:${castlePort} | xargs kill -9 2>/dev/null`, {
|
|
690
|
+
stdio: "ignore",
|
|
691
|
+
timeout: 5000,
|
|
692
|
+
});
|
|
693
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
694
|
+
} catch {
|
|
695
|
+
// Nothing on port or lsof not available
|
|
696
|
+
}
|
|
650
697
|
}
|
|
651
698
|
|
|
652
699
|
// Escape XML special characters for plist values
|
|
@@ -693,9 +740,7 @@ ${plistEnvEntries}
|
|
|
693
740
|
</dict>
|
|
694
741
|
</dict>
|
|
695
742
|
</plist>`;
|
|
696
|
-
|
|
697
|
-
execSyncChild(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "ignore", timeout: 10000 });
|
|
698
|
-
} catch { /* ignore */ }
|
|
743
|
+
// Service was already unloaded above — just write new plist and load
|
|
699
744
|
writeFile(plistPath, plist);
|
|
700
745
|
try {
|
|
701
746
|
execSyncChild(`launchctl load "${plistPath}"`, { stdio: "ignore", timeout: 10000 });
|
|
@@ -730,6 +775,32 @@ WantedBy=default.target
|
|
|
730
775
|
} catch {
|
|
731
776
|
// Non-fatal
|
|
732
777
|
}
|
|
778
|
+
} else if (isWin) {
|
|
779
|
+
// Windows: use Task Scheduler to run Castle at logon
|
|
780
|
+
// Build a batch wrapper that sets environment variables and starts the server
|
|
781
|
+
const batPath = join(castleDir, "start-server.bat");
|
|
782
|
+
const envLines = Object.entries(serverEnv)
|
|
783
|
+
.map(([k, v]) => `set "${k}=${v}"`)
|
|
784
|
+
.join("\r\n");
|
|
785
|
+
const batContent = `@echo off\r\n${envLines}\r\ncd /d "${PROJECT_ROOT}"\r\n"${nodePath}" ${serverArgs.map((a) => `"${a}"`).join(" ")}\r\n`;
|
|
786
|
+
writeFile(batPath, batContent);
|
|
787
|
+
|
|
788
|
+
// Task was already deleted above — create fresh
|
|
789
|
+
try {
|
|
790
|
+
execSyncChild(
|
|
791
|
+
`schtasks /Create /TN "CastleServer" /TR "\\"${batPath}\\"" /SC ONLOGON /RL HIGHEST /F 2>nul`,
|
|
792
|
+
{ stdio: "ignore", timeout: 10000 }
|
|
793
|
+
);
|
|
794
|
+
} catch {
|
|
795
|
+
// Non-fatal — fall back to spawning directly
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// Also start the task now
|
|
799
|
+
try {
|
|
800
|
+
execSyncChild('schtasks /Run /TN "CastleServer" 2>nul', { stdio: "ignore", timeout: 10000 });
|
|
801
|
+
} catch {
|
|
802
|
+
// Non-fatal
|
|
803
|
+
}
|
|
733
804
|
}
|
|
734
805
|
|
|
735
806
|
// If no service manager started it, spawn directly
|
|
@@ -805,6 +876,11 @@ WantedBy=default.target
|
|
|
805
876
|
}
|
|
806
877
|
|
|
807
878
|
p.outro(pc.dim(`Opening ${BLUE(`http://localhost:${castlePort}`)}...`));
|
|
808
|
-
const
|
|
809
|
-
|
|
879
|
+
const url = `http://localhost:${castlePort}`;
|
|
880
|
+
if (process.platform === "win32") {
|
|
881
|
+
execSync(`start "" "${url}"`, { stdio: "ignore" });
|
|
882
|
+
} else {
|
|
883
|
+
const open = (await import("open")).default;
|
|
884
|
+
await open(url);
|
|
885
|
+
}
|
|
810
886
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/.next/static/{R01Vc6rRTS_XwAg0Y8ERm → -R3HvmmPKzJ9Jfjg7hRqd}/_clientMiddlewareManifest.json
RENAMED
|
File without changes
|
|
File without changes
|