valheim-oz-dsm 1.5.4 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -10,10 +10,20 @@ var __export = (target, all) => {
10
10
  };
11
11
 
12
12
  // src/rcon/types.ts
13
- var RconError;
13
+ var PacketType, RconError, ValheimCommands, ValheimEvents, ValheimGlobalKeys;
14
14
  var init_types = __esm({
15
15
  "src/rcon/types.ts"() {
16
16
  "use strict";
17
+ PacketType = {
18
+ /** Authentication request */
19
+ SERVERDATA_AUTH: 3,
20
+ /** Authentication response */
21
+ SERVERDATA_AUTH_RESPONSE: 2,
22
+ /** Execute command */
23
+ SERVERDATA_EXECCOMMAND: 2,
24
+ /** Command response */
25
+ SERVERDATA_RESPONSE_VALUE: 0
26
+ };
17
27
  RconError = class extends Error {
18
28
  constructor(code, message) {
19
29
  super(message);
@@ -21,6 +31,81 @@ var init_types = __esm({
21
31
  this.name = "RconError";
22
32
  }
23
33
  };
34
+ ValheimCommands = {
35
+ // Server Management
36
+ /** Force save world */
37
+ SAVE: "save",
38
+ /** Get server info */
39
+ INFO: "info",
40
+ /** Ping server */
41
+ PING: "ping",
42
+ // Player Management
43
+ /** Kick player */
44
+ kick: (player) => `kick ${player}`,
45
+ /** Ban player */
46
+ ban: (player) => `ban ${player}`,
47
+ /** Unban player */
48
+ unban: (player) => `unban ${player}`,
49
+ /** List banned players */
50
+ BANNED: "banned",
51
+ /** List permitted players */
52
+ PERMITTED: "permitted",
53
+ // Time Control
54
+ /** Sleep through night (skip to morning) */
55
+ SLEEP: "sleep",
56
+ /** Skip time by seconds */
57
+ skiptime: (seconds) => `skiptime ${seconds}`,
58
+ // Events
59
+ /** Trigger specific event */
60
+ event: (eventName) => `event ${eventName}`,
61
+ /** Trigger random event */
62
+ RANDOMEVENT: "randomevent",
63
+ /** Stop current event */
64
+ STOPEVENT: "stopevent",
65
+ // World Management
66
+ /** Remove all dropped items */
67
+ REMOVEDROPS: "removedrops",
68
+ /** Set a global key */
69
+ setkey: (key) => `setkey ${key}`,
70
+ /** Remove a global key */
71
+ removekey: (key) => `removekey ${key}`,
72
+ /** Reset all global keys */
73
+ RESETKEYS: "resetkeys",
74
+ /** List all global keys */
75
+ LISTKEYS: "listkeys",
76
+ // Performance
77
+ /** Set LOD bias (0-5, lower = better performance) */
78
+ lodbias: (value) => `lodbias ${value}`,
79
+ /** Set LOD distance (100-6000) */
80
+ loddist: (value) => `loddist ${value}`
81
+ };
82
+ ValheimEvents = {
83
+ ARMY_EIKTHYR: "army_eikthyr",
84
+ ARMY_THEELDER: "army_theelder",
85
+ ARMY_BONEMASS: "army_bonemass",
86
+ ARMY_MODER: "army_moder",
87
+ ARMY_GOBLIN: "army_goblin",
88
+ FORESTTROLLS: "foresttrolls",
89
+ SKELETONS: "skeletons",
90
+ BLOBS: "blobs",
91
+ WOLVES: "wolves",
92
+ BATS: "bats",
93
+ SERPENTS: "serpents"
94
+ };
95
+ ValheimGlobalKeys = {
96
+ DEFEATED_EIKTHYR: "defeated_eikthyr",
97
+ DEFEATED_GDKING: "defeated_gdking",
98
+ // The Elder
99
+ DEFEATED_BONEMASS: "defeated_bonemass",
100
+ DEFEATED_DRAGON: "defeated_dragon",
101
+ // Moder
102
+ DEFEATED_GOBLINKING: "defeated_goblinking",
103
+ // Yagluth
104
+ DEFEATED_QUEEN: "defeated_queen",
105
+ KILLED_TROLL: "KilledTroll",
106
+ KILLED_BAT: "KilledBat",
107
+ KILLED_SURTLING: "KilledSurtling"
108
+ };
24
109
  }
25
110
  });
26
111
 
@@ -373,6 +458,415 @@ var init_client = __esm({
373
458
  }
374
459
  });
375
460
 
461
+ // src/rcon/manager.ts
462
+ var RconManager, rconManager;
463
+ var init_manager = __esm({
464
+ "src/rcon/manager.ts"() {
465
+ "use strict";
466
+ init_client();
467
+ RconManager = class {
468
+ client = null;
469
+ config = null;
470
+ state = "disconnected";
471
+ reconnectTimer = null;
472
+ pollTimer = null;
473
+ reconnectAttempts = 0;
474
+ maxReconnectAttempts = 10;
475
+ reconnectDelay = 5e3;
476
+ // 5 seconds
477
+ pollInterval = 1e4;
478
+ // 10 seconds
479
+ onConnectionStateChange = null;
480
+ onPlayerListUpdate = null;
481
+ lastKnownPlayers = [];
482
+ enabled = false;
483
+ /**
484
+ * Initialize the RCON manager
485
+ * @param config RCON configuration
486
+ * @param options Optional callbacks and settings
487
+ */
488
+ initialize(config, options = {}) {
489
+ this.config = config;
490
+ this.enabled = config.enabled;
491
+ this.onConnectionStateChange = options.onConnectionStateChange ?? null;
492
+ this.onPlayerListUpdate = options.onPlayerListUpdate ?? null;
493
+ if (options.pollInterval) {
494
+ this.pollInterval = options.pollInterval;
495
+ }
496
+ if (this.enabled) {
497
+ this.connect();
498
+ }
499
+ }
500
+ /**
501
+ * Connect to RCON server
502
+ */
503
+ async connect() {
504
+ if (!this.config || !this.enabled) {
505
+ return false;
506
+ }
507
+ if (this.state === "connected" || this.state === "connecting") {
508
+ return true;
509
+ }
510
+ this.setState("connecting");
511
+ this.reconnectAttempts = 0;
512
+ try {
513
+ this.client = new RconClient({
514
+ host: "localhost",
515
+ port: this.config.port,
516
+ password: this.config.password,
517
+ timeout: this.config.timeout
518
+ });
519
+ await this.client.connect();
520
+ this.setState("connected");
521
+ this.reconnectAttempts = 0;
522
+ this.startPlayerListPolling();
523
+ return true;
524
+ } catch (_error) {
525
+ this.setState("error");
526
+ this.client = null;
527
+ if (this.config.autoReconnect && this.enabled) {
528
+ this.scheduleReconnect();
529
+ }
530
+ return false;
531
+ }
532
+ }
533
+ /**
534
+ * Disconnect from RCON server
535
+ */
536
+ disconnect() {
537
+ this.enabled = false;
538
+ this.clearTimers();
539
+ if (this.client) {
540
+ this.client.disconnect();
541
+ this.client = null;
542
+ }
543
+ this.setState("disconnected");
544
+ this.lastKnownPlayers = [];
545
+ }
546
+ /**
547
+ * Send a command to the RCON server
548
+ * @param command Command to send
549
+ * @returns Response from server, or null if not connected
550
+ */
551
+ async send(command) {
552
+ if (!this.client || this.state !== "connected") {
553
+ return null;
554
+ }
555
+ try {
556
+ const response = await this.client.send(command);
557
+ return response;
558
+ } catch (_error) {
559
+ this.setState("error");
560
+ if (this.config?.autoReconnect && this.enabled) {
561
+ this.scheduleReconnect();
562
+ }
563
+ return null;
564
+ }
565
+ }
566
+ /**
567
+ * Get the current player list from the server
568
+ * @returns Array of player names, or empty array if unavailable
569
+ */
570
+ async getPlayerList() {
571
+ const response = await this.send("status");
572
+ if (!response) {
573
+ return [];
574
+ }
575
+ const players = this.parsePlayerList(response);
576
+ return players;
577
+ }
578
+ /**
579
+ * Parse player names from status command response
580
+ * @param statusText Raw status response from server
581
+ * @returns Array of player names
582
+ */
583
+ parsePlayerList(statusText) {
584
+ const players = [];
585
+ const lines = statusText.split("\n");
586
+ for (const line of lines) {
587
+ if (line.includes("players:")) {
588
+ const match = line.match(/players:\s*(.+)/);
589
+ if (match?.[1]) {
590
+ const names = match[1].split(",").map((n) => n.trim()).filter((n) => n.length > 0);
591
+ players.push(...names);
592
+ }
593
+ }
594
+ }
595
+ return players;
596
+ }
597
+ /**
598
+ * Start polling for player list updates
599
+ */
600
+ startPlayerListPolling() {
601
+ this.stopPlayerListPolling();
602
+ this.pollTimer = setInterval(async () => {
603
+ if (this.state !== "connected") {
604
+ return;
605
+ }
606
+ const players = await this.getPlayerList();
607
+ if (this.hasPlayerListChanged(players)) {
608
+ this.lastKnownPlayers = players;
609
+ if (this.onPlayerListUpdate) {
610
+ this.onPlayerListUpdate(players);
611
+ }
612
+ }
613
+ }, this.pollInterval);
614
+ }
615
+ /**
616
+ * Stop polling for player list
617
+ */
618
+ stopPlayerListPolling() {
619
+ if (this.pollTimer) {
620
+ clearInterval(this.pollTimer);
621
+ this.pollTimer = null;
622
+ }
623
+ }
624
+ /**
625
+ * Check if player list has changed
626
+ */
627
+ hasPlayerListChanged(newPlayers) {
628
+ if (newPlayers.length !== this.lastKnownPlayers.length) {
629
+ return true;
630
+ }
631
+ const sorted1 = [...newPlayers].sort();
632
+ const sorted2 = [...this.lastKnownPlayers].sort();
633
+ return !sorted1.every((name, i) => name === sorted2[i]);
634
+ }
635
+ /**
636
+ * Schedule a reconnect attempt
637
+ */
638
+ scheduleReconnect() {
639
+ if (this.reconnectTimer) {
640
+ return;
641
+ }
642
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
643
+ return;
644
+ }
645
+ this.reconnectAttempts++;
646
+ const delay = this.reconnectDelay * this.reconnectAttempts;
647
+ this.reconnectTimer = setTimeout(async () => {
648
+ this.reconnectTimer = null;
649
+ await this.connect();
650
+ }, delay);
651
+ }
652
+ /**
653
+ * Update the connection state and notify listeners
654
+ */
655
+ setState(newState) {
656
+ if (this.state === newState) {
657
+ return;
658
+ }
659
+ this.state = newState;
660
+ if (this.onConnectionStateChange) {
661
+ this.onConnectionStateChange(newState);
662
+ }
663
+ }
664
+ /**
665
+ * Clear all timers
666
+ */
667
+ clearTimers() {
668
+ if (this.reconnectTimer) {
669
+ clearTimeout(this.reconnectTimer);
670
+ this.reconnectTimer = null;
671
+ }
672
+ this.stopPlayerListPolling();
673
+ }
674
+ /**
675
+ * Get the current connection state
676
+ */
677
+ getState() {
678
+ return this.state;
679
+ }
680
+ /**
681
+ * Check if RCON is connected
682
+ */
683
+ isConnected() {
684
+ return this.state === "connected";
685
+ }
686
+ /**
687
+ * Get the last known player list
688
+ */
689
+ getLastKnownPlayers() {
690
+ return [...this.lastKnownPlayers];
691
+ }
692
+ /**
693
+ * Kick a player from the server
694
+ * @param playerName Player name to kick
695
+ * @returns Response message
696
+ */
697
+ async kickPlayer(playerName) {
698
+ return this.send(`kick ${playerName}`);
699
+ }
700
+ /**
701
+ * Ban a player from the server
702
+ * @param playerName Player name to ban
703
+ * @returns Response message
704
+ */
705
+ async banPlayer(playerName) {
706
+ return this.send(`ban ${playerName}`);
707
+ }
708
+ /**
709
+ * Unban a player
710
+ * @param playerName Player name to unban
711
+ * @returns Response message
712
+ */
713
+ async unbanPlayer(playerName) {
714
+ return this.send(`unban ${playerName}`);
715
+ }
716
+ /**
717
+ * Get list of banned players
718
+ * @returns Array of banned player names/IDs
719
+ */
720
+ async getBannedPlayers() {
721
+ const response = await this.send("banned");
722
+ if (!response) return [];
723
+ return response.split("\n").filter((line) => line.trim().length > 0);
724
+ }
725
+ /**
726
+ * Get server information
727
+ * @returns Server info response
728
+ */
729
+ async getServerInfo() {
730
+ return this.send("info");
731
+ }
732
+ /**
733
+ * Ping the server
734
+ * @returns Ping response
735
+ */
736
+ async pingServer() {
737
+ return this.send("ping");
738
+ }
739
+ /**
740
+ * Trigger a random event
741
+ * @param eventName Event name (e.g., "army_eikthyr")
742
+ * @returns Response message
743
+ */
744
+ async triggerEvent(eventName) {
745
+ return this.send(`event ${eventName}`);
746
+ }
747
+ /**
748
+ * Trigger a random event
749
+ * @returns Response message
750
+ */
751
+ async triggerRandomEvent() {
752
+ return this.send("randomevent");
753
+ }
754
+ /**
755
+ * Stop the current event
756
+ * @returns Response message
757
+ */
758
+ async stopEvent() {
759
+ return this.send("stopevent");
760
+ }
761
+ /**
762
+ * Skip time by specified seconds
763
+ * @param seconds Number of seconds to skip
764
+ * @returns Response message
765
+ */
766
+ async skipTime(seconds) {
767
+ return this.send(`skiptime ${seconds}`);
768
+ }
769
+ /**
770
+ * Sleep through the night
771
+ * @returns Response message
772
+ */
773
+ async sleep() {
774
+ return this.send("sleep");
775
+ }
776
+ /**
777
+ * Remove all dropped items from the world
778
+ * @returns Response message
779
+ */
780
+ async removeDrops() {
781
+ return this.send("removedrops");
782
+ }
783
+ /**
784
+ * Set a global key
785
+ * @param key Global key name
786
+ * @returns Response message
787
+ */
788
+ async setGlobalKey(key) {
789
+ return this.send(`setkey ${key}`);
790
+ }
791
+ /**
792
+ * Remove a global key
793
+ * @param key Global key name
794
+ * @returns Response message
795
+ */
796
+ async removeGlobalKey(key) {
797
+ return this.send(`removekey ${key}`);
798
+ }
799
+ /**
800
+ * Reset all global keys
801
+ * @returns Response message
802
+ */
803
+ async resetGlobalKeys() {
804
+ return this.send("resetkeys");
805
+ }
806
+ /**
807
+ * List all global keys
808
+ * @returns Array of global key names
809
+ */
810
+ async listGlobalKeys() {
811
+ const response = await this.send("listkeys");
812
+ if (!response) return [];
813
+ return response.split("\n").filter((line) => line.trim().length > 0);
814
+ }
815
+ /**
816
+ * Set LOD bias (0-5, lower = better performance)
817
+ * @param value LOD bias value
818
+ * @returns Response message
819
+ */
820
+ async setLodBias(value) {
821
+ return this.send(`lodbias ${value}`);
822
+ }
823
+ /**
824
+ * Set LOD distance (100-6000)
825
+ * @param value LOD distance value
826
+ * @returns Response message
827
+ */
828
+ async setLodDistance(value) {
829
+ return this.send(`loddist ${value}`);
830
+ }
831
+ /**
832
+ * Clean up resources
833
+ */
834
+ cleanup() {
835
+ this.disconnect();
836
+ }
837
+ };
838
+ rconManager = new RconManager();
839
+ }
840
+ });
841
+
842
+ // src/rcon/mod.ts
843
+ var mod_exports = {};
844
+ __export(mod_exports, {
845
+ PacketType: () => PacketType,
846
+ RconClient: () => RconClient,
847
+ RconError: () => RconError,
848
+ ValheimCommands: () => ValheimCommands,
849
+ ValheimEvents: () => ValheimEvents,
850
+ ValheimGlobalKeys: () => ValheimGlobalKeys,
851
+ createAuthPacket: () => createAuthPacket,
852
+ createCommandPacket: () => createCommandPacket,
853
+ decodePacket: () => decodePacket,
854
+ encodePacket: () => encodePacket,
855
+ isAuthFailure: () => isAuthFailure,
856
+ isAuthResponse: () => isAuthResponse,
857
+ isAuthSuccess: () => isAuthSuccess,
858
+ rconManager: () => rconManager
859
+ });
860
+ var init_mod = __esm({
861
+ "src/rcon/mod.ts"() {
862
+ "use strict";
863
+ init_client();
864
+ init_manager();
865
+ init_protocol();
866
+ init_types();
867
+ }
868
+ });
869
+
376
870
  // src/cli/args.ts
377
871
  import { z } from "zod";
378
872
  var PortSchema = z.number().int().min(1024).max(65535);
@@ -919,11 +1413,11 @@ var defaultConfig = {
919
1413
  refreshRate: 1e3
920
1414
  },
921
1415
  rcon: {
922
- enabled: false,
1416
+ enabled: true,
923
1417
  port: 25575,
924
- password: "",
1418
+ password: "valheim-rcon",
925
1419
  timeout: 5e3,
926
- autoReconnect: false
1420
+ autoReconnect: true
927
1421
  },
928
1422
  worlds: [],
929
1423
  activeWorld: null,
@@ -1026,11 +1520,11 @@ var TuiConfigSchema = z2.object({
1026
1520
  refreshRate: z2.number().int().min(100).max(5e3).default(1e3)
1027
1521
  });
1028
1522
  var RconConfigSchema = z2.object({
1029
- enabled: z2.boolean().default(false),
1523
+ enabled: z2.boolean().default(true),
1030
1524
  port: z2.number().int().min(1024, "Port must be >= 1024").max(65535, "Port must be <= 65535").default(25575),
1031
- password: z2.string().default(""),
1525
+ password: z2.string().default("valheim-rcon"),
1032
1526
  timeout: z2.number().int().min(1e3).max(6e4).default(5e3),
1033
- autoReconnect: z2.boolean().default(false)
1527
+ autoReconnect: z2.boolean().default(true)
1034
1528
  });
1035
1529
  var AppConfigSchema = z2.object({
1036
1530
  version: z2.number().int().default(1),
@@ -2496,13 +2990,7 @@ function createProgressBar(percent) {
2496
2990
 
2497
2991
  // src/cli/commands/rcon.ts
2498
2992
  import * as readline from "readline";
2499
-
2500
- // src/rcon/mod.ts
2501
- init_client();
2502
- init_protocol();
2503
- init_types();
2504
-
2505
- // src/cli/commands/rcon.ts
2993
+ init_mod();
2506
2994
  async function rconCommand(args) {
2507
2995
  const config = await loadConfig();
2508
2996
  const host = args.host ?? "localhost";
@@ -2640,10 +3128,30 @@ async function interactiveRcon(args) {
2640
3128
  }
2641
3129
 
2642
3130
  // src/server/commands.ts
3131
+ init_mod();
2643
3132
  import * as fs5 from "fs/promises";
2644
3133
  import { dirname, join } from "path";
2645
3134
 
2646
3135
  // src/server/logs.ts
3136
+ function parseLogLine(line) {
3137
+ const timestamp = /* @__PURE__ */ new Date();
3138
+ let level = "info";
3139
+ let message = line.trim();
3140
+ if (line.includes("Error") || line.includes("Exception")) {
3141
+ level = "error";
3142
+ } else if (line.includes("Warning") || line.includes("WARN")) {
3143
+ level = "warn";
3144
+ } else if (line.includes("DEBUG") || line.includes("[Debug]")) {
3145
+ level = "debug";
3146
+ }
3147
+ const timestampMatch = line.match(
3148
+ /^(\d{2}\/\d{2}\/\d{4} \d{2}:\d{2}:\d{2}): (.+)$/
3149
+ );
3150
+ if (timestampMatch) {
3151
+ message = timestampMatch[2];
3152
+ }
3153
+ return { timestamp, level, message, raw: line };
3154
+ }
2647
3155
  function parseEvent(line) {
2648
3156
  if (line.includes("Got character ZDOID from")) {
2649
3157
  const match = line.match(/Got character ZDOID from (\S+)/);
@@ -2651,8 +3159,11 @@ function parseEvent(line) {
2651
3159
  return { type: "player_join", name: match[1] };
2652
3160
  }
2653
3161
  }
2654
- if (line.includes("Closing socket")) {
2655
- return null;
3162
+ if (line.includes("Destroying abandoned non persistent zdo")) {
3163
+ const playerMatch = line.match(/owner (\S+)/);
3164
+ if (playerMatch?.[1]) {
3165
+ return { type: "player_leave", name: playerMatch[1] };
3166
+ }
2656
3167
  }
2657
3168
  if (line.includes("World saved")) {
2658
3169
  return { type: "world_saved" };
@@ -2690,9 +3201,179 @@ function parseEvent(line) {
2690
3201
  return null;
2691
3202
  }
2692
3203
 
3204
+ // src/server/logTail.ts
3205
+ import { open } from "fs/promises";
3206
+ var LogTailer = class {
3207
+ filePath;
3208
+ handle = null;
3209
+ position = 0;
3210
+ running = false;
3211
+ pollInterval = null;
3212
+ buffer = "";
3213
+ onLine;
3214
+ onEvent;
3215
+ pollMs;
3216
+ /**
3217
+ * Creates a new log tailer
3218
+ * @param filePath Path to the log file to tail
3219
+ * @param onLine Callback for each new log line
3220
+ * @param options Optional settings
3221
+ */
3222
+ constructor(filePath, onLine, options) {
3223
+ this.filePath = filePath;
3224
+ this.onLine = onLine;
3225
+ this.onEvent = options?.onEvent;
3226
+ this.pollMs = options?.pollMs ?? 500;
3227
+ }
3228
+ /**
3229
+ * Starts tailing the log file
3230
+ * @param fromEnd If true, start from end of file (skip existing content)
3231
+ */
3232
+ async start(fromEnd = true) {
3233
+ if (this.running) return;
3234
+ try {
3235
+ this.handle = await open(this.filePath, "r");
3236
+ if (fromEnd) {
3237
+ const stats = await this.handle.stat();
3238
+ this.position = stats.size;
3239
+ } else {
3240
+ this.position = 0;
3241
+ }
3242
+ this.running = true;
3243
+ this.pollInterval = setInterval(() => this.poll(), this.pollMs);
3244
+ await this.poll();
3245
+ } catch (_error) {
3246
+ this.running = true;
3247
+ this.pollInterval = setInterval(() => this.poll(), this.pollMs);
3248
+ }
3249
+ }
3250
+ /**
3251
+ * Stops tailing the log file
3252
+ */
3253
+ async stop() {
3254
+ this.running = false;
3255
+ if (this.pollInterval) {
3256
+ clearInterval(this.pollInterval);
3257
+ this.pollInterval = null;
3258
+ }
3259
+ if (this.handle) {
3260
+ await this.handle.close();
3261
+ this.handle = null;
3262
+ }
3263
+ }
3264
+ /**
3265
+ * Polls the log file for new content
3266
+ */
3267
+ async poll() {
3268
+ if (!this.running) return;
3269
+ try {
3270
+ if (!this.handle) {
3271
+ try {
3272
+ this.handle = await open(this.filePath, "r");
3273
+ const stats2 = await this.handle.stat();
3274
+ if (this.position > stats2.size) {
3275
+ this.position = 0;
3276
+ }
3277
+ } catch {
3278
+ return;
3279
+ }
3280
+ }
3281
+ const stats = await this.handle.stat();
3282
+ if (stats.size <= this.position) {
3283
+ if (stats.size < this.position) {
3284
+ this.position = 0;
3285
+ }
3286
+ return;
3287
+ }
3288
+ const bytesToRead = stats.size - this.position;
3289
+ const buffer = Buffer.alloc(bytesToRead);
3290
+ const { bytesRead } = await this.handle.read(
3291
+ buffer,
3292
+ 0,
3293
+ bytesToRead,
3294
+ this.position
3295
+ );
3296
+ if (bytesRead > 0) {
3297
+ this.position += bytesRead;
3298
+ this.processChunk(buffer.toString("utf-8", 0, bytesRead));
3299
+ }
3300
+ } catch (_error) {
3301
+ if (this.handle) {
3302
+ try {
3303
+ await this.handle.close();
3304
+ } catch {
3305
+ }
3306
+ this.handle = null;
3307
+ }
3308
+ }
3309
+ }
3310
+ /**
3311
+ * Processes a chunk of log data, splitting into lines
3312
+ */
3313
+ processChunk(chunk) {
3314
+ this.buffer += chunk;
3315
+ const lines = this.buffer.split("\n");
3316
+ this.buffer = lines.pop() ?? "";
3317
+ for (const line of lines) {
3318
+ const trimmed = line.trim();
3319
+ if (!trimmed) continue;
3320
+ const entry = parseLogLine(trimmed);
3321
+ this.onLine(trimmed, entry);
3322
+ const event = parseEvent(trimmed);
3323
+ if (event && this.onEvent) {
3324
+ this.onEvent(event);
3325
+ }
3326
+ }
3327
+ }
3328
+ /**
3329
+ * Reads the last N lines from the log file (for initial display)
3330
+ * @param lineCount Number of lines to read
3331
+ * @returns Array of log entries
3332
+ */
3333
+ async readLastLines(lineCount = 100) {
3334
+ const entries = [];
3335
+ try {
3336
+ const handle = await open(this.filePath, "r");
3337
+ const stats = await handle.stat();
3338
+ const fileSize = stats.size;
3339
+ const chunkSize = Math.min(16384, fileSize);
3340
+ let position = Math.max(0, fileSize - chunkSize);
3341
+ let content = "";
3342
+ let lines = [];
3343
+ while (lines.length < lineCount && position >= 0) {
3344
+ const buffer = Buffer.alloc(Math.min(chunkSize, fileSize - position));
3345
+ await handle.read(buffer, 0, buffer.length, position);
3346
+ content = buffer.toString("utf-8") + content;
3347
+ lines = content.split("\n").filter((l) => l.trim());
3348
+ if (position === 0) break;
3349
+ position = Math.max(0, position - chunkSize);
3350
+ }
3351
+ await handle.close();
3352
+ const lastLines = lines.slice(-lineCount);
3353
+ for (const line of lastLines) {
3354
+ entries.push(parseLogLine(line.trim()));
3355
+ }
3356
+ } catch {
3357
+ }
3358
+ return entries;
3359
+ }
3360
+ /** Whether the tailer is currently running */
3361
+ get isRunning() {
3362
+ return this.running;
3363
+ }
3364
+ };
3365
+
2693
3366
  // src/server/pidfile.ts
2694
3367
  import fs6 from "fs/promises";
2695
3368
  import path5 from "path";
3369
+ function getServerLogsDir() {
3370
+ return path5.join(getAppConfigDir(), "logs");
3371
+ }
3372
+ function getServerLogFile(timestamp) {
3373
+ const ts = timestamp ?? /* @__PURE__ */ new Date();
3374
+ const dateStr = ts.toISOString().split("T")[0];
3375
+ return path5.join(getServerLogsDir(), `valheim-server-${dateStr}.log`);
3376
+ }
2696
3377
  function getPidFilePath() {
2697
3378
  return path5.join(getConfigDir(), "oz-valheim", "server.pid");
2698
3379
  }
@@ -2746,9 +3427,28 @@ async function getRunningServer() {
2746
3427
  }
2747
3428
  return data;
2748
3429
  }
3430
+ async function ensureLogsDir() {
3431
+ const logsDir = getServerLogsDir();
3432
+ await fs6.mkdir(logsDir, { recursive: true });
3433
+ }
3434
+ async function cleanupOldLogs(keepCount = 7) {
3435
+ const logsDir = getServerLogsDir();
3436
+ try {
3437
+ const files = await fs6.readdir(logsDir);
3438
+ const logFiles = files.filter((f) => f.startsWith("valheim-server-") && f.endsWith(".log")).sort().reverse();
3439
+ for (const file of logFiles.slice(keepCount)) {
3440
+ try {
3441
+ await fs6.unlink(path5.join(logsDir, file));
3442
+ } catch {
3443
+ }
3444
+ }
3445
+ } catch {
3446
+ }
3447
+ }
2749
3448
 
2750
3449
  // src/server/process.ts
2751
3450
  import { spawn } from "child_process";
3451
+ import { createWriteStream } from "fs";
2752
3452
  var defaultEvents = {
2753
3453
  onStateChange: () => {
2754
3454
  },
@@ -2767,6 +3467,12 @@ var ValheimProcess = class {
2767
3467
  events;
2768
3468
  config;
2769
3469
  startTime = null;
3470
+ logFileStream = null;
3471
+ logTailer = null;
3472
+ _logFilePath = null;
3473
+ _isDetached = false;
3474
+ /** PID for detached processes (when we don't have a direct handle) */
3475
+ _detachedPid = null;
2770
3476
  /**
2771
3477
  * Creates a new Valheim process wrapper
2772
3478
  * @param config Server launch configuration
@@ -2778,7 +3484,7 @@ var ValheimProcess = class {
2778
3484
  }
2779
3485
  /** Gets the process ID if running, null otherwise */
2780
3486
  get pid() {
2781
- return this.process?.pid ?? null;
3487
+ return this.process?.pid ?? this._detachedPid ?? null;
2782
3488
  }
2783
3489
  /** Gets the current process state */
2784
3490
  get currentState() {
@@ -2788,6 +3494,14 @@ var ValheimProcess = class {
2788
3494
  get uptime() {
2789
3495
  return this.startTime;
2790
3496
  }
3497
+ /** Gets the log file path (for detached mode) */
3498
+ get logFilePath() {
3499
+ return this._logFilePath;
3500
+ }
3501
+ /** Whether the server is running in detached mode */
3502
+ get isDetached() {
3503
+ return this._isDetached;
3504
+ }
2791
3505
  /**
2792
3506
  * Updates the process state and notifies listeners
2793
3507
  * @param newState New process state
@@ -2806,21 +3520,16 @@ var ValheimProcess = class {
2806
3520
  }
2807
3521
  this.setState("starting");
2808
3522
  this.startTime = /* @__PURE__ */ new Date();
3523
+ this._isDetached = this.config.detached ?? false;
2809
3524
  const execPath = getValheimExecutablePath();
2810
3525
  const args = this.buildArgs();
2811
3526
  const env = this.getEnvironment();
2812
3527
  try {
2813
- this.process = spawn(execPath, args, {
2814
- stdio: ["ignore", "pipe", "pipe"],
2815
- env
2816
- });
2817
- this.streamOutput();
2818
- this.process.on("error", (error2) => {
2819
- this.setState("crashed");
2820
- this.startTime = null;
2821
- this.events.onError(error2);
2822
- });
2823
- await Promise.resolve();
3528
+ if (this._isDetached) {
3529
+ await this.startDetached(execPath, args, env);
3530
+ } else {
3531
+ await this.startAttached(execPath, args, env);
3532
+ }
2824
3533
  } catch (error2) {
2825
3534
  this.setState("crashed");
2826
3535
  this.startTime = null;
@@ -2828,6 +3537,169 @@ var ValheimProcess = class {
2828
3537
  throw error2;
2829
3538
  }
2830
3539
  }
3540
+ /**
3541
+ * Starts the server in attached mode (piped stdout/stderr)
3542
+ */
3543
+ async startAttached(execPath, args, env) {
3544
+ this.process = spawn(execPath, args, {
3545
+ stdio: ["ignore", "pipe", "pipe"],
3546
+ env
3547
+ });
3548
+ this.streamOutput();
3549
+ this.process.on("error", (error2) => {
3550
+ this.setState("crashed");
3551
+ this.startTime = null;
3552
+ this.events.onError(error2);
3553
+ });
3554
+ await Promise.resolve();
3555
+ }
3556
+ /**
3557
+ * Starts the server in detached mode (log file output, independent process)
3558
+ */
3559
+ async startDetached(execPath, args, env) {
3560
+ await ensureLogsDir();
3561
+ this._logFilePath = getServerLogFile(this.startTime);
3562
+ this.logFileStream = createWriteStream(this._logFilePath, { flags: "a" });
3563
+ const header = `
3564
+ ${"=".repeat(60)}
3565
+ Server starting at ${this.startTime.toISOString()}
3566
+ World: ${this.config.world} | Port: ${this.config.port}
3567
+ ${"=".repeat(60)}
3568
+ `;
3569
+ this.logFileStream.write(header);
3570
+ const platform = getPlatform();
3571
+ this.process = spawn(execPath, args, {
3572
+ stdio: ["ignore", "pipe", "pipe"],
3573
+ env,
3574
+ detached: true
3575
+ });
3576
+ if (this.process.stdout) {
3577
+ this.process.stdout.pipe(this.logFileStream, { end: false });
3578
+ }
3579
+ if (this.process.stderr) {
3580
+ this.process.stderr.pipe(this.logFileStream, { end: false });
3581
+ }
3582
+ this.logTailer = new LogTailer(
3583
+ this._logFilePath,
3584
+ (line, _entry) => {
3585
+ this.events.onLog(line);
3586
+ },
3587
+ {
3588
+ onEvent: (event) => this.handleEvent(event),
3589
+ fromEnd: false
3590
+ // Read from where we started
3591
+ }
3592
+ );
3593
+ await this.logTailer.start(false);
3594
+ this.process.on("error", (error2) => {
3595
+ this.setState("crashed");
3596
+ this.startTime = null;
3597
+ this.events.onError(error2);
3598
+ });
3599
+ const pid = this.process.pid;
3600
+ if (pid) {
3601
+ await writePidFile({
3602
+ pid,
3603
+ startedAt: this.startTime.toISOString(),
3604
+ world: this.config.world,
3605
+ port: this.config.port,
3606
+ logFile: this._logFilePath,
3607
+ detached: true,
3608
+ serverName: this.config.name
3609
+ });
3610
+ }
3611
+ this.process.unref();
3612
+ if (platform === "windows") {
3613
+ if (this.process.stdout && typeof this.process.stdout.unref === "function") {
3614
+ this.process.stdout.unref();
3615
+ }
3616
+ if (this.process.stderr && typeof this.process.stderr.unref === "function") {
3617
+ this.process.stderr.unref();
3618
+ }
3619
+ }
3620
+ this.process.on("exit", (code, signal) => {
3621
+ const exitCode = code ?? (signal ? 1 : 0);
3622
+ if (exitCode !== 0 && (this.state === "online" || this.state === "starting")) {
3623
+ this.startTime = null;
3624
+ this.setState("crashed");
3625
+ this.events.onError(new Error(`Server exited with code ${exitCode}`));
3626
+ }
3627
+ });
3628
+ await Promise.resolve();
3629
+ }
3630
+ /**
3631
+ * Handles a parsed event from log output
3632
+ */
3633
+ handleEvent(event) {
3634
+ this.events.onEvent?.(event);
3635
+ switch (event.type) {
3636
+ case "player_join":
3637
+ this.events.onPlayerJoin(event.name);
3638
+ break;
3639
+ case "player_leave":
3640
+ this.events.onPlayerLeave(event.name);
3641
+ break;
3642
+ case "server_ready":
3643
+ if (this.state === "starting") {
3644
+ this.setState("online");
3645
+ }
3646
+ break;
3647
+ case "error":
3648
+ this.events.onError(new Error(event.message));
3649
+ break;
3650
+ }
3651
+ }
3652
+ /**
3653
+ * Attaches to an already-running detached server
3654
+ * @param pidData PID file data for the running server
3655
+ */
3656
+ async attach(pidData) {
3657
+ if (this.state !== "offline" && this.state !== "crashed") {
3658
+ throw new Error(`Cannot attach in state: ${this.state}`);
3659
+ }
3660
+ if (!isProcessRunning(pidData.pid)) {
3661
+ throw new Error(`Server process ${pidData.pid} is not running`);
3662
+ }
3663
+ this._isDetached = true;
3664
+ this._detachedPid = pidData.pid;
3665
+ this._logFilePath = pidData.logFile ?? null;
3666
+ this.startTime = new Date(pidData.startedAt);
3667
+ if (this._logFilePath) {
3668
+ this.logTailer = new LogTailer(
3669
+ this._logFilePath,
3670
+ (line, _entry) => {
3671
+ this.events.onLog(line);
3672
+ },
3673
+ {
3674
+ onEvent: (event) => this.handleEvent(event),
3675
+ pollMs: 500
3676
+ }
3677
+ );
3678
+ const history = await this.logTailer.readLastLines(50);
3679
+ for (const entry of history) {
3680
+ this.events.onLog(entry.raw);
3681
+ }
3682
+ await this.logTailer.start(true);
3683
+ }
3684
+ this.startProcessMonitor();
3685
+ this.setState("online");
3686
+ }
3687
+ /**
3688
+ * Monitors a detached process for exit
3689
+ */
3690
+ startProcessMonitor() {
3691
+ const checkInterval = setInterval(() => {
3692
+ const pid = this._detachedPid;
3693
+ if (pid && !isProcessRunning(pid)) {
3694
+ clearInterval(checkInterval);
3695
+ this._detachedPid = null;
3696
+ this.startTime = null;
3697
+ this.setState("crashed");
3698
+ this.events.onError(new Error("Server process exited unexpectedly"));
3699
+ }
3700
+ }, 2e3);
3701
+ this._monitorInterval = checkInterval;
3702
+ }
2831
3703
  /**
2832
3704
  * Gracefully stops the server with optional timeout
2833
3705
  * @param timeout Maximum time to wait for graceful shutdown (ms)
@@ -2837,6 +3709,31 @@ var ValheimProcess = class {
2837
3709
  return;
2838
3710
  }
2839
3711
  this.setState("stopping");
3712
+ await this.cleanup();
3713
+ if (this._isDetached && this._detachedPid) {
3714
+ const platform = getPlatform();
3715
+ try {
3716
+ process.kill(this._detachedPid, "SIGTERM");
3717
+ } catch {
3718
+ }
3719
+ const startTime = Date.now();
3720
+ while (isProcessRunning(this._detachedPid) && Date.now() - startTime < timeout) {
3721
+ await new Promise((resolve) => setTimeout(resolve, 500));
3722
+ }
3723
+ if (isProcessRunning(this._detachedPid)) {
3724
+ try {
3725
+ process.kill(
3726
+ this._detachedPid,
3727
+ platform === "windows" ? "SIGTERM" : "SIGKILL"
3728
+ );
3729
+ } catch {
3730
+ }
3731
+ }
3732
+ this._detachedPid = null;
3733
+ this.startTime = null;
3734
+ this.setState("offline");
3735
+ return;
3736
+ }
2840
3737
  if (this.process) {
2841
3738
  try {
2842
3739
  this.process.kill("SIGTERM");
@@ -2864,6 +3761,18 @@ var ValheimProcess = class {
2864
3761
  * Immediately kills the server process
2865
3762
  */
2866
3763
  async kill() {
3764
+ await this.cleanup();
3765
+ if (this._isDetached && this._detachedPid) {
3766
+ const platform = getPlatform();
3767
+ try {
3768
+ process.kill(
3769
+ this._detachedPid,
3770
+ platform === "windows" ? "SIGTERM" : "SIGKILL"
3771
+ );
3772
+ } catch {
3773
+ }
3774
+ this._detachedPid = null;
3775
+ }
2867
3776
  if (this.process) {
2868
3777
  try {
2869
3778
  this.process.kill("SIGKILL");
@@ -2875,6 +3784,36 @@ var ValheimProcess = class {
2875
3784
  this.setState("offline");
2876
3785
  await Promise.resolve();
2877
3786
  }
3787
+ /**
3788
+ * Detaches from a running server without stopping it
3789
+ * Only valid for servers started in detached mode
3790
+ */
3791
+ async detach() {
3792
+ if (!this._isDetached) {
3793
+ throw new Error("Cannot detach from non-detached server");
3794
+ }
3795
+ await this.cleanup();
3796
+ this.process = null;
3797
+ this._detachedPid = null;
3798
+ }
3799
+ /**
3800
+ * Cleans up resources (log tailer, monitor interval, log file stream)
3801
+ */
3802
+ async cleanup() {
3803
+ const self = this;
3804
+ if (self._monitorInterval) {
3805
+ clearInterval(self._monitorInterval);
3806
+ self._monitorInterval = void 0;
3807
+ }
3808
+ if (this.logTailer) {
3809
+ await this.logTailer.stop();
3810
+ this.logTailer = null;
3811
+ }
3812
+ if (this.logFileStream) {
3813
+ this.logFileStream.end();
3814
+ this.logFileStream = null;
3815
+ }
3816
+ }
2878
3817
  /**
2879
3818
  * Builds command line arguments for Valheim server
2880
3819
  * @returns Array of command line arguments
@@ -3190,6 +4129,18 @@ async function startCommand(args, config) {
3190
4129
  console.log("Run 'valheim-dsm install' first to install the server.");
3191
4130
  process.exit(1);
3192
4131
  }
4132
+ const running = await getRunningServer();
4133
+ if (running) {
4134
+ console.error(`
4135
+ Error: A server is already running.`);
4136
+ console.log(` PID: ${running.pid}`);
4137
+ console.log(` World: ${running.world}`);
4138
+ console.log(` Port: ${running.port}`);
4139
+ console.log(` Started: ${new Date(running.startedAt).toLocaleString()}`);
4140
+ console.log("\nRun 'valheim-dsm stop' to stop it first.");
4141
+ process.exit(1);
4142
+ }
4143
+ await cleanupOldLogs();
3193
4144
  const serverConfig = {
3194
4145
  name: args.name ?? config.server.name,
3195
4146
  port: args.port ?? config.server.port,
@@ -3199,7 +4150,9 @@ async function startCommand(args, config) {
3199
4150
  crossplay: args.crossplay ?? config.server.crossplay,
3200
4151
  savedir: args.savedir ?? config.server.savedir,
3201
4152
  saveinterval: config.server.saveinterval,
3202
- backups: config.server.backups
4153
+ backups: config.server.backups,
4154
+ // Always use detached mode for stability
4155
+ detached: true
3203
4156
  };
3204
4157
  console.log(`
3205
4158
  Starting ${serverConfig.name}...`);
@@ -3207,6 +4160,7 @@ Starting ${serverConfig.name}...`);
3207
4160
  console.log(` Port: ${serverConfig.port}`);
3208
4161
  console.log(` Public: ${serverConfig.public}`);
3209
4162
  console.log(` Crossplay: ${serverConfig.crossplay}`);
4163
+ console.log(` Mode: Detached (server continues after terminal exits)`);
3210
4164
  console.log("");
3211
4165
  activeWatchdog = new Watchdog(
3212
4166
  serverConfig,
@@ -3220,6 +4174,12 @@ Starting ${serverConfig.name}...`);
3220
4174
  {
3221
4175
  onStateChange: (state) => {
3222
4176
  console.log(`[Server] State: ${state}`);
4177
+ if (state === "online") {
4178
+ console.log("\n\u2713 Server is now online!");
4179
+ console.log(" The server will continue running in the background.");
4180
+ console.log(" Use 'valheim-dsm stop' to stop it.");
4181
+ console.log(" Use 'valheim-dsm' (TUI) to manage it.\n");
4182
+ }
3223
4183
  },
3224
4184
  onLog: (line) => {
3225
4185
  console.log(`[Server] ${line}`);
@@ -3248,18 +4208,34 @@ Starting ${serverConfig.name}...`);
3248
4208
  setupShutdownHandlers();
3249
4209
  try {
3250
4210
  await activeWatchdog.start();
3251
- const pid = activeWatchdog.serverProcess.pid;
3252
- if (pid) {
3253
- await writePidFile({
3254
- pid,
3255
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
3256
- world: serverConfig.world,
3257
- port: serverConfig.port
3258
- });
4211
+ const logPath = activeWatchdog.serverProcess.logFilePath;
4212
+ if (logPath) {
4213
+ console.log(`
4214
+ Server log: ${logPath}`);
3259
4215
  }
3260
- console.log("\nServer started. Press Ctrl+C to stop.\n");
3261
- await new Promise(() => {
3262
- });
4216
+ console.log("\nServer is starting in detached mode.");
4217
+ console.log(
4218
+ "Press Ctrl+C to stop monitoring (server will keep running).\n"
4219
+ );
4220
+ const timeout = 12e4;
4221
+ const startTime = Date.now();
4222
+ while (Date.now() - startTime < timeout) {
4223
+ const state = activeWatchdog.serverProcess.currentState;
4224
+ if (state === "online") {
4225
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
4226
+ break;
4227
+ }
4228
+ if (state === "crashed" || state === "offline") {
4229
+ console.error("\nServer failed to start.");
4230
+ await cleanupAndExit(1);
4231
+ return;
4232
+ }
4233
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
4234
+ }
4235
+ console.log("\nDetaching from server...");
4236
+ await activeWatchdog.serverProcess.detach();
4237
+ activeWatchdog = null;
4238
+ process.exit(0);
3263
4239
  } catch (error2) {
3264
4240
  console.error(`
3265
4241
  Failed to start server: ${error2.message}`);
@@ -3272,14 +4248,26 @@ function getActiveWatchdog() {
3272
4248
  function clearActiveWatchdog() {
3273
4249
  activeWatchdog = null;
3274
4250
  }
4251
+ async function cleanupAndExit(code) {
4252
+ if (activeWatchdog) {
4253
+ try {
4254
+ await activeWatchdog.serverProcess.detach();
4255
+ } catch {
4256
+ }
4257
+ activeWatchdog = null;
4258
+ }
4259
+ process.exit(code);
4260
+ }
3275
4261
  function setupShutdownHandlers() {
3276
4262
  const shutdown = async () => {
3277
- console.log("\n\nShutting down...");
4263
+ console.log("\n\nDetaching from server (it will keep running)...");
3278
4264
  if (activeWatchdog) {
3279
- await activeWatchdog.stop();
4265
+ try {
4266
+ await activeWatchdog.serverProcess.detach();
4267
+ } catch {
4268
+ }
3280
4269
  activeWatchdog = null;
3281
4270
  }
3282
- await removePidFile();
3283
4271
  process.exit(0);
3284
4272
  };
3285
4273
  process.on("SIGINT", shutdown);
@@ -3290,9 +4278,9 @@ function setupShutdownHandlers() {
3290
4278
 
3291
4279
  // src/cli/commands/stop.ts
3292
4280
  async function stopCommand(args) {
4281
+ const timeout = args.timeout ?? 3e4;
3293
4282
  const watchdog2 = getActiveWatchdog();
3294
4283
  if (watchdog2) {
3295
- const timeout = args.timeout ?? 3e4;
3296
4284
  if (args.force) {
3297
4285
  console.log("\nForce stopping server...");
3298
4286
  await watchdog2.kill();
@@ -3312,34 +4300,53 @@ Stopping server (timeout: ${timeout}ms)...`);
3312
4300
  console.log("\nNote: Run 'valheim-dsm start' to start a server.");
3313
4301
  return;
3314
4302
  }
3315
- const { pid, world, port, startedAt } = runningServer;
4303
+ const { pid, world, port, startedAt, detached, logFile } = runningServer;
3316
4304
  console.log(`
3317
4305
  Found running server:`);
3318
4306
  console.log(` PID: ${pid}`);
3319
4307
  console.log(` World: ${world}`);
3320
4308
  console.log(` Port: ${port}`);
3321
4309
  console.log(` Started: ${new Date(startedAt).toLocaleString()}`);
4310
+ console.log(` Mode: ${detached ? "Detached" : "Attached"}`);
4311
+ if (logFile) {
4312
+ console.log(` Log: ${logFile}`);
4313
+ }
3322
4314
  if (!isProcessRunning(pid)) {
3323
4315
  console.log("\nServer process is no longer running. Cleaning up...");
3324
4316
  await removePidFile();
3325
4317
  return;
3326
4318
  }
4319
+ const platform = getPlatform();
3327
4320
  if (args.force) {
3328
4321
  console.log("\nForce killing server...");
3329
- killProcess(pid, true);
4322
+ killProcess(pid, platform !== "windows");
3330
4323
  } else {
3331
4324
  console.log("\nSending stop signal...");
3332
4325
  killProcess(pid, false);
3333
- const timeout = args.timeout ?? 3e4;
3334
4326
  const startTime = Date.now();
4327
+ let dots = 0;
3335
4328
  while (isProcessRunning(pid) && Date.now() - startTime < timeout) {
3336
4329
  await new Promise((resolve) => setTimeout(resolve, 500));
3337
4330
  process.stdout.write(".");
4331
+ dots++;
3338
4332
  }
3339
- console.log();
4333
+ if (dots > 0) console.log();
3340
4334
  if (isProcessRunning(pid)) {
3341
4335
  console.log("Server did not stop gracefully, force killing...");
3342
- killProcess(pid, true);
4336
+ killProcess(pid, platform !== "windows");
4337
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
4338
+ if (isProcessRunning(pid)) {
4339
+ console.error(
4340
+ "Failed to stop server. Process may require manual termination."
4341
+ );
4342
+ console.log(` PID: ${pid}`);
4343
+ if (platform === "windows") {
4344
+ console.log(` Try: taskkill /F /PID ${pid}`);
4345
+ } else {
4346
+ console.log(` Try: kill -9 ${pid}`);
4347
+ }
4348
+ return;
4349
+ }
3343
4350
  }
3344
4351
  }
3345
4352
  await removePidFile();
@@ -3853,13 +4860,16 @@ Error deleting world: ${error2.message}`);
3853
4860
  }
3854
4861
  }
3855
4862
 
4863
+ // src/mod.ts
4864
+ init_mod();
4865
+
3856
4866
  // src/tui/mod.ts
3857
4867
  import { withFullScreen } from "fullscreen-ink";
3858
4868
  import React2 from "react";
3859
4869
 
3860
4870
  // src/tui/App.tsx
3861
- import { Box as Box17, useApp, useInput as useInput10 } from "ink";
3862
- import { useEffect as useEffect12 } from "react";
4871
+ import { Box as Box22, useApp, useInput as useInput15 } from "ink";
4872
+ import { useEffect as useEffect14 } from "react";
3863
4873
 
3864
4874
  // src/tui/components/Header.tsx
3865
4875
  import { Box as Box2, Text as Text2 } from "ink";
@@ -3868,7 +4878,7 @@ import { useEffect as useEffect3, useMemo as useMemo2, useRef as useRef2, useSta
3868
4878
  // package.json
3869
4879
  var package_default = {
3870
4880
  name: "valheim-oz-dsm",
3871
- version: "1.5.4",
4881
+ version: "1.8.0",
3872
4882
  description: "Land of OZ - Valheim Dedicated Server Manager",
3873
4883
  type: "module",
3874
4884
  bin: {
@@ -3877,7 +4887,6 @@ var package_default = {
3877
4887
  main: "./dist/main.js",
3878
4888
  files: [
3879
4889
  "dist",
3880
- "patches",
3881
4890
  "README.md",
3882
4891
  "LICENSE",
3883
4892
  "CHANGELOG.md"
@@ -3898,12 +4907,11 @@ var package_default = {
3898
4907
  "lint:fix": "biome check --write .",
3899
4908
  format: "biome format --write .",
3900
4909
  typecheck: "tsc --noEmit",
3901
- postinstall: "patch-package",
3902
4910
  prepare: "tsx scripts/install-hooks.ts",
3903
4911
  prepublishOnly: "npm run typecheck && npm run lint && npm test && npm run build"
3904
4912
  },
3905
4913
  dependencies: {
3906
- "@caleb-collar/steamcmd": "^1.1.0",
4914
+ "@caleb-collar/steamcmd": "^1.1.1",
3907
4915
  conf: "^13.0.1",
3908
4916
  "fullscreen-ink": "^0.1.0",
3909
4917
  ink: "^6.6.0",
@@ -3916,7 +4924,6 @@ var package_default = {
3916
4924
  "@types/node": "^22.13.1",
3917
4925
  "@types/react": "^19.2.10",
3918
4926
  "@vitest/coverage-v8": "^3.2.4",
3919
- "patch-package": "^8.0.1",
3920
4927
  tsup: "^8.3.6",
3921
4928
  tsx: "^4.19.2",
3922
4929
  typescript: "^5.7.3",
@@ -4038,13 +5045,13 @@ var useStore = create((set) => ({
4038
5045
  },
4039
5046
  // Initial RCON state
4040
5047
  rcon: {
4041
- enabled: false,
5048
+ enabled: true,
4042
5049
  connected: false,
4043
5050
  port: 25575,
4044
- password: "",
5051
+ password: "valheim-rcon",
4045
5052
  host: "localhost",
4046
5053
  timeout: 5e3,
4047
- autoReconnect: false
5054
+ autoReconnect: true
4048
5055
  },
4049
5056
  // Initial worlds state
4050
5057
  worlds: {
@@ -4093,6 +5100,9 @@ var useStore = create((set) => ({
4093
5100
  players: state.server.players.filter((p) => p !== name)
4094
5101
  }
4095
5102
  })),
5103
+ setPlayers: (players) => set((state) => ({
5104
+ server: { ...state.server, players }
5105
+ })),
4096
5106
  incrementUptime: () => set((state) => ({
4097
5107
  server: { ...state.server, uptime: state.server.uptime + 1 }
4098
5108
  })),
@@ -42985,22 +43995,328 @@ var Console = () => {
42985
43995
  };
42986
43996
 
42987
43997
  // src/tui/screens/Dashboard.tsx
42988
- import { Box as Box11, Text as Text11, useInput as useInput4 } from "ink";
42989
- import { useEffect as useEffect7, useState as useState6 } from "react";
43998
+ import { Box as Box16, Text as Text16, useInput as useInput9 } from "ink";
43999
+ import { useEffect as useEffect9, useState as useState11 } from "react";
42990
44000
 
42991
- // src/tui/components/Modal.tsx
44001
+ // src/tui/components/EventManager.tsx
44002
+ init_mod();
42992
44003
  import { Box as Box9, Text as Text9, useInput as useInput3 } from "ink";
44004
+ import { useState as useState5 } from "react";
42993
44005
  import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
44006
+ var EVENT_LIST = [
44007
+ {
44008
+ key: ValheimEvents.ARMY_EIKTHYR,
44009
+ name: "Eikthyr Army",
44010
+ description: "Eikthyr rallies the creatures"
44011
+ },
44012
+ {
44013
+ key: ValheimEvents.ARMY_THEELDER,
44014
+ name: "The Elder's Hunt",
44015
+ description: "The Elder is hunting you"
44016
+ },
44017
+ {
44018
+ key: ValheimEvents.ARMY_BONEMASS,
44019
+ name: "Bonemass Army",
44020
+ description: "A foul smell from the swamp"
44021
+ },
44022
+ {
44023
+ key: ValheimEvents.ARMY_MODER,
44024
+ name: "Moder's Hunt",
44025
+ description: "You are being hunted"
44026
+ },
44027
+ {
44028
+ key: ValheimEvents.ARMY_GOBLIN,
44029
+ name: "Goblin Horde",
44030
+ description: "The horde is attacking"
44031
+ },
44032
+ {
44033
+ key: ValheimEvents.FORESTTROLLS,
44034
+ name: "Forest Trolls",
44035
+ description: "The forest is moving"
44036
+ },
44037
+ {
44038
+ key: ValheimEvents.SKELETONS,
44039
+ name: "Skeletons",
44040
+ description: "Skeleton surprise"
44041
+ },
44042
+ {
44043
+ key: ValheimEvents.BLOBS,
44044
+ name: "Blobs",
44045
+ description: "Blob attack"
44046
+ },
44047
+ {
44048
+ key: ValheimEvents.WOLVES,
44049
+ name: "Wolves",
44050
+ description: "You are being hunted"
44051
+ },
44052
+ {
44053
+ key: ValheimEvents.BATS,
44054
+ name: "Bats",
44055
+ description: "Bat swarm"
44056
+ },
44057
+ {
44058
+ key: ValheimEvents.SERPENTS,
44059
+ name: "Serpents",
44060
+ description: "Sea serpents"
44061
+ }
44062
+ ];
44063
+ var EventManager = ({ onClose }) => {
44064
+ const rconConnected = useStore((s) => s.rcon.connected);
44065
+ const addLog = useStore((s) => s.actions.addLog);
44066
+ const [selectedIndex, setSelectedIndex] = useState5(0);
44067
+ const handleTriggerEvent = async (eventKey, eventName) => {
44068
+ const response = await rconManager.triggerEvent(eventKey);
44069
+ if (response) {
44070
+ addLog("info", `Triggered event: ${eventName}`);
44071
+ } else {
44072
+ addLog("error", `Failed to trigger event: ${eventName}`);
44073
+ }
44074
+ };
44075
+ const handleRandomEvent = async () => {
44076
+ const response = await rconManager.triggerRandomEvent();
44077
+ if (response) {
44078
+ addLog("info", `Triggered random event: ${response}`);
44079
+ } else {
44080
+ addLog("error", "Failed to trigger random event");
44081
+ }
44082
+ };
44083
+ const handleStopEvent = async () => {
44084
+ const response = await rconManager.stopEvent();
44085
+ if (response) {
44086
+ addLog("info", `Stopped event: ${response}`);
44087
+ } else {
44088
+ addLog("error", "Failed to stop event");
44089
+ }
44090
+ };
44091
+ useInput3((input, key) => {
44092
+ if (key.escape || input === "q" || input === "Q") {
44093
+ onClose();
44094
+ return;
44095
+ }
44096
+ if (key.upArrow) {
44097
+ setSelectedIndex(Math.max(0, selectedIndex - 1));
44098
+ } else if (key.downArrow) {
44099
+ setSelectedIndex(Math.min(EVENT_LIST.length - 1, selectedIndex + 1));
44100
+ }
44101
+ if (key.return) {
44102
+ const selectedEvent = EVENT_LIST[selectedIndex];
44103
+ handleTriggerEvent(selectedEvent.key, selectedEvent.name);
44104
+ } else if (input === "r" || input === "R") {
44105
+ handleRandomEvent();
44106
+ } else if (input === "x" || input === "X") {
44107
+ handleStopEvent();
44108
+ }
44109
+ });
44110
+ if (!rconConnected) {
44111
+ return /* @__PURE__ */ jsxs8(
44112
+ Box9,
44113
+ {
44114
+ flexDirection: "column",
44115
+ borderStyle: "round",
44116
+ borderColor: theme.error,
44117
+ padding: 1,
44118
+ width: 60,
44119
+ children: [
44120
+ /* @__PURE__ */ jsx9(Box9, { marginBottom: 1, children: /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.error, children: "\u26A0 Event Manager" }) }),
44121
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "RCON not connected" }),
44122
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Event management requires RCON connection" }),
44123
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "[Esc/Q] Close" }) })
44124
+ ]
44125
+ }
44126
+ );
44127
+ }
44128
+ return /* @__PURE__ */ jsxs8(
44129
+ Box9,
44130
+ {
44131
+ flexDirection: "column",
44132
+ borderStyle: "round",
44133
+ borderColor: theme.primary,
44134
+ padding: 1,
44135
+ width: 70,
44136
+ height: 25,
44137
+ children: [
44138
+ /* @__PURE__ */ jsx9(Box9, { marginBottom: 1, children: /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.primary, children: "Event Manager" }) }),
44139
+ /* @__PURE__ */ jsx9(Box9, { marginBottom: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Use \u2191\u2193 to select, [Enter] Trigger, [R] Random, [X] Stop" }) }),
44140
+ /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", flexGrow: 1, children: EVENT_LIST.map((event, index) => /* @__PURE__ */ jsxs8(Box9, { marginLeft: 1, children: [
44141
+ /* @__PURE__ */ jsxs8(
44142
+ Text9,
44143
+ {
44144
+ color: index === selectedIndex ? theme.primary : theme.secondary,
44145
+ bold: index === selectedIndex,
44146
+ children: [
44147
+ index === selectedIndex ? "\u2192 " : " ",
44148
+ event.name
44149
+ ]
44150
+ }
44151
+ ),
44152
+ /* @__PURE__ */ jsxs8(Text9, { dimColor: true, children: [
44153
+ " - ",
44154
+ event.description
44155
+ ] })
44156
+ ] }, event.key)) }),
44157
+ /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "column", children: [
44158
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "[Enter] Trigger Selected" }),
44159
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "[R] Random Event [X] Stop Event" }),
44160
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "[Esc/Q] Close" })
44161
+ ] })
44162
+ ]
44163
+ }
44164
+ );
44165
+ };
44166
+
44167
+ // src/tui/components/GlobalKeysManager.tsx
44168
+ init_mod();
44169
+ import { Box as Box10, Text as Text10, useInput as useInput4 } from "ink";
44170
+ import { useEffect as useEffect5, useState as useState6 } from "react";
44171
+ import { Fragment, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
44172
+ var BOSS_KEYS = [
44173
+ { key: ValheimGlobalKeys.DEFEATED_EIKTHYR, name: "Eikthyr" },
44174
+ { key: ValheimGlobalKeys.DEFEATED_GDKING, name: "The Elder" },
44175
+ { key: ValheimGlobalKeys.DEFEATED_BONEMASS, name: "Bonemass" },
44176
+ { key: ValheimGlobalKeys.DEFEATED_DRAGON, name: "Moder" },
44177
+ { key: ValheimGlobalKeys.DEFEATED_GOBLINKING, name: "Yagluth" },
44178
+ { key: ValheimGlobalKeys.DEFEATED_QUEEN, name: "The Queen" }
44179
+ ];
44180
+ var GlobalKeysManager = ({ onClose }) => {
44181
+ const rconConnected = useStore((s) => s.rcon.connected);
44182
+ const addLog = useStore((s) => s.actions.addLog);
44183
+ const [selectedIndex, setSelectedIndex] = useState6(0);
44184
+ const [activeKeys, setActiveKeys] = useState6([]);
44185
+ const [loading, setLoading] = useState6(true);
44186
+ useEffect5(() => {
44187
+ const loadKeys = async () => {
44188
+ setLoading(true);
44189
+ const keys = await rconManager.listGlobalKeys();
44190
+ setActiveKeys(keys);
44191
+ setLoading(false);
44192
+ };
44193
+ if (rconConnected) {
44194
+ loadKeys();
44195
+ }
44196
+ }, [rconConnected]);
44197
+ const isBossDefeated = (bossKey) => {
44198
+ return activeKeys.some(
44199
+ (key) => key.toLowerCase().includes(bossKey.toLowerCase())
44200
+ );
44201
+ };
44202
+ const handleToggleKey = async (keyName, isActive) => {
44203
+ if (isActive) {
44204
+ const response = await rconManager.removeGlobalKey(keyName);
44205
+ if (response) {
44206
+ addLog("info", `Removed key: ${keyName}`);
44207
+ const keys = await rconManager.listGlobalKeys();
44208
+ setActiveKeys(keys);
44209
+ } else {
44210
+ addLog("error", `Failed to remove key: ${keyName}`);
44211
+ }
44212
+ } else {
44213
+ const response = await rconManager.setGlobalKey(keyName);
44214
+ if (response) {
44215
+ addLog("info", `Set key: ${keyName}`);
44216
+ const keys = await rconManager.listGlobalKeys();
44217
+ setActiveKeys(keys);
44218
+ } else {
44219
+ addLog("error", `Failed to set key: ${keyName}`);
44220
+ }
44221
+ }
44222
+ };
44223
+ const handleResetAll = async () => {
44224
+ const response = await rconManager.resetGlobalKeys();
44225
+ if (response) {
44226
+ addLog("warn", "Reset all global keys");
44227
+ setActiveKeys([]);
44228
+ } else {
44229
+ addLog("error", "Failed to reset keys");
44230
+ }
44231
+ };
44232
+ useInput4((input, key) => {
44233
+ if (key.escape || input === "q" || input === "Q") {
44234
+ onClose();
44235
+ return;
44236
+ }
44237
+ if (key.upArrow) {
44238
+ setSelectedIndex(Math.max(0, selectedIndex - 1));
44239
+ } else if (key.downArrow) {
44240
+ setSelectedIndex(Math.min(BOSS_KEYS.length - 1, selectedIndex + 1));
44241
+ }
44242
+ if (input === " " || key.return) {
44243
+ const selected = BOSS_KEYS[selectedIndex];
44244
+ const isActive = isBossDefeated(selected.key);
44245
+ handleToggleKey(selected.key, isActive);
44246
+ } else if (input === "r" || input === "R") {
44247
+ handleResetAll();
44248
+ }
44249
+ });
44250
+ if (!rconConnected) {
44251
+ return /* @__PURE__ */ jsxs9(
44252
+ Box10,
44253
+ {
44254
+ flexDirection: "column",
44255
+ borderStyle: "round",
44256
+ borderColor: theme.error,
44257
+ padding: 1,
44258
+ width: 60,
44259
+ children: [
44260
+ /* @__PURE__ */ jsx10(Box10, { marginBottom: 1, children: /* @__PURE__ */ jsx10(Text10, { bold: true, color: theme.error, children: "\u26A0 Global Keys Manager" }) }),
44261
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "RCON not connected" }),
44262
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "Key management requires RCON connection" }),
44263
+ /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "[Esc/Q] Close" }) })
44264
+ ]
44265
+ }
44266
+ );
44267
+ }
44268
+ return /* @__PURE__ */ jsxs9(
44269
+ Box10,
44270
+ {
44271
+ flexDirection: "column",
44272
+ borderStyle: "round",
44273
+ borderColor: theme.primary,
44274
+ padding: 1,
44275
+ width: 60,
44276
+ children: [
44277
+ /* @__PURE__ */ jsx10(Box10, { marginBottom: 1, children: /* @__PURE__ */ jsx10(Text10, { bold: true, color: theme.primary, children: "Boss Progression" }) }),
44278
+ loading ? /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "Loading keys..." }) : /* @__PURE__ */ jsxs9(Fragment, { children: [
44279
+ /* @__PURE__ */ jsx10(Box10, { marginBottom: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "Use \u2191\u2193 to select, [Space/Enter] to toggle" }) }),
44280
+ /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: BOSS_KEYS.map((boss, index) => {
44281
+ const defeated = isBossDefeated(boss.key);
44282
+ return /* @__PURE__ */ jsx10(Box10, { marginLeft: 1, children: /* @__PURE__ */ jsxs9(
44283
+ Text10,
44284
+ {
44285
+ color: index === selectedIndex ? theme.primary : theme.secondary,
44286
+ bold: index === selectedIndex,
44287
+ children: [
44288
+ index === selectedIndex ? "\u2192 " : " ",
44289
+ defeated ? "\u2611" : "\u2610",
44290
+ " ",
44291
+ boss.name
44292
+ ]
44293
+ }
44294
+ ) }, boss.key);
44295
+ }) }),
44296
+ /* @__PURE__ */ jsx10(Box10, { marginTop: 2, flexDirection: "column", children: /* @__PURE__ */ jsx10(Text10, { color: theme.warning, children: "\u26A0\uFE0F Warning: Affects world progression" }) })
44297
+ ] }),
44298
+ /* @__PURE__ */ jsxs9(Box10, { marginTop: 1, flexDirection: "column", children: [
44299
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "[Space/Enter] Toggle [R] Reset All" }),
44300
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "[Esc/Q] Close" })
44301
+ ] })
44302
+ ]
44303
+ }
44304
+ );
44305
+ };
44306
+
44307
+ // src/tui/components/Modal.tsx
44308
+ import { Box as Box11, Text as Text11, useInput as useInput5 } from "ink";
44309
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
42994
44310
  var Modal = (props) => {
42995
44311
  const { title, children, width = 50 } = props;
42996
44312
  const closeModal = useStore((s) => s.actions.closeModal);
42997
- useInput3((_input, key) => {
44313
+ useInput5((_input, key) => {
42998
44314
  if (key.escape) {
42999
44315
  closeModal();
43000
44316
  }
43001
44317
  });
43002
- return /* @__PURE__ */ jsxs8(
43003
- Box9,
44318
+ return /* @__PURE__ */ jsxs10(
44319
+ Box11,
43004
44320
  {
43005
44321
  flexDirection: "column",
43006
44322
  width,
@@ -43010,36 +44326,36 @@ var Modal = (props) => {
43010
44326
  paddingX: 2,
43011
44327
  paddingY: 1,
43012
44328
  children: [
43013
- /* @__PURE__ */ jsxs8(Box9, { justifyContent: "space-between", marginBottom: 1, children: [
43014
- /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.primary, children: title }),
43015
- /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "[ESC to close]" })
44329
+ /* @__PURE__ */ jsxs10(Box11, { justifyContent: "space-between", marginBottom: 1, children: [
44330
+ /* @__PURE__ */ jsx11(Text11, { bold: true, color: theme.primary, children: title }),
44331
+ /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "[ESC to close]" })
43016
44332
  ] }),
43017
- /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", children })
44333
+ /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children })
43018
44334
  ]
43019
44335
  }
43020
44336
  );
43021
44337
  };
43022
44338
  var ConfirmModal = (props) => {
43023
44339
  const { message, onConfirm, onCancel } = props;
43024
- useInput3((input, key) => {
44340
+ useInput5((input, key) => {
43025
44341
  if (input === "y" || input === "Y") {
43026
44342
  onConfirm();
43027
44343
  } else if (input === "n" || input === "N" || key.escape) {
43028
44344
  onCancel();
43029
44345
  }
43030
44346
  });
43031
- return /* @__PURE__ */ jsxs8(Modal, { title: "Confirm", width: 40, children: [
43032
- /* @__PURE__ */ jsx9(Text9, { children: message }),
43033
- /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, children: [
43034
- /* @__PURE__ */ jsx9(Text9, { color: theme.success, children: "[Y] Yes" }),
43035
- /* @__PURE__ */ jsx9(Text9, {}),
43036
- /* @__PURE__ */ jsx9(Text9, { color: theme.error, children: "[N] No" })
44347
+ return /* @__PURE__ */ jsxs10(Modal, { title: "Confirm", width: 40, children: [
44348
+ /* @__PURE__ */ jsx11(Text11, { children: message }),
44349
+ /* @__PURE__ */ jsxs10(Box11, { marginTop: 1, children: [
44350
+ /* @__PURE__ */ jsx11(Text11, { color: theme.success, children: "[Y] Yes" }),
44351
+ /* @__PURE__ */ jsx11(Text11, {}),
44352
+ /* @__PURE__ */ jsx11(Text11, { color: theme.error, children: "[N] No" })
43037
44353
  ] })
43038
44354
  ] });
43039
44355
  };
43040
44356
  var DeleteWorldModal = (props) => {
43041
44357
  const { worldName, backupCount, onConfirm, onCancel } = props;
43042
- useInput3((input, key) => {
44358
+ useInput5((input, key) => {
43043
44359
  if (backupCount > 0) {
43044
44360
  if (input === "y" || input === "Y") {
43045
44361
  onConfirm(true);
@@ -43057,105 +44373,438 @@ var DeleteWorldModal = (props) => {
43057
44373
  }
43058
44374
  });
43059
44375
  if (backupCount > 0) {
43060
- return /* @__PURE__ */ jsxs8(Modal, { title: "Delete World", width: 50, children: [
43061
- /* @__PURE__ */ jsxs8(Text9, { children: [
44376
+ return /* @__PURE__ */ jsxs10(Modal, { title: "Delete World", width: 50, children: [
44377
+ /* @__PURE__ */ jsxs10(Text11, { children: [
43062
44378
  'Delete world "',
43063
44379
  worldName,
43064
44380
  '"?'
43065
44381
  ] }),
43066
- /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text9, { dimColor: true, children: [
44382
+ /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
43067
44383
  "This world has ",
43068
44384
  backupCount,
43069
44385
  " backup",
43070
44386
  backupCount === 1 ? "" : "s",
43071
44387
  "."
43072
44388
  ] }) }),
43073
- /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "column", children: [
43074
- /* @__PURE__ */ jsx9(Text9, { color: theme.success, children: "[Y] Delete world and backups" }),
43075
- /* @__PURE__ */ jsx9(Text9, { color: theme.warning, children: "[N] Delete world only" }),
43076
- /* @__PURE__ */ jsx9(Text9, { color: theme.error, children: "[Esc] Cancel" })
44389
+ /* @__PURE__ */ jsxs10(Box11, { marginTop: 1, flexDirection: "column", children: [
44390
+ /* @__PURE__ */ jsx11(Text11, { color: theme.success, children: "[Y] Delete world and backups" }),
44391
+ /* @__PURE__ */ jsx11(Text11, { color: theme.warning, children: "[N] Delete world only" }),
44392
+ /* @__PURE__ */ jsx11(Text11, { color: theme.error, children: "[Esc] Cancel" })
43077
44393
  ] })
43078
44394
  ] });
43079
44395
  }
43080
- return /* @__PURE__ */ jsxs8(Modal, { title: "Confirm", width: 40, children: [
43081
- /* @__PURE__ */ jsxs8(Text9, { children: [
44396
+ return /* @__PURE__ */ jsxs10(Modal, { title: "Confirm", width: 40, children: [
44397
+ /* @__PURE__ */ jsxs10(Text11, { children: [
43082
44398
  'Delete world "',
43083
44399
  worldName,
43084
44400
  '"? This cannot be undone!'
43085
44401
  ] }),
43086
- /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, children: [
43087
- /* @__PURE__ */ jsx9(Text9, { color: theme.success, children: "[Y] Yes" }),
43088
- /* @__PURE__ */ jsx9(Text9, {}),
43089
- /* @__PURE__ */ jsx9(Text9, { color: theme.error, children: "[N] No" })
44402
+ /* @__PURE__ */ jsxs10(Box11, { marginTop: 1, children: [
44403
+ /* @__PURE__ */ jsx11(Text11, { color: theme.success, children: "[Y] Yes" }),
44404
+ /* @__PURE__ */ jsx11(Text11, {}),
44405
+ /* @__PURE__ */ jsx11(Text11, { color: theme.error, children: "[N] No" })
43090
44406
  ] })
43091
44407
  ] });
43092
44408
  };
43093
44409
 
44410
+ // src/tui/components/PlayerManager.tsx
44411
+ init_mod();
44412
+ import { Box as Box12, Text as Text12, useInput as useInput6 } from "ink";
44413
+ import { useState as useState7 } from "react";
44414
+ import { Fragment as Fragment2, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
44415
+ var PlayerManager = ({ onClose }) => {
44416
+ const players = useStore((s) => s.server.players);
44417
+ const rconConnected = useStore((s) => s.rcon.connected);
44418
+ const addLog = useStore((s) => s.actions.addLog);
44419
+ const [selectedIndex, setSelectedIndex] = useState7(0);
44420
+ const [view, setView] = useState7("players");
44421
+ const [bannedPlayers, setBannedPlayers] = useState7([]);
44422
+ const [loading, setLoading] = useState7(false);
44423
+ const loadBannedPlayers = async () => {
44424
+ setLoading(true);
44425
+ const banned = await rconManager.getBannedPlayers();
44426
+ setBannedPlayers(banned);
44427
+ setLoading(false);
44428
+ };
44429
+ const handleKick = async (playerName) => {
44430
+ const response = await rconManager.kickPlayer(playerName);
44431
+ if (response) {
44432
+ addLog("info", `Kicked ${playerName}: ${response}`);
44433
+ } else {
44434
+ addLog("error", `Failed to kick ${playerName}`);
44435
+ }
44436
+ };
44437
+ const handleBan = async (playerName) => {
44438
+ const response = await rconManager.banPlayer(playerName);
44439
+ if (response) {
44440
+ addLog("warn", `Banned ${playerName}: ${response}`);
44441
+ } else {
44442
+ addLog("error", `Failed to ban ${playerName}`);
44443
+ }
44444
+ };
44445
+ const handleUnban = async (playerIdentifier) => {
44446
+ const response = await rconManager.unbanPlayer(playerIdentifier);
44447
+ if (response) {
44448
+ addLog("info", `Unbanned ${playerIdentifier}: ${response}`);
44449
+ await loadBannedPlayers();
44450
+ } else {
44451
+ addLog("error", `Failed to unban ${playerIdentifier}`);
44452
+ }
44453
+ };
44454
+ useInput6((input, key) => {
44455
+ if (key.escape || input === "q" || input === "Q") {
44456
+ onClose();
44457
+ return;
44458
+ }
44459
+ if (input === "t" || input === "T") {
44460
+ if (view === "players") {
44461
+ setView("banned");
44462
+ loadBannedPlayers();
44463
+ } else {
44464
+ setView("players");
44465
+ }
44466
+ setSelectedIndex(0);
44467
+ return;
44468
+ }
44469
+ const currentList = view === "players" ? players : bannedPlayers;
44470
+ const maxIndex = Math.max(0, currentList.length - 1);
44471
+ if (key.upArrow) {
44472
+ setSelectedIndex(Math.max(0, selectedIndex - 1));
44473
+ } else if (key.downArrow) {
44474
+ setSelectedIndex(Math.min(maxIndex, selectedIndex + 1));
44475
+ }
44476
+ if (view === "players" && players.length > 0) {
44477
+ const selectedPlayer = players[selectedIndex];
44478
+ if (input === "k" || input === "K") {
44479
+ handleKick(selectedPlayer);
44480
+ } else if (input === "b" || input === "B") {
44481
+ handleBan(selectedPlayer);
44482
+ }
44483
+ } else if (view === "banned" && bannedPlayers.length > 0) {
44484
+ const selectedBanned = bannedPlayers[selectedIndex];
44485
+ if (input === "u" || input === "U") {
44486
+ handleUnban(selectedBanned);
44487
+ }
44488
+ }
44489
+ });
44490
+ if (!rconConnected) {
44491
+ return /* @__PURE__ */ jsxs11(
44492
+ Box12,
44493
+ {
44494
+ flexDirection: "column",
44495
+ borderStyle: "round",
44496
+ borderColor: theme.error,
44497
+ padding: 1,
44498
+ width: 60,
44499
+ children: [
44500
+ /* @__PURE__ */ jsx12(Box12, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Text12, { bold: true, color: theme.error, children: "\u26A0 Player Management" }) }),
44501
+ /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "RCON not connected" }),
44502
+ /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "Player management requires RCON connection" }),
44503
+ /* @__PURE__ */ jsx12(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "[Esc/Q] Close" }) })
44504
+ ]
44505
+ }
44506
+ );
44507
+ }
44508
+ return /* @__PURE__ */ jsxs11(
44509
+ Box12,
44510
+ {
44511
+ flexDirection: "column",
44512
+ borderStyle: "round",
44513
+ borderColor: theme.primary,
44514
+ padding: 1,
44515
+ width: 70,
44516
+ children: [
44517
+ /* @__PURE__ */ jsxs11(Box12, { marginBottom: 1, children: [
44518
+ /* @__PURE__ */ jsx12(Text12, { bold: true, color: theme.primary, children: "Player Management" }),
44519
+ /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: " - " }),
44520
+ /* @__PURE__ */ jsxs11(Text12, { color: view === "players" ? theme.success : theme.muted, children: [
44521
+ "[T] ",
44522
+ view === "players" ? "Online Players" : "Banned Players"
44523
+ ] })
44524
+ ] }),
44525
+ view === "players" ? /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", children: players.length === 0 ? /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "No players currently online" }) : /* @__PURE__ */ jsxs11(Fragment2, { children: [
44526
+ /* @__PURE__ */ jsx12(Box12, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "Use \u2191\u2193 to select, [K] Kick, [B] Ban" }) }),
44527
+ players.map((player, index) => /* @__PURE__ */ jsx12(Box12, { marginLeft: 1, children: /* @__PURE__ */ jsxs11(
44528
+ Text12,
44529
+ {
44530
+ color: index === selectedIndex ? theme.primary : theme.secondary,
44531
+ bold: index === selectedIndex,
44532
+ children: [
44533
+ index === selectedIndex ? "\u2192 " : " ",
44534
+ player
44535
+ ]
44536
+ }
44537
+ ) }, player))
44538
+ ] }) }) : /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", children: loading ? /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "Loading banned players..." }) : bannedPlayers.length === 0 ? /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "No banned players" }) : /* @__PURE__ */ jsxs11(Fragment2, { children: [
44539
+ /* @__PURE__ */ jsx12(Box12, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "Use \u2191\u2193 to select, [U] Unban" }) }),
44540
+ bannedPlayers.map((banned, index) => /* @__PURE__ */ jsx12(Box12, { marginLeft: 1, children: /* @__PURE__ */ jsxs11(
44541
+ Text12,
44542
+ {
44543
+ color: index === selectedIndex ? theme.warning : theme.secondary,
44544
+ bold: index === selectedIndex,
44545
+ children: [
44546
+ index === selectedIndex ? "\u2192 " : " ",
44547
+ banned
44548
+ ]
44549
+ }
44550
+ ) }, banned))
44551
+ ] }) }),
44552
+ /* @__PURE__ */ jsx12(Box12, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "[Esc/Q] Close [T] Toggle View" }) })
44553
+ ]
44554
+ }
44555
+ );
44556
+ };
44557
+
44558
+ // src/tui/components/ServerInfoModal.tsx
44559
+ init_mod();
44560
+ import { Box as Box14, Text as Text14, useInput as useInput7 } from "ink";
44561
+ import { useEffect as useEffect7, useState as useState9 } from "react";
44562
+
43094
44563
  // src/tui/components/Spinner.tsx
43095
- import { Box as Box10, Text as Text10 } from "ink";
43096
- import { useEffect as useEffect5, useState as useState5 } from "react";
43097
- import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
44564
+ import { Box as Box13, Text as Text13 } from "ink";
44565
+ import { useEffect as useEffect6, useState as useState8 } from "react";
44566
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
43098
44567
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
43099
44568
  var SPINNER_INTERVAL = 80;
43100
44569
  var Spinner = (props) => {
43101
44570
  const { label, color = valheimPalette.mandarin } = props;
43102
- const [frame, setFrame] = useState5(0);
43103
- useEffect5(() => {
44571
+ const [frame, setFrame] = useState8(0);
44572
+ useEffect6(() => {
43104
44573
  const timer = setInterval(() => {
43105
44574
  setFrame((f) => (f + 1) % SPINNER_FRAMES.length);
43106
44575
  }, SPINNER_INTERVAL);
43107
44576
  return () => clearInterval(timer);
43108
44577
  }, []);
43109
- return /* @__PURE__ */ jsxs9(Box10, { children: [
43110
- /* @__PURE__ */ jsx10(Text10, { color, children: SPINNER_FRAMES[frame] }),
43111
- label && /* @__PURE__ */ jsxs9(Text10, { children: [
44578
+ return /* @__PURE__ */ jsxs12(Box13, { children: [
44579
+ /* @__PURE__ */ jsx13(Text13, { color, children: SPINNER_FRAMES[frame] }),
44580
+ label && /* @__PURE__ */ jsxs12(Text13, { children: [
43112
44581
  " ",
43113
44582
  label
43114
44583
  ] })
43115
44584
  ] });
43116
44585
  };
43117
44586
 
44587
+ // src/tui/components/ServerInfoModal.tsx
44588
+ import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
44589
+ var ServerInfoModal = ({ onClose }) => {
44590
+ const [info2, setInfo] = useState9("");
44591
+ const [ping, setPing] = useState9("");
44592
+ const [loading, setLoading] = useState9(true);
44593
+ useEffect7(() => {
44594
+ const fetchInfo = async () => {
44595
+ setLoading(true);
44596
+ const [infoResponse, pingResponse] = await Promise.all([
44597
+ rconManager.getServerInfo(),
44598
+ rconManager.pingServer()
44599
+ ]);
44600
+ setInfo(infoResponse || "No info available");
44601
+ setPing(pingResponse || "");
44602
+ setLoading(false);
44603
+ };
44604
+ fetchInfo();
44605
+ }, []);
44606
+ useInput7((input, key) => {
44607
+ if (key.escape || input === "q" || input === "Q") {
44608
+ onClose();
44609
+ }
44610
+ });
44611
+ return /* @__PURE__ */ jsxs13(
44612
+ Box14,
44613
+ {
44614
+ flexDirection: "column",
44615
+ borderStyle: "round",
44616
+ borderColor: theme.primary,
44617
+ padding: 1,
44618
+ width: 70,
44619
+ children: [
44620
+ /* @__PURE__ */ jsx14(Box14, { marginBottom: 1, children: /* @__PURE__ */ jsx14(Text14, { bold: true, color: theme.primary, children: "Server Information" }) }),
44621
+ loading ? /* @__PURE__ */ jsx14(Box14, { marginY: 2, children: /* @__PURE__ */ jsx14(Spinner, { label: "Fetching server info..." }) }) : /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
44622
+ /* @__PURE__ */ jsx14(Box14, { marginBottom: 1, children: /* @__PURE__ */ jsx14(Text14, { bold: true, children: "Info:" }) }),
44623
+ /* @__PURE__ */ jsx14(Box14, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx14(Text14, { children: info2 }) }),
44624
+ ping && /* @__PURE__ */ jsxs13(Fragment3, { children: [
44625
+ /* @__PURE__ */ jsx14(Box14, { marginBottom: 1, children: /* @__PURE__ */ jsx14(Text14, { bold: true, children: "Ping:" }) }),
44626
+ /* @__PURE__ */ jsx14(Box14, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx14(Text14, { color: theme.success, children: ping }) })
44627
+ ] })
44628
+ ] }),
44629
+ /* @__PURE__ */ jsx14(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: "[Esc/Q] Close" }) })
44630
+ ]
44631
+ }
44632
+ );
44633
+ };
44634
+
44635
+ // src/tui/components/TimeControl.tsx
44636
+ init_mod();
44637
+ import { Box as Box15, Text as Text15, useInput as useInput8 } from "ink";
44638
+ import { useState as useState10 } from "react";
44639
+ import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
44640
+ var TIME_OPTIONS = [
44641
+ { index: 0, label: "Sleep (Skip to morning)", seconds: 0 },
44642
+ { index: 1, label: "Skip 1 hour", seconds: 3600 },
44643
+ { index: 2, label: "Skip 3 hours", seconds: 10800 },
44644
+ { index: 3, label: "Skip 6 hours", seconds: 21600 },
44645
+ { index: 4, label: "Skip 12 hours", seconds: 43200 },
44646
+ { index: 5, label: "Skip 1 day", seconds: 86400 }
44647
+ ];
44648
+ var TimeControl = ({ onClose }) => {
44649
+ const rconConnected = useStore((s) => s.rcon.connected);
44650
+ const addLog = useStore((s) => s.actions.addLog);
44651
+ const [selectedIndex, setSelectedIndex] = useState10(0);
44652
+ const handleTimeSkip = async (seconds, label) => {
44653
+ if (seconds === 0) {
44654
+ const response = await rconManager.sleep();
44655
+ if (response) {
44656
+ addLog("info", `Sleeping through night: ${response}`);
44657
+ } else {
44658
+ addLog("error", "Failed to sleep");
44659
+ }
44660
+ } else {
44661
+ const response = await rconManager.skipTime(seconds);
44662
+ if (response) {
44663
+ addLog("info", `${label}: ${response}`);
44664
+ } else {
44665
+ addLog("error", `Failed to skip time: ${label}`);
44666
+ }
44667
+ }
44668
+ };
44669
+ useInput8((input, key) => {
44670
+ if (key.escape || input === "q" || input === "Q") {
44671
+ onClose();
44672
+ return;
44673
+ }
44674
+ if (key.upArrow) {
44675
+ setSelectedIndex(Math.max(0, selectedIndex - 1));
44676
+ } else if (key.downArrow) {
44677
+ setSelectedIndex(Math.min(TIME_OPTIONS.length - 1, selectedIndex + 1));
44678
+ }
44679
+ if (key.return) {
44680
+ const selected = TIME_OPTIONS[selectedIndex];
44681
+ handleTimeSkip(selected.seconds, selected.label);
44682
+ }
44683
+ });
44684
+ if (!rconConnected) {
44685
+ return /* @__PURE__ */ jsxs14(
44686
+ Box15,
44687
+ {
44688
+ flexDirection: "column",
44689
+ borderStyle: "round",
44690
+ borderColor: theme.error,
44691
+ padding: 1,
44692
+ width: 60,
44693
+ children: [
44694
+ /* @__PURE__ */ jsx15(Box15, { marginBottom: 1, children: /* @__PURE__ */ jsx15(Text15, { bold: true, color: theme.error, children: "\u26A0 Time Control" }) }),
44695
+ /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "RCON not connected" }),
44696
+ /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "Time control requires RCON connection" }),
44697
+ /* @__PURE__ */ jsx15(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "[Esc/Q] Close" }) })
44698
+ ]
44699
+ }
44700
+ );
44701
+ }
44702
+ return /* @__PURE__ */ jsxs14(
44703
+ Box15,
44704
+ {
44705
+ flexDirection: "column",
44706
+ borderStyle: "round",
44707
+ borderColor: theme.primary,
44708
+ padding: 1,
44709
+ width: 50,
44710
+ children: [
44711
+ /* @__PURE__ */ jsx15(Box15, { marginBottom: 1, children: /* @__PURE__ */ jsx15(Text15, { bold: true, color: theme.primary, children: "Time Control" }) }),
44712
+ /* @__PURE__ */ jsx15(Box15, { marginBottom: 1, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "Use \u2191\u2193 to select, [Enter] to execute" }) }),
44713
+ /* @__PURE__ */ jsx15(Box15, { flexDirection: "column", children: TIME_OPTIONS.map((option) => /* @__PURE__ */ jsx15(Box15, { marginLeft: 1, children: /* @__PURE__ */ jsxs14(
44714
+ Text15,
44715
+ {
44716
+ color: option.index === selectedIndex ? theme.primary : theme.secondary,
44717
+ bold: option.index === selectedIndex,
44718
+ children: [
44719
+ option.index === selectedIndex ? "\u2192 " : " ",
44720
+ option.label
44721
+ ]
44722
+ }
44723
+ ) }, option.index)) }),
44724
+ /* @__PURE__ */ jsx15(Box15, { marginTop: 2, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "\u26A0\uFE0F Warning: Time skip affects all players" }) }),
44725
+ /* @__PURE__ */ jsx15(Box15, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "[Enter] Execute [Esc/Q] Close" }) })
44726
+ ]
44727
+ }
44728
+ );
44729
+ };
44730
+
43118
44731
  // src/tui/hooks/useServer.ts
43119
- import { useCallback as useCallback3, useEffect as useEffect6 } from "react";
44732
+ init_mod();
44733
+ import { useCallback as useCallback3, useEffect as useEffect8, useRef as useRef3 } from "react";
43120
44734
 
43121
44735
  // src/tui/serverManager.ts
43122
44736
  var watchdog = null;
43123
44737
  var updating = false;
44738
+ var isAttached = false;
43124
44739
  function getWatchdog() {
43125
44740
  return watchdog;
43126
44741
  }
43127
44742
  function hasActiveServer() {
43128
44743
  return watchdog !== null;
43129
44744
  }
44745
+ function isAttachedToServer() {
44746
+ return isAttached;
44747
+ }
43130
44748
  function isUpdating() {
43131
44749
  return updating;
43132
44750
  }
43133
44751
  function setUpdating(value) {
43134
44752
  updating = value;
43135
44753
  }
44754
+ async function checkRunningServer() {
44755
+ return getRunningServer();
44756
+ }
44757
+ async function attachToServer(pidData, events) {
44758
+ if (watchdog) {
44759
+ throw new Error("Already managing a server - stop it first");
44760
+ }
44761
+ const config = {
44762
+ name: pidData.serverName ?? "Valheim Server",
44763
+ port: pidData.port,
44764
+ world: pidData.world,
44765
+ password: "",
44766
+ // Not needed for attach
44767
+ public: false,
44768
+ crossplay: false,
44769
+ detached: true
44770
+ };
44771
+ watchdog = new Watchdog(config, { enabled: false }, events);
44772
+ await watchdog.serverProcess.attach(pidData);
44773
+ isAttached = true;
44774
+ return watchdog;
44775
+ }
43136
44776
  async function startServer(config, watchdogConfig, events) {
43137
44777
  if (watchdog) {
43138
44778
  throw new Error("Server is already running");
43139
44779
  }
43140
- watchdog = new Watchdog(config, watchdogConfig, events);
43141
- await watchdog.start();
43142
- const pid = watchdog.serverProcess.pid;
43143
- if (pid) {
43144
- await writePidFile({
43145
- pid,
43146
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
43147
- world: config.world,
43148
- port: config.port
43149
- });
44780
+ const running = await getRunningServer();
44781
+ if (running) {
44782
+ throw new Error(
44783
+ `A server is already running (PID: ${running.pid}, World: ${running.world}). Stop it first or attach to it.`
44784
+ );
43150
44785
  }
44786
+ const detachedConfig = {
44787
+ ...config,
44788
+ detached: true
44789
+ };
44790
+ watchdog = new Watchdog(detachedConfig, watchdogConfig, events);
44791
+ await watchdog.start();
44792
+ isAttached = false;
43151
44793
  return watchdog;
43152
44794
  }
43153
- async function stopServer() {
44795
+ async function stopServer(keepRunning = false) {
43154
44796
  if (!watchdog) {
43155
44797
  return;
43156
44798
  }
44799
+ if (keepRunning && isAttached) {
44800
+ await watchdog.serverProcess.detach();
44801
+ watchdog = null;
44802
+ isAttached = false;
44803
+ return;
44804
+ }
43157
44805
  await watchdog.stop();
43158
44806
  watchdog = null;
44807
+ isAttached = false;
43159
44808
  await removePidFile();
43160
44809
  }
43161
44810
  async function killServer() {
@@ -43164,21 +44813,46 @@ async function killServer() {
43164
44813
  }
43165
44814
  await watchdog.kill();
43166
44815
  watchdog = null;
44816
+ isAttached = false;
43167
44817
  await removePidFile();
43168
44818
  }
44819
+ async function detachFromServer() {
44820
+ if (!watchdog) {
44821
+ return;
44822
+ }
44823
+ if (!isAttached && !watchdog.serverProcess.isDetached) {
44824
+ throw new Error("Cannot detach from a non-detached server");
44825
+ }
44826
+ await watchdog.serverProcess.detach();
44827
+ watchdog = null;
44828
+ isAttached = false;
44829
+ }
44830
+ function getLogFilePath() {
44831
+ return watchdog?.serverProcess.logFilePath ?? null;
44832
+ }
43169
44833
  async function cleanupOnExit() {
43170
- if (watchdog) {
44834
+ if (!watchdog) {
44835
+ return;
44836
+ }
44837
+ if (isAttached || watchdog.serverProcess.isDetached) {
43171
44838
  try {
43172
- await watchdog.stop();
44839
+ await watchdog.serverProcess.detach();
43173
44840
  } catch {
43174
- try {
43175
- await watchdog.kill();
43176
- } catch {
43177
- }
43178
44841
  }
43179
44842
  watchdog = null;
43180
- await removePidFile();
44843
+ isAttached = false;
44844
+ return;
44845
+ }
44846
+ try {
44847
+ await watchdog.stop();
44848
+ } catch {
44849
+ try {
44850
+ await watchdog.kill();
44851
+ } catch {
44852
+ }
43181
44853
  }
44854
+ watchdog = null;
44855
+ await removePidFile();
43182
44856
  }
43183
44857
 
43184
44858
  // src/tui/hooks/useServer.ts
@@ -43214,6 +44888,7 @@ function useServer() {
43214
44888
  const config = useStore((s) => s.config);
43215
44889
  const rcon = useStore((s) => s.rcon);
43216
44890
  const actions = useStore((s) => s.actions);
44891
+ const hasCheckedForRunning = useRef3(false);
43217
44892
  const createWatchdogEvents = useCallback3(
43218
44893
  () => ({
43219
44894
  onStateChange: (state) => {
@@ -43285,6 +44960,38 @@ function useServer() {
43285
44960
  }),
43286
44961
  [actions]
43287
44962
  );
44963
+ const checkAndAttach = useCallback3(async () => {
44964
+ if (hasActiveServer()) {
44965
+ return;
44966
+ }
44967
+ const running = await checkRunningServer();
44968
+ if (!running) {
44969
+ return;
44970
+ }
44971
+ actions.addLog(
44972
+ "info",
44973
+ `Found running server (PID: ${running.pid}, World: ${running.world})`
44974
+ );
44975
+ actions.addLog("info", "Attaching to running server...");
44976
+ try {
44977
+ const events = createWatchdogEvents();
44978
+ await attachToServer(running, events);
44979
+ actions.setServerPid(running.pid);
44980
+ actions.setWorld(running.world);
44981
+ actions.setServerStatus("online");
44982
+ actions.setStartupPhase("ready");
44983
+ actions.addLog("info", "Successfully attached to running server");
44984
+ } catch (error2) {
44985
+ actions.addLog("error", `Failed to attach to running server: ${error2}`);
44986
+ }
44987
+ }, [actions, createWatchdogEvents]);
44988
+ useEffect8(() => {
44989
+ if (hasCheckedForRunning.current) return;
44990
+ hasCheckedForRunning.current = true;
44991
+ checkAndAttach().catch((error2) => {
44992
+ actions.addLog("error", `Error checking for running server: ${error2}`);
44993
+ });
44994
+ }, [checkAndAttach, actions]);
43288
44995
  const start = useCallback3(async () => {
43289
44996
  if (status !== "offline") {
43290
44997
  actions.addLog("warn", "Server is not offline, cannot start");
@@ -43405,13 +45112,78 @@ function useServer() {
43405
45112
  actions.addLog("error", `Failed to send save command: ${error2}`);
43406
45113
  }
43407
45114
  }, [rcon, actions]);
43408
- useEffect6(() => {
45115
+ const detach = useCallback3(async () => {
45116
+ if (!hasActiveServer()) {
45117
+ actions.addLog("warn", "No server to detach from");
45118
+ return;
45119
+ }
45120
+ try {
45121
+ await detachFromServer();
45122
+ actions.addLog("info", "Detached from server - it will continue running");
45123
+ } catch (error2) {
45124
+ actions.addLog("error", `Failed to detach: ${error2}`);
45125
+ }
45126
+ }, [actions]);
45127
+ useEffect8(() => {
43409
45128
  if (status !== "online") return;
43410
45129
  const interval = setInterval(() => {
43411
45130
  actions.incrementUptime();
43412
45131
  }, 1e3);
43413
45132
  return () => clearInterval(interval);
43414
45133
  }, [status, actions]);
45134
+ useEffect8(() => {
45135
+ if (!rcon.enabled) {
45136
+ if (rconManager.isConnected()) {
45137
+ rconManager.disconnect();
45138
+ actions.setRconConnected(false);
45139
+ }
45140
+ return;
45141
+ }
45142
+ rconManager.initialize(
45143
+ {
45144
+ host: "localhost",
45145
+ port: rcon.port,
45146
+ password: rcon.password,
45147
+ timeout: rcon.timeout,
45148
+ enabled: rcon.enabled,
45149
+ autoReconnect: rcon.autoReconnect
45150
+ },
45151
+ {
45152
+ onConnectionStateChange: (state) => {
45153
+ const connected = state === "connected";
45154
+ actions.setRconConnected(connected);
45155
+ if (connected) {
45156
+ actions.addLog("info", "RCON connected");
45157
+ } else if (state === "error") {
45158
+ actions.addLog("warn", "RCON connection error");
45159
+ } else if (state === "disconnected") {
45160
+ actions.addLog("info", "RCON disconnected");
45161
+ }
45162
+ },
45163
+ onPlayerListUpdate: (players) => {
45164
+ actions.setPlayers(players);
45165
+ },
45166
+ pollInterval: 1e4
45167
+ // Poll every 10 seconds
45168
+ }
45169
+ );
45170
+ return () => {
45171
+ rconManager.disconnect();
45172
+ };
45173
+ }, [rcon, actions]);
45174
+ useEffect8(() => {
45175
+ if (status === "online" && rcon.enabled && !rconManager.isConnected()) {
45176
+ const timer = setTimeout(() => {
45177
+ rconManager.connect().catch((error2) => {
45178
+ actions.addLog("warn", `RCON connection failed: ${error2}`);
45179
+ });
45180
+ }, 3e3);
45181
+ return () => clearTimeout(timer);
45182
+ }
45183
+ if (status === "offline" && rconManager.isConnected()) {
45184
+ rconManager.disconnect();
45185
+ }
45186
+ }, [status, rcon.enabled, actions]);
43415
45187
  return {
43416
45188
  status,
43417
45189
  start,
@@ -43420,16 +45192,20 @@ function useServer() {
43420
45192
  restart,
43421
45193
  update,
43422
45194
  forceSave,
45195
+ detach,
45196
+ checkAndAttach,
43423
45197
  isOnline: status === "online",
43424
45198
  isOffline: status === "offline",
43425
45199
  isTransitioning: status === "starting" || status === "stopping",
43426
45200
  isUpdating: isUpdating(),
45201
+ isAttached: isAttachedToServer(),
45202
+ logFilePath: getLogFilePath(),
43427
45203
  watchdog: getWatchdog()
43428
45204
  };
43429
45205
  }
43430
45206
 
43431
45207
  // src/tui/screens/Dashboard.tsx
43432
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
45208
+ import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
43433
45209
  function formatUptime2(seconds) {
43434
45210
  if (seconds === 0) return "N/A";
43435
45211
  const hours = Math.floor(seconds / 3600);
@@ -43491,6 +45267,8 @@ var Dashboard = () => {
43491
45267
  const memoryUsage = useStore((s) => s.server.memoryUsage);
43492
45268
  const startupPhase = useStore((s) => s.server.startupPhase);
43493
45269
  const config = useStore((s) => s.config);
45270
+ const rconConfig = useStore((s) => s.rcon);
45271
+ const rconConnected = useStore((s) => s.rcon.connected);
43494
45272
  const modalOpen = useStore((s) => s.ui.modalOpen);
43495
45273
  const openModal = useStore((s) => s.actions.openModal);
43496
45274
  const closeModal = useStore((s) => s.actions.closeModal);
@@ -43526,12 +45304,12 @@ var Dashboard = () => {
43526
45304
  const setValheimBuildId = useStore((s) => s.actions.setValheimBuildId);
43527
45305
  const resetValheimInstall = useStore((s) => s.actions.resetValheimInstall);
43528
45306
  const { start, stop, restart, update, forceSave } = useServer();
43529
- const [isUpdating2, setIsUpdating] = useState6(false);
43530
- const [updateProgress, setUpdateProgress] = useState6("");
43531
- const [startupTaskRegistered, setStartupTaskRegistered] = useState6(null);
43532
- const [startupTaskProcessing, setStartupTaskProcessing] = useState6(false);
45307
+ const [isUpdating2, setIsUpdating] = useState11(false);
45308
+ const [updateProgress, setUpdateProgress] = useState11("");
45309
+ const [startupTaskRegistered, setStartupTaskRegistered] = useState11(null);
45310
+ const [startupTaskProcessing, setStartupTaskProcessing] = useState11(false);
43533
45311
  const statusColor = getStatusColor(status);
43534
- useEffect7(() => {
45312
+ useEffect9(() => {
43535
45313
  const checkSteamCmd2 = async () => {
43536
45314
  try {
43537
45315
  const installed = await isSteamCmdInstalled();
@@ -43546,7 +45324,7 @@ var Dashboard = () => {
43546
45324
  };
43547
45325
  checkSteamCmd2();
43548
45326
  }, [setSteamCmdInstalled, setSteamCmdPath]);
43549
- useEffect7(() => {
45327
+ useEffect9(() => {
43550
45328
  if (steamCmdInstalled !== true) return;
43551
45329
  const checkValheim = async () => {
43552
45330
  try {
@@ -43577,7 +45355,7 @@ var Dashboard = () => {
43577
45355
  setValheimBuildId,
43578
45356
  addLog
43579
45357
  ]);
43580
- useEffect7(() => {
45358
+ useEffect9(() => {
43581
45359
  if (steamCmdInstalled === true && valheimInstalled === false && !valheimInstalling) {
43582
45360
  const autoInstallValheim = async () => {
43583
45361
  setValheimInstalling(true);
@@ -43636,7 +45414,7 @@ var Dashboard = () => {
43636
45414
  resetValheimInstall,
43637
45415
  addLog
43638
45416
  ]);
43639
- useEffect7(() => {
45417
+ useEffect9(() => {
43640
45418
  const checkStartupTask = async () => {
43641
45419
  try {
43642
45420
  const registered = await isStartupTaskRegistered();
@@ -43778,7 +45556,7 @@ var Dashboard = () => {
43778
45556
  setStartupTaskProcessing(false);
43779
45557
  }
43780
45558
  };
43781
- useInput4((input) => {
45559
+ useInput9((input) => {
43782
45560
  if (modalOpen || isUpdating2 || steamCmdInstalling || valheimInstalling || startupTaskProcessing)
43783
45561
  return;
43784
45562
  if (input === "s" || input === "S") {
@@ -43790,7 +45568,7 @@ var Dashboard = () => {
43790
45568
  if (input === "x" || input === "X") {
43791
45569
  if (status === "online") {
43792
45570
  openModal(
43793
- /* @__PURE__ */ jsx11(
45571
+ /* @__PURE__ */ jsx16(
43794
45572
  ConfirmModal,
43795
45573
  {
43796
45574
  message: "Stop the server? Players will be disconnected.",
@@ -43804,7 +45582,7 @@ var Dashboard = () => {
43804
45582
  if (input === "r" || input === "R") {
43805
45583
  if (status === "online") {
43806
45584
  openModal(
43807
- /* @__PURE__ */ jsx11(
45585
+ /* @__PURE__ */ jsx16(
43808
45586
  ConfirmModal,
43809
45587
  {
43810
45588
  message: "Restart the server? Players will be briefly disconnected.",
@@ -43818,7 +45596,7 @@ var Dashboard = () => {
43818
45596
  if (input === "u" || input === "U") {
43819
45597
  if (status === "offline" && steamCmdInstalled && valheimInstalled) {
43820
45598
  openModal(
43821
- /* @__PURE__ */ jsx11(
45599
+ /* @__PURE__ */ jsx16(
43822
45600
  ConfirmModal,
43823
45601
  {
43824
45602
  message: "Update server via SteamCMD? This may take a few minutes.",
@@ -43832,7 +45610,7 @@ var Dashboard = () => {
43832
45610
  if (input === "i" || input === "I") {
43833
45611
  if (!steamCmdInstalled && !steamCmdInstalling) {
43834
45612
  openModal(
43835
- /* @__PURE__ */ jsx11(
45613
+ /* @__PURE__ */ jsx16(
43836
45614
  ConfirmModal,
43837
45615
  {
43838
45616
  message: "Install SteamCMD? This is required to download and update Valheim.",
@@ -43847,7 +45625,7 @@ var Dashboard = () => {
43847
45625
  if (steamCmdInstalled && !valheimInstalling) {
43848
45626
  const action = valheimInstalled === false ? "Install" : "Reinstall/Verify";
43849
45627
  openModal(
43850
- /* @__PURE__ */ jsx11(
45628
+ /* @__PURE__ */ jsx16(
43851
45629
  ConfirmModal,
43852
45630
  {
43853
45631
  message: `${action} Valheim Dedicated Server? This may take several minutes.`,
@@ -43866,7 +45644,7 @@ var Dashboard = () => {
43866
45644
  if (input === "k" || input === "K") {
43867
45645
  if (status === "online" || status === "starting" || status === "stopping") {
43868
45646
  openModal(
43869
- /* @__PURE__ */ jsx11(
45647
+ /* @__PURE__ */ jsx16(
43870
45648
  ConfirmModal,
43871
45649
  {
43872
45650
  message: "Force kill the server? Data may be lost!",
@@ -43881,7 +45659,7 @@ var Dashboard = () => {
43881
45659
  if (startupTaskRegistered === null) return;
43882
45660
  const action = startupTaskRegistered ? "Remove" : "Enable";
43883
45661
  openModal(
43884
- /* @__PURE__ */ jsx11(
45662
+ /* @__PURE__ */ jsx16(
43885
45663
  ConfirmModal,
43886
45664
  {
43887
45665
  message: `${action} auto-start at login? The server manager will ${startupTaskRegistered ? "no longer" : ""} start automatically when you log in.`,
@@ -43891,256 +45669,336 @@ var Dashboard = () => {
43891
45669
  )
43892
45670
  );
43893
45671
  }
45672
+ if (rconConnected && status === "online") {
45673
+ if (input === "p" || input === "P") {
45674
+ openModal(/* @__PURE__ */ jsx16(PlayerManager, { onClose: closeModal }));
45675
+ }
45676
+ if (input === "n" || input === "N") {
45677
+ openModal(/* @__PURE__ */ jsx16(ServerInfoModal, { onClose: closeModal }));
45678
+ }
45679
+ if (input === "e" || input === "E") {
45680
+ openModal(/* @__PURE__ */ jsx16(EventManager, { onClose: closeModal }));
45681
+ }
45682
+ if (input === "t" || input === "T") {
45683
+ openModal(/* @__PURE__ */ jsx16(TimeControl, { onClose: closeModal }));
45684
+ }
45685
+ if (input === "g" || input === "G") {
45686
+ openModal(/* @__PURE__ */ jsx16(GlobalKeysManager, { onClose: closeModal }));
45687
+ }
45688
+ if (input === "d" || input === "D") {
45689
+ openModal(
45690
+ /* @__PURE__ */ jsx16(
45691
+ ConfirmModal,
45692
+ {
45693
+ message: "Remove all dropped items from the world? This cannot be undone.",
45694
+ onConfirm: async () => {
45695
+ closeModal();
45696
+ const { rconManager: rconManager2 } = await Promise.resolve().then(() => (init_mod(), mod_exports));
45697
+ const response = await rconManager2.removeDrops();
45698
+ addLog("info", `Remove drops: ${response || "Done"}`);
45699
+ },
45700
+ onCancel: closeModal
45701
+ }
45702
+ )
45703
+ );
45704
+ }
45705
+ }
43894
45706
  });
43895
45707
  const renderStatusWithLoading = () => {
43896
45708
  if (status === "starting") {
43897
45709
  const phaseDesc = getPhaseDescription(startupPhase);
43898
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
43899
- /* @__PURE__ */ jsxs10(Box11, { children: [
43900
- /* @__PURE__ */ jsx11(Spinner, {}),
43901
- /* @__PURE__ */ jsxs10(Text11, { color: statusColor, bold: true, children: [
45710
+ return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", children: [
45711
+ /* @__PURE__ */ jsxs15(Box16, { children: [
45712
+ /* @__PURE__ */ jsx16(Spinner, {}),
45713
+ /* @__PURE__ */ jsxs15(Text16, { color: statusColor, bold: true, children: [
43902
45714
  " ",
43903
45715
  "STARTING"
43904
45716
  ] })
43905
45717
  ] }),
43906
- phaseDesc && /* @__PURE__ */ jsx11(Box11, { marginLeft: 2, children: /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: phaseDesc }) })
45718
+ phaseDesc && /* @__PURE__ */ jsx16(Box16, { marginLeft: 2, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: phaseDesc }) })
43907
45719
  ] });
43908
45720
  }
43909
45721
  if (status === "stopping") {
43910
- return /* @__PURE__ */ jsxs10(Box11, { children: [
43911
- /* @__PURE__ */ jsx11(Spinner, {}),
43912
- /* @__PURE__ */ jsxs10(Text11, { color: statusColor, bold: true, children: [
45722
+ return /* @__PURE__ */ jsxs15(Box16, { children: [
45723
+ /* @__PURE__ */ jsx16(Spinner, {}),
45724
+ /* @__PURE__ */ jsxs15(Text16, { color: statusColor, bold: true, children: [
43913
45725
  " ",
43914
45726
  "STOPPING"
43915
45727
  ] })
43916
45728
  ] });
43917
45729
  }
43918
45730
  if (status === "online" && startupPhase === "ready") {
43919
- return /* @__PURE__ */ jsx11(Text11, { color: statusColor, bold: true, children: "\u25CF ONLINE (Ready to join)" });
45731
+ return /* @__PURE__ */ jsx16(Text16, { color: statusColor, bold: true, children: "\u25CF ONLINE (Ready to join)" });
43920
45732
  }
43921
- return /* @__PURE__ */ jsxs10(Text11, { color: statusColor, bold: true, children: [
45733
+ return /* @__PURE__ */ jsxs15(Text16, { color: statusColor, bold: true, children: [
43922
45734
  "\u25CF ",
43923
45735
  status.toUpperCase()
43924
45736
  ] });
43925
45737
  };
43926
45738
  if (isUpdating2) {
43927
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
43928
- /* @__PURE__ */ jsx11(Box11, { marginBottom: 1, children: /* @__PURE__ */ jsx11(Text11, { bold: true, color: theme.primary, children: "\u2500 Dashboard \u2500" }) }),
43929
- /* @__PURE__ */ jsxs10(
43930
- Box11,
45739
+ return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
45740
+ /* @__PURE__ */ jsx16(Box16, { marginBottom: 1, children: /* @__PURE__ */ jsx16(Text16, { bold: true, color: theme.primary, children: "\u2500 Dashboard \u2500" }) }),
45741
+ /* @__PURE__ */ jsxs15(
45742
+ Box16,
43931
45743
  {
43932
45744
  flexDirection: "column",
43933
45745
  alignItems: "center",
43934
45746
  justifyContent: "center",
43935
45747
  flexGrow: 1,
43936
45748
  children: [
43937
- /* @__PURE__ */ jsx11(Spinner, { label: "Updating server..." }),
43938
- updateProgress && /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: updateProgress }) }),
43939
- /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "Please wait, this may take several minutes..." }) })
45749
+ /* @__PURE__ */ jsx16(Spinner, { label: "Updating server..." }),
45750
+ updateProgress && /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: updateProgress }) }),
45751
+ /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "Please wait, this may take several minutes..." }) })
43940
45752
  ]
43941
45753
  }
43942
45754
  )
43943
45755
  ] });
43944
45756
  }
43945
45757
  if (steamCmdInstalling) {
43946
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
43947
- /* @__PURE__ */ jsx11(Box11, { marginBottom: 1, children: /* @__PURE__ */ jsx11(Text11, { bold: true, color: theme.primary, children: "\u2500 Dashboard \u2500" }) }),
43948
- /* @__PURE__ */ jsxs10(
43949
- Box11,
45758
+ return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
45759
+ /* @__PURE__ */ jsx16(Box16, { marginBottom: 1, children: /* @__PURE__ */ jsx16(Text16, { bold: true, color: theme.primary, children: "\u2500 Dashboard \u2500" }) }),
45760
+ /* @__PURE__ */ jsxs15(
45761
+ Box16,
43950
45762
  {
43951
45763
  flexDirection: "column",
43952
45764
  alignItems: "center",
43953
45765
  justifyContent: "center",
43954
45766
  flexGrow: 1,
43955
45767
  children: [
43956
- /* @__PURE__ */ jsx11(Spinner, { label: "Installing SteamCMD..." }),
43957
- steamCmdProgress && /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
45768
+ /* @__PURE__ */ jsx16(Spinner, { label: "Installing SteamCMD..." }),
45769
+ steamCmdProgress && /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsxs15(Text16, { dimColor: true, children: [
43958
45770
  steamCmdProgress,
43959
45771
  steamCmdPercent > 0 && ` (${steamCmdPercent}%)`
43960
45772
  ] }) }),
43961
- /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "Please wait..." }) })
45773
+ /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "Please wait..." }) })
43962
45774
  ]
43963
45775
  }
43964
45776
  )
43965
45777
  ] });
43966
45778
  }
43967
45779
  if (valheimInstalling) {
43968
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
43969
- /* @__PURE__ */ jsx11(Box11, { marginBottom: 1, children: /* @__PURE__ */ jsx11(Text11, { bold: true, color: theme.primary, children: "\u2500 Dashboard \u2500" }) }),
43970
- /* @__PURE__ */ jsxs10(
43971
- Box11,
45780
+ return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
45781
+ /* @__PURE__ */ jsx16(Box16, { marginBottom: 1, children: /* @__PURE__ */ jsx16(Text16, { bold: true, color: theme.primary, children: "\u2500 Dashboard \u2500" }) }),
45782
+ /* @__PURE__ */ jsxs15(
45783
+ Box16,
43972
45784
  {
43973
45785
  flexDirection: "column",
43974
45786
  alignItems: "center",
43975
45787
  justifyContent: "center",
43976
45788
  flexGrow: 1,
43977
45789
  children: [
43978
- /* @__PURE__ */ jsx11(Spinner, { label: "Installing Valheim Dedicated Server..." }),
43979
- valheimProgress && /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
45790
+ /* @__PURE__ */ jsx16(Spinner, { label: "Installing Valheim Dedicated Server..." }),
45791
+ valheimProgress && /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsxs15(Text16, { dimColor: true, children: [
43980
45792
  valheimProgress,
43981
45793
  valheimPercent > 0 && ` (${valheimPercent}%)`
43982
45794
  ] }) }),
43983
- /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "Please wait, this may take several minutes..." }) })
45795
+ /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "Please wait, this may take several minutes..." }) })
43984
45796
  ]
43985
45797
  }
43986
45798
  )
43987
45799
  ] });
43988
45800
  }
43989
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
43990
- /* @__PURE__ */ jsx11(Box11, { marginBottom: 1, children: /* @__PURE__ */ jsx11(Text11, { bold: true, color: theme.primary, children: "\u2500 Dashboard \u2500" }) }),
43991
- /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginBottom: 1, children: [
43992
- /* @__PURE__ */ jsx11(Text11, { bold: true, children: "SteamCMD" }),
43993
- /* @__PURE__ */ jsxs10(Box11, { marginLeft: 2, flexDirection: "column", children: [
43994
- /* @__PURE__ */ jsxs10(Box11, { flexShrink: 0, children: [
43995
- /* @__PURE__ */ jsx11(Text11, { children: "Status: " }),
43996
- steamCmdInstalled === null ? /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "Checking..." }) : steamCmdInstalled ? /* @__PURE__ */ jsx11(Text11, { color: theme.success, children: "\u25CF Installed" }) : /* @__PURE__ */ jsx11(Text11, { color: theme.warning, children: "\u25CB Not Installed" })
45801
+ return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
45802
+ /* @__PURE__ */ jsx16(Box16, { marginBottom: 1, children: /* @__PURE__ */ jsx16(Text16, { bold: true, color: theme.primary, children: "\u2500 Dashboard \u2500" }) }),
45803
+ /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginBottom: 1, children: [
45804
+ /* @__PURE__ */ jsx16(Text16, { bold: true, children: "SteamCMD" }),
45805
+ /* @__PURE__ */ jsxs15(Box16, { marginLeft: 2, flexDirection: "column", children: [
45806
+ /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45807
+ /* @__PURE__ */ jsx16(Text16, { children: "Status: " }),
45808
+ steamCmdInstalled === null ? /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "Checking..." }) : steamCmdInstalled ? /* @__PURE__ */ jsx16(Text16, { color: theme.success, children: "\u25CF Installed" }) : /* @__PURE__ */ jsx16(Text16, { color: theme.warning, children: "\u25CB Not Installed" })
43997
45809
  ] }),
43998
- steamCmdInstalled && steamCmdPath && /* @__PURE__ */ jsxs10(Box11, { flexShrink: 0, children: [
43999
- /* @__PURE__ */ jsx11(Text11, { children: "Location: " }),
44000
- /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: steamCmdPath })
45810
+ steamCmdInstalled && steamCmdPath && /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45811
+ /* @__PURE__ */ jsx16(Text16, { children: "Location: " }),
45812
+ /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: steamCmdPath })
44001
45813
  ] })
44002
45814
  ] })
44003
45815
  ] }),
44004
- /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginBottom: 1, children: [
44005
- /* @__PURE__ */ jsx11(Text11, { bold: true, children: "Valheim Dedicated Server" }),
44006
- /* @__PURE__ */ jsxs10(Box11, { marginLeft: 2, flexDirection: "column", children: [
44007
- /* @__PURE__ */ jsxs10(Box11, { flexShrink: 0, children: [
44008
- /* @__PURE__ */ jsx11(Text11, { children: "Status: " }),
44009
- steamCmdInstalled !== true ? /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "Waiting for SteamCMD..." }) : valheimInstalled === null ? /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "Checking..." }) : valheimInstalled ? /* @__PURE__ */ jsx11(Text11, { color: theme.success, children: "\u25CF Installed" }) : /* @__PURE__ */ jsx11(Text11, { color: theme.warning, children: "\u25CB Not Installed" })
45816
+ /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginBottom: 1, children: [
45817
+ /* @__PURE__ */ jsx16(Text16, { bold: true, children: "Valheim Dedicated Server" }),
45818
+ /* @__PURE__ */ jsxs15(Box16, { marginLeft: 2, flexDirection: "column", children: [
45819
+ /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45820
+ /* @__PURE__ */ jsx16(Text16, { children: "Status: " }),
45821
+ steamCmdInstalled !== true ? /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "Waiting for SteamCMD..." }) : valheimInstalled === null ? /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "Checking..." }) : valheimInstalled ? /* @__PURE__ */ jsx16(Text16, { color: theme.success, children: "\u25CF Installed" }) : /* @__PURE__ */ jsx16(Text16, { color: theme.warning, children: "\u25CB Not Installed" })
44010
45822
  ] }),
44011
- valheimInstalled && valheimVerified !== null && /* @__PURE__ */ jsxs10(Box11, { flexShrink: 0, children: [
44012
- /* @__PURE__ */ jsx11(Text11, { children: "Verified: " }),
44013
- valheimVerified ? /* @__PURE__ */ jsx11(Text11, { color: theme.success, children: "\u25CF Yes" }) : /* @__PURE__ */ jsx11(Text11, { color: theme.error, children: "\u25CB Files Missing" })
45823
+ valheimInstalled && valheimVerified !== null && /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45824
+ /* @__PURE__ */ jsx16(Text16, { children: "Verified: " }),
45825
+ valheimVerified ? /* @__PURE__ */ jsx16(Text16, { color: theme.success, children: "\u25CF Yes" }) : /* @__PURE__ */ jsx16(Text16, { color: theme.error, children: "\u25CB Files Missing" })
44014
45826
  ] }),
44015
- valheimInstalled && valheimBuildId && /* @__PURE__ */ jsxs10(Box11, { flexShrink: 0, children: [
44016
- /* @__PURE__ */ jsx11(Text11, { children: "Build ID: " }),
44017
- /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: valheimBuildId })
45827
+ valheimInstalled && valheimBuildId && /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45828
+ /* @__PURE__ */ jsx16(Text16, { children: "Build ID: " }),
45829
+ /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: valheimBuildId })
44018
45830
  ] }),
44019
- valheimInstalled && valheimPath && /* @__PURE__ */ jsxs10(Box11, { flexShrink: 0, children: [
44020
- /* @__PURE__ */ jsx11(Text11, { children: "Location: " }),
44021
- /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: valheimPath })
45831
+ valheimInstalled && valheimPath && /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45832
+ /* @__PURE__ */ jsx16(Text16, { children: "Location: " }),
45833
+ /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: valheimPath })
44022
45834
  ] })
44023
45835
  ] })
44024
45836
  ] }),
44025
- /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginBottom: 1, children: [
44026
- /* @__PURE__ */ jsx11(Text11, { bold: true, children: "Server Status" }),
44027
- /* @__PURE__ */ jsxs10(Box11, { marginLeft: 2, flexDirection: "column", children: [
44028
- /* @__PURE__ */ jsxs10(Box11, { flexShrink: 0, children: [
44029
- /* @__PURE__ */ jsx11(Text11, { children: "Status: " }),
45837
+ /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginBottom: 1, children: [
45838
+ /* @__PURE__ */ jsx16(Text16, { bold: true, children: "Server Status" }),
45839
+ /* @__PURE__ */ jsxs15(Box16, { marginLeft: 2, flexDirection: "column", children: [
45840
+ /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45841
+ /* @__PURE__ */ jsx16(Text16, { children: "Status: " }),
44030
45842
  renderStatusWithLoading()
44031
45843
  ] }),
44032
- /* @__PURE__ */ jsxs10(Box11, { flexShrink: 0, children: [
44033
- /* @__PURE__ */ jsx11(Text11, { children: "Server Name: " }),
44034
- /* @__PURE__ */ jsx11(Text11, { color: theme.primary, children: config.serverName })
45844
+ /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45845
+ /* @__PURE__ */ jsx16(Text16, { children: "Server Name: " }),
45846
+ /* @__PURE__ */ jsx16(Text16, { color: theme.primary, children: config.serverName })
45847
+ ] }),
45848
+ /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45849
+ /* @__PURE__ */ jsx16(Text16, { children: "World: " }),
45850
+ /* @__PURE__ */ jsx16(Text16, { color: theme.primary, children: config.world })
44035
45851
  ] }),
44036
- /* @__PURE__ */ jsxs10(Box11, { flexShrink: 0, children: [
44037
- /* @__PURE__ */ jsx11(Text11, { children: "World: " }),
44038
- /* @__PURE__ */ jsx11(Text11, { color: theme.primary, children: config.world })
45852
+ /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45853
+ /* @__PURE__ */ jsx16(Text16, { children: "Port: " }),
45854
+ /* @__PURE__ */ jsx16(Text16, { children: config.port })
44039
45855
  ] }),
44040
- /* @__PURE__ */ jsxs10(Box11, { flexShrink: 0, children: [
44041
- /* @__PURE__ */ jsx11(Text11, { children: "Port: " }),
44042
- /* @__PURE__ */ jsx11(Text11, { children: config.port })
45856
+ version && /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45857
+ /* @__PURE__ */ jsx16(Text16, { children: "Version: " }),
45858
+ /* @__PURE__ */ jsx16(Text16, { children: version }),
45859
+ updateAvailable && /* @__PURE__ */ jsx16(Text16, { color: theme.warning, children: " (Update available!)" })
44043
45860
  ] }),
44044
- version && /* @__PURE__ */ jsxs10(Box11, { flexShrink: 0, children: [
44045
- /* @__PURE__ */ jsx11(Text11, { children: "Version: " }),
44046
- /* @__PURE__ */ jsx11(Text11, { children: version }),
44047
- updateAvailable && /* @__PURE__ */ jsx11(Text11, { color: theme.warning, children: " (Update available!)" })
45861
+ pid && /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45862
+ /* @__PURE__ */ jsx16(Text16, { children: "PID: " }),
45863
+ /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: pid })
44048
45864
  ] }),
44049
- pid && /* @__PURE__ */ jsxs10(Box11, { flexShrink: 0, children: [
44050
- /* @__PURE__ */ jsx11(Text11, { children: "PID: " }),
44051
- /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: pid })
45865
+ /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45866
+ /* @__PURE__ */ jsx16(Text16, { children: "Uptime: " }),
45867
+ /* @__PURE__ */ jsx16(Text16, { children: formatUptime2(uptime) })
44052
45868
  ] }),
44053
- /* @__PURE__ */ jsxs10(Box11, { flexShrink: 0, children: [
44054
- /* @__PURE__ */ jsx11(Text11, { children: "Uptime: " }),
44055
- /* @__PURE__ */ jsx11(Text11, { children: formatUptime2(uptime) })
45869
+ /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45870
+ /* @__PURE__ */ jsx16(Text16, { children: "Last Save: " }),
45871
+ /* @__PURE__ */ jsx16(Text16, { children: formatLastSave(lastSave) })
44056
45872
  ] }),
44057
- /* @__PURE__ */ jsxs10(Box11, { flexShrink: 0, children: [
44058
- /* @__PURE__ */ jsx11(Text11, { children: "Last Save: " }),
44059
- /* @__PURE__ */ jsx11(Text11, { children: formatLastSave(lastSave) })
45873
+ memoryUsage !== null && memoryUsage > 0 && /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45874
+ /* @__PURE__ */ jsx16(Text16, { children: "Memory: " }),
45875
+ /* @__PURE__ */ jsx16(Text16, { children: formatBytes(memoryUsage) })
45876
+ ] })
45877
+ ] })
45878
+ ] }),
45879
+ /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginBottom: 1, children: [
45880
+ /* @__PURE__ */ jsx16(Text16, { bold: true, children: "RCON Status" }),
45881
+ /* @__PURE__ */ jsxs15(Box16, { marginLeft: 2, flexDirection: "column", children: [
45882
+ /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45883
+ /* @__PURE__ */ jsx16(Text16, { children: "RCON: " }),
45884
+ rconConfig.enabled ? rconConnected ? /* @__PURE__ */ jsx16(Text16, { color: theme.success, children: "\u25CF Connected" }) : status === "online" ? /* @__PURE__ */ jsx16(Text16, { color: theme.warning, children: "\u25CB Connecting..." }) : /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "\u25CB Waiting for server..." }) : /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "\u25CB Disabled" })
44060
45885
  ] }),
44061
- memoryUsage !== null && memoryUsage > 0 && /* @__PURE__ */ jsxs10(Box11, { flexShrink: 0, children: [
44062
- /* @__PURE__ */ jsx11(Text11, { children: "Memory: " }),
44063
- /* @__PURE__ */ jsx11(Text11, { children: formatBytes(memoryUsage) })
45886
+ rconConfig.enabled && /* @__PURE__ */ jsxs15(Fragment4, { children: [
45887
+ /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45888
+ /* @__PURE__ */ jsx16(Text16, { children: "Port: " }),
45889
+ /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: rconConfig.port })
45890
+ ] }),
45891
+ /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45892
+ /* @__PURE__ */ jsx16(Text16, { children: "Auto-reconnect: " }),
45893
+ /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: rconConfig.autoReconnect ? "Enabled" : "Disabled" })
45894
+ ] })
44064
45895
  ] })
44065
45896
  ] })
44066
45897
  ] }),
44067
- /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginBottom: 1, children: [
44068
- /* @__PURE__ */ jsxs10(Text11, { bold: true, children: [
45898
+ /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginBottom: 1, children: [
45899
+ /* @__PURE__ */ jsxs15(Text16, { bold: true, children: [
44069
45900
  "Players (",
44070
45901
  players.length,
44071
45902
  ")"
44072
45903
  ] }),
44073
- /* @__PURE__ */ jsx11(Box11, { marginLeft: 2, flexDirection: "column", children: players.length === 0 ? /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "No players connected" }) : players.map((player) => /* @__PURE__ */ jsx11(Box11, { flexShrink: 0, children: /* @__PURE__ */ jsxs10(Text11, { color: theme.primary, children: [
45904
+ /* @__PURE__ */ jsx16(Box16, { marginLeft: 2, flexDirection: "column", children: players.length === 0 ? /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "No players connected" }) : players.map((player) => /* @__PURE__ */ jsx16(Box16, { flexShrink: 0, children: /* @__PURE__ */ jsxs15(Text16, { color: theme.primary, children: [
44074
45905
  "\u2022 ",
44075
45906
  player
44076
45907
  ] }) }, player)) })
44077
45908
  ] }),
44078
- /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginBottom: 1, children: [
44079
- /* @__PURE__ */ jsx11(Text11, { bold: true, children: "Auto-start" }),
44080
- /* @__PURE__ */ jsxs10(Box11, { marginLeft: 2, flexShrink: 0, children: [
44081
- /* @__PURE__ */ jsx11(Text11, { children: "Status: " }),
44082
- startupTaskRegistered === null ? /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "Checking..." }) : startupTaskRegistered ? /* @__PURE__ */ jsx11(Text11, { color: theme.success, children: "\u25CF Enabled" }) : /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "\u25CB Disabled" })
45909
+ /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginBottom: 1, children: [
45910
+ /* @__PURE__ */ jsx16(Text16, { bold: true, children: "Auto-start" }),
45911
+ /* @__PURE__ */ jsxs15(Box16, { marginLeft: 2, flexShrink: 0, children: [
45912
+ /* @__PURE__ */ jsx16(Text16, { children: "Status: " }),
45913
+ startupTaskRegistered === null ? /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "Checking..." }) : startupTaskRegistered ? /* @__PURE__ */ jsx16(Text16, { color: theme.success, children: "\u25CF Enabled" }) : /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "\u25CB Disabled" })
44083
45914
  ] })
44084
45915
  ] }),
44085
- /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
44086
- /* @__PURE__ */ jsx11(Text11, { bold: true, children: "Quick Actions" }),
44087
- /* @__PURE__ */ jsxs10(Box11, { marginLeft: 2, marginTop: 1, flexDirection: "column", children: [
44088
- steamCmdInstalled === false && /* @__PURE__ */ jsx11(Box11, { marginBottom: 1, flexShrink: 0, children: /* @__PURE__ */ jsxs10(Box11, { flexShrink: 0, children: [
44089
- /* @__PURE__ */ jsx11(Text11, { color: theme.info, children: "[I] " }),
44090
- /* @__PURE__ */ jsx11(Text11, { children: "Install SteamCMD" }),
44091
- /* @__PURE__ */ jsx11(Text11, { color: theme.warning, children: " (required)" })
45916
+ /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", children: [
45917
+ /* @__PURE__ */ jsx16(Text16, { bold: true, children: "Quick Actions" }),
45918
+ /* @__PURE__ */ jsxs15(Box16, { marginLeft: 2, marginTop: 1, flexDirection: "column", children: [
45919
+ steamCmdInstalled === false && /* @__PURE__ */ jsx16(Box16, { marginBottom: 1, flexShrink: 0, children: /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45920
+ /* @__PURE__ */ jsx16(Text16, { color: theme.info, children: "[I] " }),
45921
+ /* @__PURE__ */ jsx16(Text16, { children: "Install SteamCMD" }),
45922
+ /* @__PURE__ */ jsx16(Text16, { color: theme.warning, children: " (required)" })
44092
45923
  ] }) }),
44093
- steamCmdInstalled && valheimInstalled === false && !valheimInstalling && /* @__PURE__ */ jsx11(Box11, { marginBottom: 1, flexShrink: 0, children: /* @__PURE__ */ jsxs10(Box11, { flexShrink: 0, children: [
44094
- /* @__PURE__ */ jsx11(Text11, { color: theme.info, children: "[V] " }),
44095
- /* @__PURE__ */ jsx11(Text11, { children: "Install Valheim Server" }),
44096
- /* @__PURE__ */ jsx11(Text11, { color: theme.warning, children: " (required)" })
45924
+ steamCmdInstalled && valheimInstalled === false && !valheimInstalling && /* @__PURE__ */ jsx16(Box16, { marginBottom: 1, flexShrink: 0, children: /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45925
+ /* @__PURE__ */ jsx16(Text16, { color: theme.info, children: "[V] " }),
45926
+ /* @__PURE__ */ jsx16(Text16, { children: "Install Valheim Server" }),
45927
+ /* @__PURE__ */ jsx16(Text16, { color: theme.warning, children: " (required)" })
44097
45928
  ] }) }),
44098
- steamCmdInstalled && valheimInstalled && valheimVerified === false && /* @__PURE__ */ jsx11(Box11, { marginBottom: 1, flexShrink: 0, children: /* @__PURE__ */ jsxs10(Box11, { flexShrink: 0, children: [
44099
- /* @__PURE__ */ jsx11(Text11, { color: theme.warning, children: "[V] " }),
44100
- /* @__PURE__ */ jsx11(Text11, { children: "Reinstall Valheim Server" }),
44101
- /* @__PURE__ */ jsx11(Text11, { color: theme.error, children: " (verification failed)" })
45929
+ steamCmdInstalled && valheimInstalled && valheimVerified === false && /* @__PURE__ */ jsx16(Box16, { marginBottom: 1, flexShrink: 0, children: /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45930
+ /* @__PURE__ */ jsx16(Text16, { color: theme.warning, children: "[V] " }),
45931
+ /* @__PURE__ */ jsx16(Text16, { children: "Reinstall Valheim Server" }),
45932
+ /* @__PURE__ */ jsx16(Text16, { color: theme.error, children: " (verification failed)" })
44102
45933
  ] }) }),
44103
- steamCmdInstalled && valheimInstalled ? status === "offline" ? /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
44104
- /* @__PURE__ */ jsxs10(Box11, { flexShrink: 0, children: [
44105
- /* @__PURE__ */ jsx11(Text11, { color: theme.success, children: "[S] " }),
44106
- /* @__PURE__ */ jsx11(Text11, { children: "Start Server" })
45934
+ steamCmdInstalled && valheimInstalled ? status === "offline" ? /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", children: [
45935
+ /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45936
+ /* @__PURE__ */ jsx16(Text16, { color: theme.success, children: "[S] " }),
45937
+ /* @__PURE__ */ jsx16(Text16, { children: "Start Server" })
44107
45938
  ] }),
44108
- /* @__PURE__ */ jsxs10(Box11, { flexShrink: 0, children: [
44109
- /* @__PURE__ */ jsx11(Text11, { color: theme.info, children: "[U] " }),
44110
- /* @__PURE__ */ jsx11(Text11, { children: "Update Server" }),
44111
- updateAvailable && /* @__PURE__ */ jsx11(Text11, { color: theme.warning, children: " \u2605" })
45939
+ /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45940
+ /* @__PURE__ */ jsx16(Text16, { color: theme.info, children: "[U] " }),
45941
+ /* @__PURE__ */ jsx16(Text16, { children: "Update Server" }),
45942
+ updateAvailable && /* @__PURE__ */ jsx16(Text16, { color: theme.warning, children: " \u2605" })
44112
45943
  ] }),
44113
- /* @__PURE__ */ jsxs10(Box11, { flexShrink: 0, children: [
44114
- /* @__PURE__ */ jsx11(Text11, { color: theme.info, children: "[V] " }),
44115
- /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "Verify/Reinstall Server" })
45944
+ /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45945
+ /* @__PURE__ */ jsx16(Text16, { color: theme.info, children: "[V] " }),
45946
+ /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "Verify/Reinstall Server" })
44116
45947
  ] })
44117
- ] }) : status === "online" ? /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
44118
- /* @__PURE__ */ jsxs10(Box11, { flexShrink: 0, children: [
44119
- /* @__PURE__ */ jsx11(Text11, { color: theme.error, children: "[X] " }),
44120
- /* @__PURE__ */ jsx11(Text11, { children: "Stop Server" })
45948
+ ] }) : status === "online" ? /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", children: [
45949
+ /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45950
+ /* @__PURE__ */ jsx16(Text16, { color: theme.error, children: "[X] " }),
45951
+ /* @__PURE__ */ jsx16(Text16, { children: "Stop Server" })
45952
+ ] }),
45953
+ /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45954
+ /* @__PURE__ */ jsx16(Text16, { color: theme.warning, children: "[R] " }),
45955
+ /* @__PURE__ */ jsx16(Text16, { children: "Restart Server" })
44121
45956
  ] }),
44122
- /* @__PURE__ */ jsxs10(Box11, { flexShrink: 0, children: [
44123
- /* @__PURE__ */ jsx11(Text11, { color: theme.warning, children: "[R] " }),
44124
- /* @__PURE__ */ jsx11(Text11, { children: "Restart Server" })
45957
+ /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45958
+ /* @__PURE__ */ jsx16(Text16, { color: theme.info, children: "[F] " }),
45959
+ /* @__PURE__ */ jsx16(Text16, { children: "Force Save" })
44125
45960
  ] }),
44126
- /* @__PURE__ */ jsxs10(Box11, { flexShrink: 0, children: [
44127
- /* @__PURE__ */ jsx11(Text11, { color: theme.info, children: "[F] " }),
44128
- /* @__PURE__ */ jsx11(Text11, { children: "Force Save" })
45961
+ /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45962
+ /* @__PURE__ */ jsx16(Text16, { color: theme.error, children: "[K] " }),
45963
+ /* @__PURE__ */ jsx16(Text16, { children: "Kill Process" })
44129
45964
  ] }),
44130
- /* @__PURE__ */ jsxs10(Box11, { flexShrink: 0, children: [
44131
- /* @__PURE__ */ jsx11(Text11, { color: theme.error, children: "[K] " }),
44132
- /* @__PURE__ */ jsx11(Text11, { children: "Kill Process" })
45965
+ rconConnected && /* @__PURE__ */ jsxs15(Fragment4, { children: [
45966
+ /* @__PURE__ */ jsx16(Box16, { marginTop: 1, flexShrink: 0, children: /* @__PURE__ */ jsx16(Text16, { bold: true, color: theme.primary, children: "RCON Admin:" }) }),
45967
+ /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45968
+ /* @__PURE__ */ jsx16(Text16, { color: theme.primary, children: "[P] " }),
45969
+ /* @__PURE__ */ jsx16(Text16, { children: "Player Manager" })
45970
+ ] }),
45971
+ /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45972
+ /* @__PURE__ */ jsx16(Text16, { color: theme.info, children: "[N] " }),
45973
+ /* @__PURE__ */ jsx16(Text16, { children: "Server Info" })
45974
+ ] }),
45975
+ /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45976
+ /* @__PURE__ */ jsx16(Text16, { color: theme.warning, children: "[E] " }),
45977
+ /* @__PURE__ */ jsx16(Text16, { children: "Event Manager" })
45978
+ ] }),
45979
+ /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45980
+ /* @__PURE__ */ jsx16(Text16, { color: theme.info, children: "[T] " }),
45981
+ /* @__PURE__ */ jsx16(Text16, { children: "Time Control" })
45982
+ ] }),
45983
+ /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45984
+ /* @__PURE__ */ jsx16(Text16, { color: theme.primary, children: "[G] " }),
45985
+ /* @__PURE__ */ jsx16(Text16, { children: "Boss Progress" })
45986
+ ] }),
45987
+ /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45988
+ /* @__PURE__ */ jsx16(Text16, { color: theme.error, children: "[D] " }),
45989
+ /* @__PURE__ */ jsx16(Text16, { children: "Remove Drops" })
45990
+ ] })
44133
45991
  ] })
44134
- ] }) : /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
44135
- /* @__PURE__ */ jsx11(Spinner, { label: `Server is ${status}...` }),
44136
- /* @__PURE__ */ jsxs10(Box11, { marginTop: 1, flexShrink: 0, children: [
44137
- /* @__PURE__ */ jsx11(Text11, { color: theme.error, children: "[K] " }),
44138
- /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "Force Kill (if stuck)" })
45992
+ ] }) : /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", children: [
45993
+ /* @__PURE__ */ jsx16(Spinner, { label: `Server is ${status}...` }),
45994
+ /* @__PURE__ */ jsxs15(Box16, { marginTop: 1, flexShrink: 0, children: [
45995
+ /* @__PURE__ */ jsx16(Text16, { color: theme.error, children: "[K] " }),
45996
+ /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "Force Kill (if stuck)" })
44139
45997
  ] })
44140
- ] }) : steamCmdInstalled === null ? /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "Checking installation status..." }) : !steamCmdInstalled ? /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "Install SteamCMD to manage server" }) : valheimInstalled === null ? /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "Checking Valheim installation..." }) : /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "Install Valheim to manage server" }),
44141
- /* @__PURE__ */ jsx11(Box11, { marginTop: 1, flexShrink: 0, children: startupTaskProcessing ? /* @__PURE__ */ jsx11(Spinner, { label: "Updating auto-start..." }) : startupTaskRegistered === null ? /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "Checking auto-start status..." }) : /* @__PURE__ */ jsxs10(Box11, { flexShrink: 0, children: [
44142
- /* @__PURE__ */ jsxs10(
44143
- Text11,
45998
+ ] }) : steamCmdInstalled === null ? /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "Checking installation status..." }) : !steamCmdInstalled ? /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "Install SteamCMD to manage server" }) : valheimInstalled === null ? /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "Checking Valheim installation..." }) : /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "Install Valheim to manage server" }),
45999
+ /* @__PURE__ */ jsx16(Box16, { marginTop: 1, flexShrink: 0, children: startupTaskProcessing ? /* @__PURE__ */ jsx16(Spinner, { label: "Updating auto-start..." }) : startupTaskRegistered === null ? /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "Checking auto-start status..." }) : /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
46000
+ /* @__PURE__ */ jsxs15(
46001
+ Text16,
44144
46002
  {
44145
46003
  color: startupTaskRegistered ? theme.warning : theme.success,
44146
46004
  children: [
@@ -44149,7 +46007,7 @@ var Dashboard = () => {
44149
46007
  ]
44150
46008
  }
44151
46009
  ),
44152
- /* @__PURE__ */ jsxs10(Text11, { children: [
46010
+ /* @__PURE__ */ jsxs15(Text16, { children: [
44153
46011
  startupTaskRegistered ? "Disable" : "Enable",
44154
46012
  " Auto-start"
44155
46013
  ] })
@@ -44160,13 +46018,13 @@ var Dashboard = () => {
44160
46018
  };
44161
46019
 
44162
46020
  // src/tui/screens/Settings.tsx
44163
- import { Box as Box15, Text as Text15, useInput as useInput8 } from "ink";
44164
- import { useCallback as useCallback5, useMemo as useMemo4, useState as useState10 } from "react";
46021
+ import { Box as Box20, Text as Text20, useInput as useInput13 } from "ink";
46022
+ import { useCallback as useCallback5, useMemo as useMemo4, useState as useState15 } from "react";
44165
46023
 
44166
46024
  // src/tui/components/NumberInput.tsx
44167
- import { Box as Box12, Text as Text12, useInput as useInput5 } from "ink";
44168
- import { useEffect as useEffect8, useState as useState7 } from "react";
44169
- import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
46025
+ import { Box as Box17, Text as Text17, useInput as useInput10 } from "ink";
46026
+ import { useEffect as useEffect10, useState as useState12 } from "react";
46027
+ import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
44170
46028
  var NumberInput = ({
44171
46029
  value,
44172
46030
  onChange,
@@ -44180,15 +46038,15 @@ var NumberInput = ({
44180
46038
  label,
44181
46039
  width = 10
44182
46040
  }) => {
44183
- const [editMode, setEditMode] = useState7(false);
44184
- const [editBuffer, setEditBuffer] = useState7("");
44185
- const [cursorVisible, setCursorVisible] = useState7(true);
44186
- useEffect8(() => {
46041
+ const [editMode, setEditMode] = useState12(false);
46042
+ const [editBuffer, setEditBuffer] = useState12("");
46043
+ const [cursorVisible, setCursorVisible] = useState12(true);
46044
+ useEffect10(() => {
44187
46045
  if (editMode) {
44188
46046
  setEditBuffer(String(value));
44189
46047
  }
44190
46048
  }, [editMode, value]);
44191
- useEffect8(() => {
46049
+ useEffect10(() => {
44192
46050
  if (!focus || !editMode) {
44193
46051
  setCursorVisible(false);
44194
46052
  return;
@@ -44209,7 +46067,7 @@ var NumberInput = ({
44209
46067
  }
44210
46068
  setEditMode(false);
44211
46069
  };
44212
- useInput5(
46070
+ useInput10(
44213
46071
  (input, key) => {
44214
46072
  if (!focus) return;
44215
46073
  if (key.escape) {
@@ -44280,38 +46138,38 @@ var NumberInput = ({
44280
46138
  const padding = " ".repeat(paddingLength);
44281
46139
  const atMin = value <= min;
44282
46140
  const atMax = value >= max;
44283
- return /* @__PURE__ */ jsxs11(Box12, { children: [
44284
- label && /* @__PURE__ */ jsx12(Box12, { marginRight: 1, children: /* @__PURE__ */ jsxs11(Text12, { children: [
46141
+ return /* @__PURE__ */ jsxs16(Box17, { children: [
46142
+ label && /* @__PURE__ */ jsx17(Box17, { marginRight: 1, children: /* @__PURE__ */ jsxs16(Text17, { children: [
44285
46143
  label,
44286
46144
  ":"
44287
46145
  ] }) }),
44288
- /* @__PURE__ */ jsxs11(Box12, { children: [
44289
- focus && !editMode && /* @__PURE__ */ jsx12(Text12, { color: atMin ? theme.muted : theme.primary, children: atMin ? " " : "\u25C0" }),
44290
- /* @__PURE__ */ jsx12(
44291
- Box12,
46146
+ /* @__PURE__ */ jsxs16(Box17, { children: [
46147
+ focus && !editMode && /* @__PURE__ */ jsx17(Text17, { color: atMin ? theme.muted : theme.primary, children: atMin ? " " : "\u25C0" }),
46148
+ /* @__PURE__ */ jsx17(
46149
+ Box17,
44292
46150
  {
44293
46151
  borderStyle: editMode ? "single" : void 0,
44294
46152
  borderColor: editMode ? theme.primary : void 0,
44295
46153
  marginX: focus && !editMode ? 1 : 0,
44296
- children: /* @__PURE__ */ jsxs11(Text12, { color: focus ? theme.primary : void 0, bold: focus, children: [
46154
+ children: /* @__PURE__ */ jsxs16(Text17, { color: focus ? theme.primary : void 0, bold: focus, children: [
44297
46155
  displayValue,
44298
- editMode && cursorVisible && /* @__PURE__ */ jsx12(Text12, { inverse: true, children: " " }),
44299
- suffix && /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: suffix }),
46156
+ editMode && cursorVisible && /* @__PURE__ */ jsx17(Text17, { inverse: true, children: " " }),
46157
+ suffix && /* @__PURE__ */ jsx17(Text17, { dimColor: true, children: suffix }),
44300
46158
  padding
44301
46159
  ] })
44302
46160
  }
44303
46161
  ),
44304
- focus && !editMode && /* @__PURE__ */ jsx12(Text12, { color: atMax ? theme.muted : theme.primary, children: atMax ? " " : "\u25B6" })
46162
+ focus && !editMode && /* @__PURE__ */ jsx17(Text17, { color: atMax ? theme.muted : theme.primary, children: atMax ? " " : "\u25B6" })
44305
46163
  ] }),
44306
- focus && !editMode && /* @__PURE__ */ jsx12(Box12, { marginLeft: 1, children: /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "[\u2191/\u2193 or type]" }) }),
44307
- editMode && /* @__PURE__ */ jsx12(Box12, { marginLeft: 1, children: /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "[Enter to save, Esc to cancel]" }) })
46164
+ focus && !editMode && /* @__PURE__ */ jsx17(Box17, { marginLeft: 1, children: /* @__PURE__ */ jsx17(Text17, { dimColor: true, children: "[\u2191/\u2193 or type]" }) }),
46165
+ editMode && /* @__PURE__ */ jsx17(Box17, { marginLeft: 1, children: /* @__PURE__ */ jsx17(Text17, { dimColor: true, children: "[Enter to save, Esc to cancel]" }) })
44308
46166
  ] });
44309
46167
  };
44310
46168
 
44311
46169
  // src/tui/components/SelectInput.tsx
44312
- import { Box as Box13, Text as Text13, useInput as useInput6 } from "ink";
44313
- import { useMemo as useMemo3, useState as useState8 } from "react";
44314
- import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
46170
+ import { Box as Box18, Text as Text18, useInput as useInput11 } from "ink";
46171
+ import { useMemo as useMemo3, useState as useState13 } from "react";
46172
+ import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
44315
46173
  function SelectInput({
44316
46174
  options,
44317
46175
  value,
@@ -44324,12 +46182,12 @@ function SelectInput({
44324
46182
  maxVisible = 5,
44325
46183
  expanded: initialExpanded = false
44326
46184
  }) {
44327
- const [expanded, setExpanded] = useState8(initialExpanded);
44328
- const [highlightIndex, setHighlightIndex] = useState8(() => {
46185
+ const [expanded, setExpanded] = useState13(initialExpanded);
46186
+ const [highlightIndex, setHighlightIndex] = useState13(() => {
44329
46187
  const idx = options.findIndex((opt) => opt.value === value);
44330
46188
  return idx >= 0 ? idx : 0;
44331
46189
  });
44332
- const [filter, setFilter] = useState8("");
46190
+ const [filter, setFilter] = useState13("");
44333
46191
  const filteredOptions = useMemo3(() => {
44334
46192
  if (!filter) return options;
44335
46193
  const lowerFilter = filter.toLowerCase();
@@ -44349,7 +46207,7 @@ function SelectInput({
44349
46207
  );
44350
46208
  const selectedOption = options.find((opt) => opt.value === value);
44351
46209
  const displayLabel = selectedOption?.label ?? placeholder;
44352
- useInput6(
46210
+ useInput11(
44353
46211
  (input, key) => {
44354
46212
  if (!focus) return;
44355
46213
  if (!expanded) {
@@ -44426,23 +46284,23 @@ function SelectInput({
44426
46284
  },
44427
46285
  { isActive: focus }
44428
46286
  );
44429
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", children: [
44430
- /* @__PURE__ */ jsxs12(Box13, { children: [
44431
- label && /* @__PURE__ */ jsx13(Box13, { marginRight: 1, children: /* @__PURE__ */ jsxs12(Text13, { children: [
46287
+ return /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", children: [
46288
+ /* @__PURE__ */ jsxs17(Box18, { children: [
46289
+ label && /* @__PURE__ */ jsx18(Box18, { marginRight: 1, children: /* @__PURE__ */ jsxs17(Text18, { children: [
44432
46290
  label,
44433
46291
  ":"
44434
46292
  ] }) }),
44435
- /* @__PURE__ */ jsxs12(Box13, { children: [
44436
- /* @__PURE__ */ jsx13(Text13, { color: focus ? theme.primary : void 0, bold: focus, children: displayLabel }),
44437
- /* @__PURE__ */ jsxs12(Text13, { dimColor: true, children: [
46293
+ /* @__PURE__ */ jsxs17(Box18, { children: [
46294
+ /* @__PURE__ */ jsx18(Text18, { color: focus ? theme.primary : void 0, bold: focus, children: displayLabel }),
46295
+ /* @__PURE__ */ jsxs17(Text18, { dimColor: true, children: [
44438
46296
  " ",
44439
46297
  expanded ? "\u25B2" : "\u25BC"
44440
46298
  ] })
44441
46299
  ] }),
44442
- focus && !expanded && /* @__PURE__ */ jsx13(Box13, { marginLeft: 1, children: /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: "[Enter to expand, \u2191/\u2193 to change]" }) })
46300
+ focus && !expanded && /* @__PURE__ */ jsx18(Box18, { marginLeft: 1, children: /* @__PURE__ */ jsx18(Text18, { dimColor: true, children: "[Enter to expand, \u2191/\u2193 to change]" }) })
44443
46301
  ] }),
44444
- expanded && /* @__PURE__ */ jsxs12(
44445
- Box13,
46302
+ expanded && /* @__PURE__ */ jsxs17(
46303
+ Box18,
44446
46304
  {
44447
46305
  flexDirection: "column",
44448
46306
  marginTop: 1,
@@ -44450,22 +46308,22 @@ function SelectInput({
44450
46308
  borderColor: theme.primary,
44451
46309
  paddingX: 1,
44452
46310
  children: [
44453
- filter && /* @__PURE__ */ jsxs12(Box13, { marginBottom: 1, children: [
44454
- /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: "Filter: " }),
44455
- /* @__PURE__ */ jsx13(Text13, { color: theme.primary, children: filter })
46311
+ filter && /* @__PURE__ */ jsxs17(Box18, { marginBottom: 1, children: [
46312
+ /* @__PURE__ */ jsx18(Text18, { dimColor: true, children: "Filter: " }),
46313
+ /* @__PURE__ */ jsx18(Text18, { color: theme.primary, children: filter })
44456
46314
  ] }),
44457
- scrollOffset > 0 && /* @__PURE__ */ jsx13(Box13, { children: /* @__PURE__ */ jsxs12(Text13, { dimColor: true, children: [
46315
+ scrollOffset > 0 && /* @__PURE__ */ jsx18(Box18, { children: /* @__PURE__ */ jsxs17(Text18, { dimColor: true, children: [
44458
46316
  " \u2191 more (",
44459
46317
  scrollOffset,
44460
46318
  ")"
44461
46319
  ] }) }),
44462
- filteredOptions.length === 0 ? /* @__PURE__ */ jsx13(Box13, { children: /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: "No matches" }) }) : visibleOptions.map((option, visibleIdx) => {
46320
+ filteredOptions.length === 0 ? /* @__PURE__ */ jsx18(Box18, { children: /* @__PURE__ */ jsx18(Text18, { dimColor: true, children: "No matches" }) }) : visibleOptions.map((option, visibleIdx) => {
44463
46321
  const actualIdx = scrollOffset + visibleIdx;
44464
46322
  const isHighlighted = actualIdx === highlightIndex;
44465
46323
  const isSelected = option.value === value;
44466
- return /* @__PURE__ */ jsxs12(Box13, { paddingY: 0, children: [
44467
- /* @__PURE__ */ jsxs12(
44468
- Text13,
46324
+ return /* @__PURE__ */ jsxs17(Box18, { paddingY: 0, children: [
46325
+ /* @__PURE__ */ jsxs17(
46326
+ Text18,
44469
46327
  {
44470
46328
  color: isHighlighted ? theme.primary : isSelected ? theme.success : void 0,
44471
46329
  bold: isHighlighted,
@@ -44476,18 +46334,18 @@ function SelectInput({
44476
46334
  ]
44477
46335
  }
44478
46336
  ),
44479
- option.description && /* @__PURE__ */ jsxs12(Text13, { dimColor: true, children: [
46337
+ option.description && /* @__PURE__ */ jsxs17(Text18, { dimColor: true, children: [
44480
46338
  " - ",
44481
46339
  option.description
44482
46340
  ] })
44483
46341
  ] }, String(option.value));
44484
46342
  }),
44485
- scrollOffset + maxVisible < filteredOptions.length && /* @__PURE__ */ jsx13(Box13, { children: /* @__PURE__ */ jsxs12(Text13, { dimColor: true, children: [
46343
+ scrollOffset + maxVisible < filteredOptions.length && /* @__PURE__ */ jsx18(Box18, { children: /* @__PURE__ */ jsxs17(Text18, { dimColor: true, children: [
44486
46344
  "\u2193 more (",
44487
46345
  filteredOptions.length - scrollOffset - maxVisible,
44488
46346
  ")"
44489
46347
  ] }) }),
44490
- /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: "\u2191/\u2193 navigate \u2022 Enter select \u2022 Esc close \u2022 type to filter" }) })
46348
+ /* @__PURE__ */ jsx18(Box18, { marginTop: 1, children: /* @__PURE__ */ jsx18(Text18, { dimColor: true, children: "\u2191/\u2193 navigate \u2022 Enter select \u2022 Esc close \u2022 type to filter" }) })
44491
46349
  ]
44492
46350
  }
44493
46351
  )
@@ -44495,9 +46353,9 @@ function SelectInput({
44495
46353
  }
44496
46354
 
44497
46355
  // src/tui/components/TextInput.tsx
44498
- import { Box as Box14, Text as Text14, useInput as useInput7 } from "ink";
44499
- import { useEffect as useEffect9, useState as useState9 } from "react";
44500
- import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
46356
+ import { Box as Box19, Text as Text19, useInput as useInput12 } from "ink";
46357
+ import { useEffect as useEffect11, useState as useState14 } from "react";
46358
+ import { jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
44501
46359
  var TextInput = ({
44502
46360
  value,
44503
46361
  onChange,
@@ -44510,12 +46368,12 @@ var TextInput = ({
44510
46368
  focus = true,
44511
46369
  label
44512
46370
  }) => {
44513
- const [cursorPosition, setCursorPosition] = useState9(value.length);
44514
- const [cursorVisible, setCursorVisible] = useState9(true);
44515
- useEffect9(() => {
46371
+ const [cursorPosition, setCursorPosition] = useState14(value.length);
46372
+ const [cursorVisible, setCursorVisible] = useState14(true);
46373
+ useEffect11(() => {
44516
46374
  setCursorPosition(value.length);
44517
46375
  }, [value.length]);
44518
- useEffect9(() => {
46376
+ useEffect11(() => {
44519
46377
  if (!focus) {
44520
46378
  setCursorVisible(false);
44521
46379
  return;
@@ -44526,7 +46384,7 @@ var TextInput = ({
44526
46384
  setCursorVisible(true);
44527
46385
  return () => clearInterval(interval);
44528
46386
  }, [focus]);
44529
- useInput7(
46387
+ useInput12(
44530
46388
  (input, key) => {
44531
46389
  if (!focus) return;
44532
46390
  if (key.return) {
@@ -44582,29 +46440,29 @@ var TextInput = ({
44582
46440
  const paddingLength = Math.max(0, width - contentLength);
44583
46441
  const padding = " ".repeat(paddingLength);
44584
46442
  const showPlaceholder = value.length === 0 && placeholder;
44585
- return /* @__PURE__ */ jsxs13(Box14, { children: [
44586
- label && /* @__PURE__ */ jsx14(Box14, { marginRight: 1, children: /* @__PURE__ */ jsxs13(Text14, { children: [
46443
+ return /* @__PURE__ */ jsxs18(Box19, { children: [
46444
+ label && /* @__PURE__ */ jsx19(Box19, { marginRight: 1, children: /* @__PURE__ */ jsxs18(Text19, { children: [
44587
46445
  label,
44588
46446
  ":"
44589
46447
  ] }) }),
44590
- /* @__PURE__ */ jsx14(
44591
- Box14,
46448
+ /* @__PURE__ */ jsx19(
46449
+ Box19,
44592
46450
  {
44593
46451
  borderStyle: focus ? "single" : void 0,
44594
46452
  borderColor: focus ? theme.primary : void 0,
44595
46453
  paddingX: focus ? 0 : 0,
44596
- children: showPlaceholder && !focus ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: placeholder }) : /* @__PURE__ */ jsxs13(Text14, { children: [
44597
- /* @__PURE__ */ jsx14(Text14, { children: beforeCursor }),
44598
- /* @__PURE__ */ jsx14(
44599
- Text14,
46454
+ children: showPlaceholder && !focus ? /* @__PURE__ */ jsx19(Text19, { dimColor: true, children: placeholder }) : /* @__PURE__ */ jsxs18(Text19, { children: [
46455
+ /* @__PURE__ */ jsx19(Text19, { children: beforeCursor }),
46456
+ /* @__PURE__ */ jsx19(
46457
+ Text19,
44600
46458
  {
44601
46459
  inverse: focus && cursorVisible,
44602
46460
  color: focus ? theme.primary : void 0,
44603
46461
  children: cursorChar
44604
46462
  }
44605
46463
  ),
44606
- /* @__PURE__ */ jsx14(Text14, { children: afterCursor }),
44607
- /* @__PURE__ */ jsx14(Text14, { children: padding })
46464
+ /* @__PURE__ */ jsx19(Text19, { children: afterCursor }),
46465
+ /* @__PURE__ */ jsx19(Text19, { children: padding })
44608
46466
  ] })
44609
46467
  }
44610
46468
  )
@@ -44612,7 +46470,7 @@ var TextInput = ({
44612
46470
  };
44613
46471
 
44614
46472
  // src/tui/hooks/useWorlds.ts
44615
- import { useCallback as useCallback4, useEffect as useEffect10 } from "react";
46473
+ import { useCallback as useCallback4, useEffect as useEffect12 } from "react";
44616
46474
  function useWorlds() {
44617
46475
  const worlds = useStore((s) => s.worlds.worlds);
44618
46476
  const loading = useStore((s) => s.worlds.loading);
@@ -44788,7 +46646,7 @@ function useWorlds() {
44788
46646
  }
44789
46647
 
44790
46648
  // src/tui/screens/Settings.tsx
44791
- import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
46649
+ import { jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
44792
46650
  var SECTION_LABELS = {
44793
46651
  server: "Server",
44794
46652
  modifiers: "Difficulty Modifiers",
@@ -44868,10 +46726,10 @@ var Settings = () => {
44868
46726
  const editingField = useStore((s) => s.ui.editingField);
44869
46727
  const setEditingField = useStore((s) => s.actions.setEditingField);
44870
46728
  const addLog = useStore((s) => s.actions.addLog);
44871
- const [selectedIndex, setSelectedIndex] = useState10(0);
44872
- const [localValue, setLocalValue] = useState10("");
44873
- const [scrollOffset, setScrollOffset] = useState10(0);
44874
- const [expandedSections, setExpandedSections] = useState10(
46729
+ const [selectedIndex, setSelectedIndex] = useState15(0);
46730
+ const [localValue, setLocalValue] = useState15("");
46731
+ const [scrollOffset, setScrollOffset] = useState15(0);
46732
+ const [expandedSections, setExpandedSections] = useState15(
44875
46733
  () => /* @__PURE__ */ new Set(["server"])
44876
46734
  );
44877
46735
  const MAX_VISIBLE_ROWS = 15;
@@ -45235,7 +47093,7 @@ var Settings = () => {
45235
47093
  const cancelEdit = useCallback5(() => {
45236
47094
  setEditingField(null);
45237
47095
  }, [setEditingField]);
45238
- useInput8(
47096
+ useInput13(
45239
47097
  (input, key) => {
45240
47098
  if (isEditing) return;
45241
47099
  if (key.upArrow || input === "k") {
@@ -45291,14 +47149,14 @@ var Settings = () => {
45291
47149
  const itemCount = settings.filter(
45292
47150
  (s) => s.section === item.section
45293
47151
  ).length;
45294
- return /* @__PURE__ */ jsxs14(Box15, { flexShrink: 0, minHeight: 1, children: [
45295
- /* @__PURE__ */ jsx15(Text15, { color: isSelected ? theme.primary : void 0, children: isSelected ? "\u25B6 " : " " }),
45296
- /* @__PURE__ */ jsxs14(Text15, { bold: true, color: isSelected ? theme.primary : theme.secondary, children: [
47152
+ return /* @__PURE__ */ jsxs19(Box20, { flexShrink: 0, minHeight: 1, children: [
47153
+ /* @__PURE__ */ jsx20(Text20, { color: isSelected ? theme.primary : void 0, children: isSelected ? "\u25B6 " : " " }),
47154
+ /* @__PURE__ */ jsxs19(Text20, { bold: true, color: isSelected ? theme.primary : theme.secondary, children: [
45297
47155
  isExpanded ? "\u25BC" : "\u25B6",
45298
47156
  " ",
45299
47157
  item.label
45300
47158
  ] }),
45301
- /* @__PURE__ */ jsxs14(Text15, { dimColor: true, children: [
47159
+ /* @__PURE__ */ jsxs19(Text20, { dimColor: true, children: [
45302
47160
  " (",
45303
47161
  itemCount,
45304
47162
  ")"
@@ -45312,13 +47170,13 @@ var Settings = () => {
45312
47170
  switch (setting.type) {
45313
47171
  case "text":
45314
47172
  case "password":
45315
- return /* @__PURE__ */ jsxs14(Box15, { flexShrink: 0, minHeight: 1, children: [
45316
- /* @__PURE__ */ jsxs14(Text15, { color: theme.primary, children: [
47173
+ return /* @__PURE__ */ jsxs19(Box20, { flexShrink: 0, minHeight: 1, children: [
47174
+ /* @__PURE__ */ jsxs19(Text20, { color: theme.primary, children: [
45317
47175
  " \u25B6 ",
45318
47176
  setting.label,
45319
47177
  ": "
45320
47178
  ] }),
45321
- /* @__PURE__ */ jsx15(
47179
+ /* @__PURE__ */ jsx20(
45322
47180
  TextInput,
45323
47181
  {
45324
47182
  value: localValue,
@@ -45331,13 +47189,13 @@ var Settings = () => {
45331
47189
  )
45332
47190
  ] }, setting.key);
45333
47191
  case "number":
45334
- return /* @__PURE__ */ jsxs14(Box15, { flexShrink: 0, minHeight: 1, children: [
45335
- /* @__PURE__ */ jsxs14(Text15, { color: theme.primary, children: [
47192
+ return /* @__PURE__ */ jsxs19(Box20, { flexShrink: 0, minHeight: 1, children: [
47193
+ /* @__PURE__ */ jsxs19(Text20, { color: theme.primary, children: [
45336
47194
  " \u25B6 ",
45337
47195
  setting.label,
45338
47196
  ": "
45339
47197
  ] }),
45340
- /* @__PURE__ */ jsx15(
47198
+ /* @__PURE__ */ jsx20(
45341
47199
  NumberInput,
45342
47200
  {
45343
47201
  value: localValue,
@@ -45353,13 +47211,13 @@ var Settings = () => {
45353
47211
  )
45354
47212
  ] }, setting.key);
45355
47213
  case "select":
45356
- return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", flexShrink: 0, children: [
45357
- /* @__PURE__ */ jsx15(Box15, { flexShrink: 0, children: /* @__PURE__ */ jsxs14(Text15, { color: theme.primary, children: [
47214
+ return /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", flexShrink: 0, children: [
47215
+ /* @__PURE__ */ jsx20(Box20, { flexShrink: 0, children: /* @__PURE__ */ jsxs19(Text20, { color: theme.primary, children: [
45358
47216
  " \u25B6 ",
45359
47217
  setting.label,
45360
47218
  ": "
45361
47219
  ] }) }),
45362
- /* @__PURE__ */ jsx15(Box15, { marginLeft: 4, children: /* @__PURE__ */ jsx15(
47220
+ /* @__PURE__ */ jsx20(Box20, { marginLeft: 4, children: /* @__PURE__ */ jsx20(
45363
47221
  SelectInput,
45364
47222
  {
45365
47223
  options: setting.options ?? [],
@@ -45382,15 +47240,15 @@ var Settings = () => {
45382
47240
  }
45383
47241
  }
45384
47242
  const displayValue = setting.type === "toggle" ? value ? "Yes" : "No" : setting.type === "password" ? value ? "********" : "(none)" : setting.type === "select" ? setting.options?.find((o) => o.value === value)?.label ?? value : `${value}${setting.suffix ?? ""}`;
45385
- return /* @__PURE__ */ jsxs14(Box15, { flexShrink: 0, minHeight: 1, children: [
45386
- /* @__PURE__ */ jsx15(Text15, { color: isSelected ? theme.primary : void 0, children: isSelected ? " \u25B6 " : " " }),
45387
- /* @__PURE__ */ jsxs14(Text15, { bold: isSelected, color: isSelected ? theme.primary : void 0, children: [
47243
+ return /* @__PURE__ */ jsxs19(Box20, { flexShrink: 0, minHeight: 1, children: [
47244
+ /* @__PURE__ */ jsx20(Text20, { color: isSelected ? theme.primary : void 0, children: isSelected ? " \u25B6 " : " " }),
47245
+ /* @__PURE__ */ jsxs19(Text20, { bold: isSelected, color: isSelected ? theme.primary : void 0, children: [
45388
47246
  setting.label,
45389
47247
  ":"
45390
47248
  ] }),
45391
- /* @__PURE__ */ jsx15(Text15, { children: " " }),
45392
- /* @__PURE__ */ jsx15(
45393
- Text15,
47249
+ /* @__PURE__ */ jsx20(Text20, { children: " " }),
47250
+ /* @__PURE__ */ jsx20(
47251
+ Text20,
45394
47252
  {
45395
47253
  color: setting.type === "toggle" ? value ? theme.success : theme.error : theme.secondary,
45396
47254
  children: displayValue
@@ -45398,36 +47256,36 @@ var Settings = () => {
45398
47256
  )
45399
47257
  ] }, setting.key);
45400
47258
  };
45401
- return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", flexGrow: 1, padding: 1, overflow: "hidden", children: [
45402
- /* @__PURE__ */ jsx15(Box15, { marginBottom: 1, flexShrink: 0, minHeight: 1, children: /* @__PURE__ */ jsx15(Text15, { bold: true, color: theme.primary, children: "\u2500 Settings \u2500" }) }),
45403
- /* @__PURE__ */ jsx15(Box15, { marginBottom: 1, flexShrink: 0, minHeight: 1, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: isEditing ? "Enter to save, Esc to cancel" : "\u2191/\u2193 navigate \u2022 Enter expand/edit \u2022 Tab collapse all" }) }),
45404
- hasMoreAbove && /* @__PURE__ */ jsx15(Box15, { flexShrink: 0, minHeight: 1, children: /* @__PURE__ */ jsxs14(Text15, { dimColor: true, children: [
47259
+ return /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", flexGrow: 1, padding: 1, overflow: "hidden", children: [
47260
+ /* @__PURE__ */ jsx20(Box20, { marginBottom: 1, flexShrink: 0, minHeight: 1, children: /* @__PURE__ */ jsx20(Text20, { bold: true, color: theme.primary, children: "\u2500 Settings \u2500" }) }),
47261
+ /* @__PURE__ */ jsx20(Box20, { marginBottom: 1, flexShrink: 0, minHeight: 1, children: /* @__PURE__ */ jsx20(Text20, { dimColor: true, children: isEditing ? "Enter to save, Esc to cancel" : "\u2191/\u2193 navigate \u2022 Enter expand/edit \u2022 Tab collapse all" }) }),
47262
+ hasMoreAbove && /* @__PURE__ */ jsx20(Box20, { flexShrink: 0, minHeight: 1, children: /* @__PURE__ */ jsxs19(Text20, { dimColor: true, children: [
45405
47263
  " \u2191 ",
45406
47264
  scrollOffset,
45407
47265
  " more above"
45408
47266
  ] }) }),
45409
- /* @__PURE__ */ jsx15(Box15, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: visibleItems.map((item, visibleIdx) => {
47267
+ /* @__PURE__ */ jsx20(Box20, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: visibleItems.map((item, visibleIdx) => {
45410
47268
  const globalIndex = scrollOffset + visibleIdx;
45411
47269
  return renderNavItem(item, globalIndex);
45412
47270
  }) }),
45413
- hasMoreBelow && /* @__PURE__ */ jsx15(Box15, { flexShrink: 0, minHeight: 1, children: /* @__PURE__ */ jsxs14(Text15, { dimColor: true, children: [
47271
+ hasMoreBelow && /* @__PURE__ */ jsx20(Box20, { flexShrink: 0, minHeight: 1, children: /* @__PURE__ */ jsxs19(Text20, { dimColor: true, children: [
45414
47272
  " ",
45415
47273
  "\u2193 ",
45416
47274
  navItems.length - scrollOffset - MAX_VISIBLE_ROWS,
45417
47275
  " more below"
45418
47276
  ] }) }),
45419
- rcon.enabled && /* @__PURE__ */ jsxs14(Box15, { marginTop: 1, flexShrink: 0, minHeight: 1, children: [
45420
- /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "RCON Status: " }),
45421
- /* @__PURE__ */ jsx15(Text15, { color: rcon.connected ? theme.success : theme.error, children: rcon.connected ? "Connected" : "Disconnected" })
47277
+ rcon.enabled && /* @__PURE__ */ jsxs19(Box20, { marginTop: 1, flexShrink: 0, minHeight: 1, children: [
47278
+ /* @__PURE__ */ jsx20(Text20, { dimColor: true, children: "RCON Status: " }),
47279
+ /* @__PURE__ */ jsx20(Text20, { color: rcon.connected ? theme.success : theme.error, children: rcon.connected ? "Connected" : "Disconnected" })
45422
47280
  ] }),
45423
- /* @__PURE__ */ jsx15(Box15, { marginTop: 1, flexShrink: 0, minHeight: 1, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "Note: Restart server for changes to take effect" }) })
47281
+ /* @__PURE__ */ jsx20(Box20, { marginTop: 1, flexShrink: 0, minHeight: 1, children: /* @__PURE__ */ jsx20(Text20, { dimColor: true, children: "Note: Restart server for changes to take effect" }) })
45424
47282
  ] });
45425
47283
  };
45426
47284
 
45427
47285
  // src/tui/screens/Worlds.tsx
45428
- import { Box as Box16, Text as Text16, useInput as useInput9 } from "ink";
45429
- import { useCallback as useCallback6, useEffect as useEffect11, useState as useState11 } from "react";
45430
- import { Fragment, jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
47286
+ import { Box as Box21, Text as Text21, useInput as useInput14 } from "ink";
47287
+ import { useCallback as useCallback6, useEffect as useEffect13, useState as useState16 } from "react";
47288
+ import { Fragment as Fragment5, jsx as jsx21, jsxs as jsxs20 } from "react/jsx-runtime";
45431
47289
  function formatBytes2(bytes) {
45432
47290
  if (bytes === 0) return "0 B";
45433
47291
  const k = 1024;
@@ -45480,16 +47338,16 @@ var Worlds = () => {
45480
47338
  const serverStatus = useStore((s) => s.server.status);
45481
47339
  const worldGenerating = useStore((s) => s.server.worldGenerating);
45482
47340
  const setEditingField = useStore((s) => s.actions.setEditingField);
45483
- const [mode, setModeInternal] = useState11("list");
45484
- const [inputPath, setInputPath] = useState11("");
45485
- const [newWorldName, setNewWorldName] = useState11("");
45486
- const [newWorldSeed, setNewWorldSeed] = useState11("");
45487
- const [createStep, setCreateStep] = useState11("name");
45488
- const [loading, setLoading] = useState11(false);
45489
- const [operationStatus, setOperationStatus] = useState11("");
45490
- const [pendingConfigSelected, setPendingConfigSelected] = useState11(false);
45491
- const [prevServerStatus, setPrevServerStatus] = useState11(serverStatus);
45492
- const [prevWorldGenerating, setPrevWorldGenerating] = useState11(worldGenerating);
47341
+ const [mode, setModeInternal] = useState16("list");
47342
+ const [inputPath, setInputPath] = useState16("");
47343
+ const [newWorldName, setNewWorldName] = useState16("");
47344
+ const [newWorldSeed, setNewWorldSeed] = useState16("");
47345
+ const [createStep, setCreateStep] = useState16("name");
47346
+ const [loading, setLoading] = useState16(false);
47347
+ const [operationStatus, setOperationStatus] = useState16("");
47348
+ const [pendingConfigSelected, setPendingConfigSelected] = useState16(false);
47349
+ const [prevServerStatus, setPrevServerStatus] = useState16(serverStatus);
47350
+ const [prevWorldGenerating, setPrevWorldGenerating] = useState16(worldGenerating);
45493
47351
  const hasPendingConfig = config.world && !worlds.some((w) => w.name === config.world);
45494
47352
  const otherPendingWorlds = pendingWorldNames.filter(
45495
47353
  (name) => name !== config.world && !worlds.some((w) => w.name === name)
@@ -45502,10 +47360,10 @@ var Worlds = () => {
45502
47360
  },
45503
47361
  [setEditingField]
45504
47362
  );
45505
- useEffect11(() => {
47363
+ useEffect13(() => {
45506
47364
  refresh();
45507
47365
  }, [refresh]);
45508
- useEffect11(() => {
47366
+ useEffect13(() => {
45509
47367
  if (prevServerStatus !== "online" && serverStatus === "online") {
45510
47368
  const timer = setTimeout(() => {
45511
47369
  refresh();
@@ -45515,14 +47373,14 @@ var Worlds = () => {
45515
47373
  }
45516
47374
  setPrevServerStatus(serverStatus);
45517
47375
  }, [serverStatus, prevServerStatus, refresh, addLog]);
45518
- useEffect11(() => {
47376
+ useEffect13(() => {
45519
47377
  if (prevWorldGenerating && !worldGenerating) {
45520
47378
  refresh();
45521
47379
  addLog("info", "Refreshed worlds list after world generation completed");
45522
47380
  }
45523
47381
  setPrevWorldGenerating(worldGenerating);
45524
47382
  }, [worldGenerating, prevWorldGenerating, refresh, addLog]);
45525
- useEffect11(() => {
47383
+ useEffect13(() => {
45526
47384
  if (!hasPendingConfig && pendingConfigSelected) {
45527
47385
  setPendingConfigSelected(false);
45528
47386
  }
@@ -45669,7 +47527,7 @@ var Worlds = () => {
45669
47527
  },
45670
47528
  [closeModal, getSelectedWorld, config.world, deleteWorld2, refresh, addLog]
45671
47529
  );
45672
- useInput9(
47530
+ useInput14(
45673
47531
  (input, key) => {
45674
47532
  if (modalOpen || loading) return;
45675
47533
  if (key.upArrow || input === "k") {
@@ -45722,7 +47580,7 @@ var Worlds = () => {
45722
47580
  const world = getSelectedWorld();
45723
47581
  if (world && world.name !== config.world) {
45724
47582
  openModal(
45725
- /* @__PURE__ */ jsx16(
47583
+ /* @__PURE__ */ jsx21(
45726
47584
  DeleteWorldModal,
45727
47585
  {
45728
47586
  worldName: world.name,
@@ -45787,27 +47645,27 @@ var Worlds = () => {
45787
47645
  handleBackup
45788
47646
  ]);
45789
47647
  if (worldsLoading && worlds.length === 0) {
45790
- return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
45791
- /* @__PURE__ */ jsx16(Box16, { marginBottom: 1, children: /* @__PURE__ */ jsx16(Text16, { bold: true, color: theme.primary, children: "\u2500 Worlds \u2500" }) }),
45792
- /* @__PURE__ */ jsx16(Spinner, { label: "Loading worlds..." })
47648
+ return /* @__PURE__ */ jsxs20(Box21, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
47649
+ /* @__PURE__ */ jsx21(Box21, { marginBottom: 1, children: /* @__PURE__ */ jsx21(Text21, { bold: true, color: theme.primary, children: "\u2500 Worlds \u2500" }) }),
47650
+ /* @__PURE__ */ jsx21(Spinner, { label: "Loading worlds..." })
45793
47651
  ] });
45794
47652
  }
45795
47653
  if (error2 && worlds.length === 0) {
45796
- return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
45797
- /* @__PURE__ */ jsx16(Box16, { marginBottom: 1, children: /* @__PURE__ */ jsx16(Text16, { bold: true, color: theme.primary, children: "\u2500 Worlds \u2500" }) }),
45798
- /* @__PURE__ */ jsxs15(Text16, { color: theme.error, children: [
47654
+ return /* @__PURE__ */ jsxs20(Box21, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
47655
+ /* @__PURE__ */ jsx21(Box21, { marginBottom: 1, children: /* @__PURE__ */ jsx21(Text21, { bold: true, color: theme.primary, children: "\u2500 Worlds \u2500" }) }),
47656
+ /* @__PURE__ */ jsxs20(Text21, { color: theme.error, children: [
45799
47657
  "Error: ",
45800
47658
  error2
45801
47659
  ] }),
45802
- /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "Press R to retry" }) })
47660
+ /* @__PURE__ */ jsx21(Box21, { marginTop: 1, children: /* @__PURE__ */ jsx21(Text21, { dimColor: true, children: "Press R to retry" }) })
45803
47661
  ] });
45804
47662
  }
45805
47663
  if (mode === "create") {
45806
- return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
45807
- /* @__PURE__ */ jsx16(Box16, { marginBottom: 1, children: /* @__PURE__ */ jsx16(Text16, { bold: true, color: theme.primary, children: "\u2500 Create New World \u2500" }) }),
45808
- loading ? /* @__PURE__ */ jsx16(Spinner, { label: operationStatus }) : createStep === "name" ? /* @__PURE__ */ jsxs15(Fragment, { children: [
45809
- /* @__PURE__ */ jsx16(Box16, { marginBottom: 1, children: /* @__PURE__ */ jsx16(Text16, { children: "Enter a name for your new world:" }) }),
45810
- /* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsx16(
47664
+ return /* @__PURE__ */ jsxs20(Box21, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
47665
+ /* @__PURE__ */ jsx21(Box21, { marginBottom: 1, children: /* @__PURE__ */ jsx21(Text21, { bold: true, color: theme.primary, children: "\u2500 Create New World \u2500" }) }),
47666
+ loading ? /* @__PURE__ */ jsx21(Spinner, { label: operationStatus }) : createStep === "name" ? /* @__PURE__ */ jsxs20(Fragment5, { children: [
47667
+ /* @__PURE__ */ jsx21(Box21, { marginBottom: 1, children: /* @__PURE__ */ jsx21(Text21, { children: "Enter a name for your new world:" }) }),
47668
+ /* @__PURE__ */ jsx21(Box21, { children: /* @__PURE__ */ jsx21(
45811
47669
  TextInput,
45812
47670
  {
45813
47671
  value: newWorldName,
@@ -45818,14 +47676,14 @@ var Worlds = () => {
45818
47676
  focus: true
45819
47677
  }
45820
47678
  ) }),
45821
- /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "Enter to continue, Esc to cancel" }) })
45822
- ] }) : /* @__PURE__ */ jsxs15(Fragment, { children: [
45823
- /* @__PURE__ */ jsxs15(Box16, { marginBottom: 1, children: [
45824
- /* @__PURE__ */ jsx16(Text16, { children: "World: " }),
45825
- /* @__PURE__ */ jsx16(Text16, { color: theme.primary, bold: true, children: newWorldName })
47679
+ /* @__PURE__ */ jsx21(Box21, { marginTop: 1, children: /* @__PURE__ */ jsx21(Text21, { dimColor: true, children: "Enter to continue, Esc to cancel" }) })
47680
+ ] }) : /* @__PURE__ */ jsxs20(Fragment5, { children: [
47681
+ /* @__PURE__ */ jsxs20(Box21, { marginBottom: 1, children: [
47682
+ /* @__PURE__ */ jsx21(Text21, { children: "World: " }),
47683
+ /* @__PURE__ */ jsx21(Text21, { color: theme.primary, bold: true, children: newWorldName })
45826
47684
  ] }),
45827
- /* @__PURE__ */ jsx16(Box16, { marginBottom: 1, children: /* @__PURE__ */ jsx16(Text16, { children: "Enter a seed (optional, leave empty for random):" }) }),
45828
- /* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsx16(
47685
+ /* @__PURE__ */ jsx21(Box21, { marginBottom: 1, children: /* @__PURE__ */ jsx21(Text21, { children: "Enter a seed (optional, leave empty for random):" }) }),
47686
+ /* @__PURE__ */ jsx21(Box21, { children: /* @__PURE__ */ jsx21(
45829
47687
  TextInput,
45830
47688
  {
45831
47689
  value: newWorldSeed,
@@ -45836,26 +47694,26 @@ var Worlds = () => {
45836
47694
  focus: true
45837
47695
  }
45838
47696
  ) }),
45839
- /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "Enter to create world, Esc to cancel" }) }),
45840
- /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text16, { color: theme.warning, children: "Note: Start the server to generate the world files" }) })
47697
+ /* @__PURE__ */ jsx21(Box21, { marginTop: 1, children: /* @__PURE__ */ jsx21(Text21, { dimColor: true, children: "Enter to create world, Esc to cancel" }) }),
47698
+ /* @__PURE__ */ jsx21(Box21, { marginTop: 1, children: /* @__PURE__ */ jsx21(Text21, { color: theme.warning, children: "Note: Start the server to generate the world files" }) })
45841
47699
  ] })
45842
47700
  ] });
45843
47701
  }
45844
47702
  if (mode !== "list") {
45845
47703
  const modeTitle = mode === "import" ? "Import World" : mode === "export" ? "Export World" : "Backup World";
45846
47704
  const modeHint = mode === "import" ? "Enter path to world folder or .db file" : mode === "export" ? `Export "${selectedWorld}" to path` : `Backup "${selectedWorld}" to path`;
45847
- return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
45848
- /* @__PURE__ */ jsx16(Box16, { marginBottom: 1, children: /* @__PURE__ */ jsxs15(Text16, { bold: true, color: theme.primary, children: [
47705
+ return /* @__PURE__ */ jsxs20(Box21, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
47706
+ /* @__PURE__ */ jsx21(Box21, { marginBottom: 1, children: /* @__PURE__ */ jsxs20(Text21, { bold: true, color: theme.primary, children: [
45849
47707
  "\u2500 ",
45850
47708
  modeTitle,
45851
47709
  " \u2500"
45852
47710
  ] }) }),
45853
- loading ? /* @__PURE__ */ jsx16(Spinner, { label: operationStatus }) : /* @__PURE__ */ jsxs15(Fragment, { children: [
45854
- /* @__PURE__ */ jsx16(Box16, { marginBottom: 1, children: /* @__PURE__ */ jsxs15(Text16, { children: [
47711
+ loading ? /* @__PURE__ */ jsx21(Spinner, { label: operationStatus }) : /* @__PURE__ */ jsxs20(Fragment5, { children: [
47712
+ /* @__PURE__ */ jsx21(Box21, { marginBottom: 1, children: /* @__PURE__ */ jsxs20(Text21, { children: [
45855
47713
  modeHint,
45856
47714
  ":"
45857
47715
  ] }) }),
45858
- /* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsx16(
47716
+ /* @__PURE__ */ jsx21(Box21, { children: /* @__PURE__ */ jsx21(
45859
47717
  TextInput,
45860
47718
  {
45861
47719
  value: inputPath,
@@ -45866,19 +47724,19 @@ var Worlds = () => {
45866
47724
  focus: true
45867
47725
  }
45868
47726
  ) }),
45869
- /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "Enter to confirm, Esc to cancel" }) })
47727
+ /* @__PURE__ */ jsx21(Box21, { marginTop: 1, children: /* @__PURE__ */ jsx21(Text21, { dimColor: true, children: "Enter to confirm, Esc to cancel" }) })
45870
47728
  ] })
45871
47729
  ] });
45872
47730
  }
45873
- return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", flexGrow: 1, padding: 1, overflow: "hidden", children: [
45874
- /* @__PURE__ */ jsxs15(Box16, { marginBottom: 1, children: [
45875
- /* @__PURE__ */ jsx16(Text16, { bold: true, color: theme.primary, children: "\u2500 Worlds \u2500" }),
45876
- worldsLoading && /* @__PURE__ */ jsx16(Spinner, {})
47731
+ return /* @__PURE__ */ jsxs20(Box21, { flexDirection: "column", flexGrow: 1, padding: 1, overflow: "hidden", children: [
47732
+ /* @__PURE__ */ jsxs20(Box21, { marginBottom: 1, children: [
47733
+ /* @__PURE__ */ jsx21(Text21, { bold: true, color: theme.primary, children: "\u2500 Worlds \u2500" }),
47734
+ worldsLoading && /* @__PURE__ */ jsx21(Spinner, {})
45877
47735
  ] }),
45878
- /* @__PURE__ */ jsx16(Box16, { marginBottom: 1, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "\u2191/\u2193 Navigate | Enter Set Active | N New | I Import | E Export | B Backup | D Delete | R Refresh" }) }),
45879
- /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", overflow: "hidden", children: [
45880
- hasPendingConfig && /* @__PURE__ */ jsxs15(
45881
- Box16,
47736
+ /* @__PURE__ */ jsx21(Box21, { marginBottom: 1, children: /* @__PURE__ */ jsx21(Text21, { dimColor: true, children: "\u2191/\u2193 Navigate | Enter Set Active | N New | I Import | E Export | B Backup | D Delete | R Refresh" }) }),
47737
+ /* @__PURE__ */ jsxs20(Box21, { flexDirection: "column", overflow: "hidden", children: [
47738
+ hasPendingConfig && /* @__PURE__ */ jsxs20(
47739
+ Box21,
45882
47740
  {
45883
47741
  flexDirection: "column",
45884
47742
  flexShrink: 0,
@@ -45887,33 +47745,33 @@ var Worlds = () => {
45887
47745
  paddingX: pendingConfigSelected || worlds.length === 0 ? 1 : 0,
45888
47746
  marginBottom: 1,
45889
47747
  children: [
45890
- /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45891
- /* @__PURE__ */ jsx16(
45892
- Text16,
47748
+ /* @__PURE__ */ jsxs20(Box21, { flexShrink: 0, children: [
47749
+ /* @__PURE__ */ jsx21(
47750
+ Text21,
45893
47751
  {
45894
47752
  color: pendingConfigSelected ? theme.primary : void 0,
45895
47753
  bold: true,
45896
47754
  children: config.world
45897
47755
  }
45898
47756
  ),
45899
- /* @__PURE__ */ jsx16(Text16, { color: theme.success, children: " (Active)" }),
45900
- worldGenerating ? /* @__PURE__ */ jsxs15(Box16, { marginLeft: 1, children: [
45901
- /* @__PURE__ */ jsx16(Spinner, {}),
45902
- /* @__PURE__ */ jsx16(Text16, { color: theme.warning, children: " Generating..." })
45903
- ] }) : serverStatus === "starting" ? /* @__PURE__ */ jsxs15(Box16, { marginLeft: 1, children: [
45904
- /* @__PURE__ */ jsx16(Spinner, {}),
45905
- /* @__PURE__ */ jsx16(Text16, { color: theme.info, children: " Server starting..." })
45906
- ] }) : /* @__PURE__ */ jsx16(Text16, { color: theme.warning, children: " - Not generated" })
47757
+ /* @__PURE__ */ jsx21(Text21, { color: theme.success, children: " (Active)" }),
47758
+ worldGenerating ? /* @__PURE__ */ jsxs20(Box21, { marginLeft: 1, children: [
47759
+ /* @__PURE__ */ jsx21(Spinner, {}),
47760
+ /* @__PURE__ */ jsx21(Text21, { color: theme.warning, children: " Generating..." })
47761
+ ] }) : serverStatus === "starting" ? /* @__PURE__ */ jsxs20(Box21, { marginLeft: 1, children: [
47762
+ /* @__PURE__ */ jsx21(Spinner, {}),
47763
+ /* @__PURE__ */ jsx21(Text21, { color: theme.info, children: " Server starting..." })
47764
+ ] }) : /* @__PURE__ */ jsx21(Text21, { color: theme.warning, children: " - Not generated" })
45907
47765
  ] }),
45908
- /* @__PURE__ */ jsx16(Box16, { marginLeft: 2, flexShrink: 0, children: worldGenerating ? /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "New world is being generated (this may take ~1 minute)" }) : /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "Start the server to generate this world" }) })
47766
+ /* @__PURE__ */ jsx21(Box21, { marginLeft: 2, flexShrink: 0, children: worldGenerating ? /* @__PURE__ */ jsx21(Text21, { dimColor: true, children: "New world is being generated (this may take ~1 minute)" }) : /* @__PURE__ */ jsx21(Text21, { dimColor: true, children: "Start the server to generate this world" }) })
45909
47767
  ]
45910
47768
  }
45911
47769
  ),
45912
- worlds.length === 0 && !config.world ? /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "No worlds found. Press N to create one." }) : worlds.map((world) => {
47770
+ worlds.length === 0 && !config.world ? /* @__PURE__ */ jsx21(Text21, { dimColor: true, children: "No worlds found. Press N to create one." }) : worlds.map((world) => {
45913
47771
  const isSelected = world.name === selectedWorld;
45914
47772
  const isActive = world.name === config.world;
45915
- return /* @__PURE__ */ jsxs15(
45916
- Box16,
47773
+ return /* @__PURE__ */ jsxs20(
47774
+ Box21,
45917
47775
  {
45918
47776
  flexDirection: "column",
45919
47777
  flexShrink: 0,
@@ -45922,11 +47780,11 @@ var Worlds = () => {
45922
47780
  paddingX: isSelected ? 1 : 0,
45923
47781
  marginBottom: 1,
45924
47782
  children: [
45925
- /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45926
- /* @__PURE__ */ jsx16(Text16, { color: isSelected ? theme.primary : void 0, bold: true, children: world.name }),
45927
- isActive && /* @__PURE__ */ jsx16(Text16, { color: theme.success, children: " (Active)" }),
45928
- world.pendingSave && /* @__PURE__ */ jsx16(Text16, { color: theme.warning, children: " - Pending Save" }),
45929
- world.backups && world.backups.length > 0 && /* @__PURE__ */ jsxs15(Text16, { color: theme.info, children: [
47783
+ /* @__PURE__ */ jsxs20(Box21, { flexShrink: 0, children: [
47784
+ /* @__PURE__ */ jsx21(Text21, { color: isSelected ? theme.primary : void 0, bold: true, children: world.name }),
47785
+ isActive && /* @__PURE__ */ jsx21(Text21, { color: theme.success, children: " (Active)" }),
47786
+ world.pendingSave && /* @__PURE__ */ jsx21(Text21, { color: theme.warning, children: " - Pending Save" }),
47787
+ world.backups && world.backups.length > 0 && /* @__PURE__ */ jsxs20(Text21, { color: theme.info, children: [
45930
47788
  " ",
45931
47789
  "[",
45932
47790
  world.backups.length,
@@ -45935,27 +47793,27 @@ var Worlds = () => {
45935
47793
  "]"
45936
47794
  ] })
45937
47795
  ] }),
45938
- /* @__PURE__ */ jsx16(Box16, { marginLeft: 2, flexShrink: 0, children: world.pendingSave ? /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "World generated but not yet saved to disk" }) : /* @__PURE__ */ jsxs15(Fragment, { children: [
45939
- /* @__PURE__ */ jsxs15(Text16, { dimColor: true, children: [
47796
+ /* @__PURE__ */ jsx21(Box21, { marginLeft: 2, flexShrink: 0, children: world.pendingSave ? /* @__PURE__ */ jsx21(Text21, { dimColor: true, children: "World generated but not yet saved to disk" }) : /* @__PURE__ */ jsxs20(Fragment5, { children: [
47797
+ /* @__PURE__ */ jsxs20(Text21, { dimColor: true, children: [
45940
47798
  "Size: ",
45941
47799
  formatBytes2(world.size)
45942
47800
  ] }),
45943
- /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: " | " }),
45944
- /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: getSourceLabel(world.source) })
47801
+ /* @__PURE__ */ jsx21(Text21, { dimColor: true, children: " | " }),
47802
+ /* @__PURE__ */ jsx21(Text21, { dimColor: true, children: getSourceLabel(world.source) })
45945
47803
  ] }) }),
45946
- !world.pendingSave && /* @__PURE__ */ jsx16(Box16, { marginLeft: 2, flexShrink: 0, children: /* @__PURE__ */ jsxs15(Text16, { dimColor: true, children: [
47804
+ !world.pendingSave && /* @__PURE__ */ jsx21(Box21, { marginLeft: 2, flexShrink: 0, children: /* @__PURE__ */ jsxs20(Text21, { dimColor: true, children: [
45947
47805
  "Modified: ",
45948
47806
  formatDate(world.modified)
45949
47807
  ] }) }),
45950
- isSelected && world.backups && world.backups.length > 0 && /* @__PURE__ */ jsxs15(Box16, { marginLeft: 2, flexDirection: "column", flexShrink: 0, children: [
45951
- /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "Backups:" }),
45952
- world.backups.slice(0, 3).map((backup) => /* @__PURE__ */ jsx16(Box16, { marginLeft: 2, children: /* @__PURE__ */ jsxs15(Text16, { dimColor: true, children: [
47808
+ isSelected && world.backups && world.backups.length > 0 && /* @__PURE__ */ jsxs20(Box21, { marginLeft: 2, flexDirection: "column", flexShrink: 0, children: [
47809
+ /* @__PURE__ */ jsx21(Text21, { dimColor: true, children: "Backups:" }),
47810
+ world.backups.slice(0, 3).map((backup) => /* @__PURE__ */ jsx21(Box21, { marginLeft: 2, children: /* @__PURE__ */ jsxs20(Text21, { dimColor: true, children: [
45953
47811
  "\u2022",
45954
47812
  " ",
45955
47813
  formatBackupTimestamp(backup.backupTimestamp ?? ""),
45956
47814
  backup.pendingSave ? " (pending)" : ` (${formatBytes2(backup.size)})`
45957
47815
  ] }) }, backup.name)),
45958
- world.backups.length > 3 && /* @__PURE__ */ jsx16(Box16, { marginLeft: 2, children: /* @__PURE__ */ jsxs15(Text16, { dimColor: true, children: [
47816
+ world.backups.length > 3 && /* @__PURE__ */ jsx21(Box21, { marginLeft: 2, children: /* @__PURE__ */ jsxs20(Text21, { dimColor: true, children: [
45959
47817
  "... and ",
45960
47818
  world.backups.length - 3,
45961
47819
  " more"
@@ -45966,30 +47824,30 @@ var Worlds = () => {
45966
47824
  world.name
45967
47825
  );
45968
47826
  }),
45969
- otherPendingWorlds.map((name) => /* @__PURE__ */ jsxs15(
45970
- Box16,
47827
+ otherPendingWorlds.map((name) => /* @__PURE__ */ jsxs20(
47828
+ Box21,
45971
47829
  {
45972
47830
  flexDirection: "column",
45973
47831
  flexShrink: 0,
45974
47832
  marginBottom: 1,
45975
47833
  children: [
45976
- /* @__PURE__ */ jsxs15(Box16, { flexShrink: 0, children: [
45977
- /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: name }),
45978
- /* @__PURE__ */ jsx16(Text16, { color: theme.warning, children: " - Not generated" })
47834
+ /* @__PURE__ */ jsxs20(Box21, { flexShrink: 0, children: [
47835
+ /* @__PURE__ */ jsx21(Text21, { dimColor: true, children: name }),
47836
+ /* @__PURE__ */ jsx21(Text21, { color: theme.warning, children: " - Not generated" })
45979
47837
  ] }),
45980
- /* @__PURE__ */ jsx16(Box16, { marginLeft: 2, flexShrink: 0, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "Set as active and start the server to generate" }) })
47838
+ /* @__PURE__ */ jsx21(Box21, { marginLeft: 2, flexShrink: 0, children: /* @__PURE__ */ jsx21(Text21, { dimColor: true, children: "Set as active and start the server to generate" }) })
45981
47839
  ]
45982
47840
  },
45983
47841
  `pending-${name}`
45984
47842
  ))
45985
47843
  ] }),
45986
- operationStatus && /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: operationStatus }) }),
45987
- serverStatus !== "offline" && /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text16, { color: theme.warning, children: "Stop the server to change or delete worlds" }) })
47844
+ operationStatus && /* @__PURE__ */ jsx21(Box21, { marginTop: 1, children: /* @__PURE__ */ jsx21(Text21, { dimColor: true, children: operationStatus }) }),
47845
+ serverStatus !== "offline" && /* @__PURE__ */ jsx21(Box21, { marginTop: 1, children: /* @__PURE__ */ jsx21(Text21, { color: theme.warning, children: "Stop the server to change or delete worlds" }) })
45988
47846
  ] });
45989
47847
  };
45990
47848
 
45991
47849
  // src/tui/App.tsx
45992
- import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
47850
+ import { jsx as jsx22, jsxs as jsxs21 } from "react/jsx-runtime";
45993
47851
  var screens = {
45994
47852
  dashboard: Dashboard,
45995
47853
  settings: Settings,
@@ -46006,11 +47864,11 @@ var App = () => {
46006
47864
  const openModal = useStore((s) => s.actions.openModal);
46007
47865
  const addLog = useStore((s) => s.actions.addLog);
46008
47866
  useConfigSync();
46009
- useEffect12(() => {
47867
+ useEffect14(() => {
46010
47868
  addLog("info", "TUI started");
46011
47869
  addLog("info", "Press 1-4 to navigate, ? for help, Q to quit");
46012
47870
  }, [addLog]);
46013
- useInput10((input, key) => {
47871
+ useInput15((input, key) => {
46014
47872
  if (modalOpen || editingField) return;
46015
47873
  if (input === "q" || input === "Q" || key.ctrl && input === "c") {
46016
47874
  addLog("info", "Shutting down...");
@@ -46019,7 +47877,7 @@ var App = () => {
46019
47877
  return;
46020
47878
  }
46021
47879
  if (input === "?") {
46022
- openModal(/* @__PURE__ */ jsx17(HelpOverlay, {}));
47880
+ openModal(/* @__PURE__ */ jsx22(HelpOverlay, {}));
46023
47881
  return;
46024
47882
  }
46025
47883
  if (input === "1") setScreen("dashboard");
@@ -46028,25 +47886,25 @@ var App = () => {
46028
47886
  if (input === "4") setScreen("console");
46029
47887
  });
46030
47888
  const ScreenComponent = screens[activeScreen] ?? Dashboard;
46031
- return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", height: "100%", width: "100%", children: [
46032
- /* @__PURE__ */ jsx17(Header, {}),
46033
- /* @__PURE__ */ jsxs16(Box17, { flexGrow: 1, flexDirection: "row", height: "100%", children: [
46034
- /* @__PURE__ */ jsx17(Menu, {}),
46035
- /* @__PURE__ */ jsx17(
46036
- Box17,
47889
+ return /* @__PURE__ */ jsxs21(Box22, { flexDirection: "column", height: "100%", width: "100%", children: [
47890
+ /* @__PURE__ */ jsx22(Header, {}),
47891
+ /* @__PURE__ */ jsxs21(Box22, { flexGrow: 1, flexDirection: "row", height: "100%", children: [
47892
+ /* @__PURE__ */ jsx22(Menu, {}),
47893
+ /* @__PURE__ */ jsx22(
47894
+ Box22,
46037
47895
  {
46038
47896
  flexGrow: 1,
46039
47897
  borderStyle: "single",
46040
47898
  borderColor: theme.muted,
46041
47899
  flexDirection: "column",
46042
47900
  height: "100%",
46043
- children: /* @__PURE__ */ jsx17(ScreenComponent, {})
47901
+ children: /* @__PURE__ */ jsx22(ScreenComponent, {})
46044
47902
  }
46045
47903
  )
46046
47904
  ] }),
46047
- /* @__PURE__ */ jsx17(LogFeed, {}),
46048
- modalOpen && modalContent && /* @__PURE__ */ jsx17(
46049
- Box17,
47905
+ /* @__PURE__ */ jsx22(LogFeed, {}),
47906
+ modalOpen && modalContent && /* @__PURE__ */ jsx22(
47907
+ Box22,
46050
47908
  {
46051
47909
  position: "absolute",
46052
47910
  flexDirection: "column",
@@ -46064,20 +47922,20 @@ var App = () => {
46064
47922
  // src/tui/components/PathInput.tsx
46065
47923
  import fs10 from "fs/promises";
46066
47924
  import path8 from "path";
46067
- import { Box as Box18, Text as Text17, useInput as useInput11 } from "ink";
46068
- import { useEffect as useEffect13, useState as useState12 } from "react";
46069
- import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
47925
+ import { Box as Box23, Text as Text22, useInput as useInput16 } from "ink";
47926
+ import { useEffect as useEffect15, useState as useState17 } from "react";
47927
+ import { jsx as jsx23, jsxs as jsxs22 } from "react/jsx-runtime";
46070
47928
 
46071
47929
  // src/tui/components/StatusBar.tsx
46072
- import { Box as Box19, Text as Text18 } from "ink";
46073
- import { jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
47930
+ import { Box as Box24, Text as Text23 } from "ink";
47931
+ import { jsx as jsx24, jsxs as jsxs23 } from "react/jsx-runtime";
46074
47932
 
46075
47933
  // src/tui/components/Toggle.tsx
46076
- import { Box as Box20, Text as Text19, useInput as useInput12 } from "ink";
46077
- import { jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
47934
+ import { Box as Box25, Text as Text24, useInput as useInput17 } from "ink";
47935
+ import { jsx as jsx25, jsxs as jsxs24 } from "react/jsx-runtime";
46078
47936
 
46079
47937
  // src/tui/hooks/useLogs.ts
46080
- import { useCallback as useCallback7, useEffect as useEffect14 } from "react";
47938
+ import { useCallback as useCallback7, useEffect as useEffect16 } from "react";
46081
47939
 
46082
47940
  // src/tui/mod.ts
46083
47941
  function launchTui() {
@@ -46085,7 +47943,7 @@ function launchTui() {
46085
47943
  }
46086
47944
 
46087
47945
  // src/mod.ts
46088
- var VERSION2 = "1.5.4";
47946
+ var VERSION2 = "1.8.0";
46089
47947
  var APP_NAME = "Land of OZ - Valheim DSM";
46090
47948
 
46091
47949
  // main.ts