lightman-agent 1.0.5 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/agent.config.template.json +30 -30
  2. package/package.json +52 -52
  3. package/public/assets/index-CcBNCz6h.css +1 -1
  4. package/public/assets/index-D9QHMG8k.js +1 -0
  5. package/public/assets/index-H-8HDl46.js +1 -1
  6. package/public/assets/index-YodeiCia.css +1 -0
  7. package/public/assets/index-legacy-DWtNM8y7.js +41 -0
  8. package/public/assets/museum-map-CwVDA2z1.svg +4182 -0
  9. package/public/assets/polyfills-legacy-DyVYWHbW.js +4 -0
  10. package/public/index.html +7 -2
  11. package/public/templates/custom08/elements/back-button.svg +20 -0
  12. package/public/templates/custom08/elements/base-map-background.svg +37 -0
  13. package/public/templates/custom08/elements/base-map.svg +1191 -0
  14. package/public/templates/custom08/elements/gallery-1-2-3-info-panel.svg +236 -0
  15. package/public/templates/custom08/elements/gallery-4-5-6-7-info-panel.svg +266 -0
  16. package/public/templates/custom08/elements/gallery-8-9-info-panel.svg +274 -0
  17. package/public/templates/custom08/elements/gallery-labels/_nav-map-styles.css +554 -0
  18. package/public/templates/custom08/elements/gallery-labels/_styles.css +556 -0
  19. package/public/templates/custom08/elements/gallery-labels/gallery-1.svg +35 -0
  20. package/public/templates/custom08/elements/gallery-labels/gallery-2.svg +34 -0
  21. package/public/templates/custom08/elements/gallery-labels/gallery-3.svg +34 -0
  22. package/public/templates/custom08/elements/gallery-labels/gallery-4.svg +37 -0
  23. package/public/templates/custom08/elements/gallery-labels/gallery-5.svg +34 -0
  24. package/public/templates/custom08/elements/gallery-labels/gallery-6.svg +34 -0
  25. package/public/templates/custom08/elements/gallery-labels/gallery-7.svg +34 -0
  26. package/public/templates/custom08/elements/gallery-labels/gallery-8.svg +37 -0
  27. package/public/templates/custom08/elements/gallery-labels/gallery-9.svg +34 -0
  28. package/public/templates/custom08/elements/hand-hint.png +0 -0
  29. package/public/templates/custom08/elements/idle-screen-bg.svg +5 -0
  30. package/public/templates/custom08/elements/idle-screen-map.svg +627 -0
  31. package/public/templates/custom08/elements/idle-screen-text.svg +350 -0
  32. package/public/templates/custom08/elements/key-map-1.svg +986 -0
  33. package/public/templates/custom08/elements/key-map-2.svg +1018 -0
  34. package/public/templates/custom08/elements/key-map-3.svg +1019 -0
  35. package/public/templates/custom08/elements/key-map-combined.svg +1001 -0
  36. package/public/templates/custom08/elements/map-highlight-marker.svg +11 -0
  37. package/public/templates/custom08/elements/map-pin-marker.svg +15 -0
  38. package/public/templates/custom08/elements/map-teardrop-star-marker.svg +13 -0
  39. package/public/templates/custom08/elements/nav-circle-galleries-1-3.svg +21 -0
  40. package/public/templates/custom08/elements/nav-circle-galleries-4-7.svg +24 -0
  41. package/public/templates/custom08/elements/nav-circle-galleries-8-9.svg +20 -0
  42. package/public/templates/custom08/elements/section1-map.svg +1435 -0
  43. package/public/templates/custom08/elements/section2-map.svg +1724 -0
  44. package/public/templates/custom08/elements/section3-map.svg +1295 -0
  45. package/public/templates/custom08/fonts/CabinetGrotesk-Variable.ttf +0 -0
  46. package/public/templates/custom08/images/highlights/Screenshot_2026-03-05_at_7.23.12_PM.png +0 -0
  47. package/public/templates/custom08/images/highlights/Screenshot_2026-03-05_at_7.23.56_PM.png +0 -0
  48. package/public/templates/custom08/images/highlights/Screenshot_2026-03-05_at_7.24.24_PM.png +0 -0
  49. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.31.58_PM.jpg +0 -0
  50. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.32.11_PM.jpg +0 -0
  51. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.32.36_PM.jpg +0 -0
  52. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.32.48_PM.jpg +0 -0
  53. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.32.59_PM.jpg +0 -0
  54. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.33.15_PM.jpg +0 -0
  55. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.33.27_PM.jpg +0 -0
  56. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.33.34_PM.jpg +0 -0
  57. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.33.42_PM.jpg +0 -0
  58. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.33.50_PM.jpg +0 -0
  59. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.33.58_PM.jpg +0 -0
  60. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.34.04_PM.jpg +0 -0
  61. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.34.11_PM.jpg +0 -0
  62. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.34.20_PM.jpg +0 -0
  63. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.34.57_PM.jpg +0 -0
  64. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.35.03_PM.jpg +0 -0
  65. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.35.16_PM.jpg +0 -0
  66. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.35.23_PM.jpg +0 -0
  67. package/public/templates/custom08/images/highlights/prologue-highlight.png +0 -0
  68. package/scripts/guardian.ps1 +75 -75
  69. package/scripts/install-linux.sh +134 -134
  70. package/scripts/install-rpi.sh +117 -117
  71. package/scripts/install-windows.ps1 +505 -505
  72. package/scripts/launch-kiosk.vbs +101 -101
  73. package/scripts/lightman-agent.logrotate +12 -12
  74. package/scripts/lightman-agent.service +38 -38
  75. package/scripts/lightman-shell.bat +107 -107
  76. package/scripts/reinstall-windows.ps1 +26 -26
  77. package/scripts/restore-desktop.ps1 +32 -32
  78. package/scripts/setup.ps1 +116 -116
  79. package/scripts/setup.sh +115 -115
  80. package/scripts/sync-display.mjs +20 -0
  81. package/scripts/uninstall-linux.sh +50 -50
  82. package/scripts/uninstall-windows.ps1 +54 -54
  83. package/src/commands/display.ts +177 -177
  84. package/src/commands/kiosk.ts +113 -113
  85. package/src/commands/maintenance.ts +106 -106
  86. package/src/commands/network.ts +129 -129
  87. package/src/commands/power.ts +163 -163
  88. package/src/commands/rpi.ts +45 -45
  89. package/src/commands/screenshot.ts +166 -166
  90. package/src/commands/serial.ts +17 -17
  91. package/src/commands/update.ts +124 -124
  92. package/src/index.ts +652 -652
  93. package/src/lib/config.ts +69 -69
  94. package/src/lib/identity.ts +40 -40
  95. package/src/lib/logger.ts +137 -137
  96. package/src/lib/platform.ts +10 -10
  97. package/src/lib/rpi.ts +180 -180
  98. package/src/lib/screens.ts +128 -128
  99. package/src/lib/types.ts +176 -176
  100. package/src/services/commands.ts +107 -107
  101. package/src/services/health.ts +161 -161
  102. package/src/services/kiosk.ts +384 -384
  103. package/src/services/localEvents.ts +60 -60
  104. package/src/services/logForwarder.ts +72 -72
  105. package/src/services/multiScreenKiosk.ts +324 -324
  106. package/src/services/oscBridge.ts +186 -186
  107. package/src/services/powerScheduler.ts +260 -260
  108. package/src/services/provisioning.ts +120 -120
  109. package/src/services/serialBridge.ts +230 -230
  110. package/src/services/serviceLauncher.ts +183 -183
  111. package/src/services/staticServer.ts +226 -226
  112. package/src/services/updater.ts +249 -249
  113. package/src/services/watchdog.ts +310 -310
  114. package/src/services/websocket.ts +152 -152
  115. package/tsconfig.json +28 -28
@@ -1,113 +1,113 @@
1
- import { z } from 'zod';
2
- import type { CommandHandler, KioskStatus } from '../lib/types.js';
3
- import type { KioskManager } from '../services/kiosk.js';
4
- import type { MultiScreenKioskManager } from '../services/multiScreenKiosk.js';
5
- import type { Logger } from '../lib/logger.js';
6
-
7
- // --- Zod Schemas ---
8
- const HttpUrlSchema = z.string().url().refine(
9
- (val) => {
10
- try {
11
- const parsed = new URL(val);
12
- return parsed.protocol === 'http:' || parsed.protocol === 'https:';
13
- } catch {
14
- return false;
15
- }
16
- },
17
- { message: 'Must be a valid http/https URL' }
18
- );
19
-
20
- const LaunchArgsSchema = z.object({
21
- url: HttpUrlSchema.optional(),
22
- });
23
-
24
- const NavigateArgsSchema = z.object({
25
- url: HttpUrlSchema,
26
- });
27
-
28
- export function registerKioskCommands(
29
- register: (command: string, handler: CommandHandler) => void,
30
- kioskManager: KioskManager,
31
- logger: Logger
32
- ): void {
33
- register('kiosk:launch', async (args) => {
34
- const parsed = LaunchArgsSchema.safeParse(args ?? {});
35
- if (!parsed.success) {
36
- throw new Error('kiosk:launch requires a valid http/https URL');
37
- }
38
- const { url } = parsed.data;
39
- logger.info(`kiosk:launch${url ? ` → ${url}` : ' (default URL)'}`);
40
- const status: KioskStatus = await kioskManager.launch(url);
41
- return status as unknown as Record<string, unknown>;
42
- });
43
-
44
- register('kiosk:kill', async () => {
45
- logger.info('kiosk:kill');
46
- await kioskManager.kill();
47
- return { killed: true };
48
- });
49
-
50
- register('kiosk:navigate', async (args) => {
51
- const parsed = NavigateArgsSchema.safeParse(args ?? {});
52
- if (!parsed.success) {
53
- const hasUrl = typeof args?.url === 'string' && args.url.length > 0;
54
- if (!hasUrl) {
55
- throw new Error('kiosk:navigate requires args.url');
56
- }
57
- throw new Error('kiosk:navigate requires a valid http/https URL');
58
- }
59
- const { url } = parsed.data;
60
- logger.info(`kiosk:navigate → ${url}`);
61
- await kioskManager.navigate(url);
62
- return { navigated: true, url };
63
- });
64
-
65
- register('kiosk:restart', async () => {
66
- logger.info('kiosk:restart');
67
- const status: KioskStatus = await kioskManager.restart();
68
- return status as unknown as Record<string, unknown>;
69
- });
70
-
71
- register('kiosk:status', async () => {
72
- const status: KioskStatus = kioskManager.getStatus();
73
- return status as unknown as Record<string, unknown>;
74
- });
75
- }
76
-
77
- /**
78
- * Register multi-screen kiosk commands.
79
- * These allow the server to manage individual screens on multi-display devices.
80
- */
81
- export function registerMultiScreenKioskCommands(
82
- register: (command: string, handler: CommandHandler) => void,
83
- multiKiosk: MultiScreenKioskManager,
84
- getIdentity: () => { deviceId: string; apiKey: string },
85
- logger: Logger
86
- ): void {
87
- register('kiosk:multi:status', async () => {
88
- const status = multiKiosk.getStatus();
89
- return status as unknown as Record<string, unknown>;
90
- });
91
-
92
- register('kiosk:multi:navigate', async (args) => {
93
- const hardwareId = args?.hardwareId as string;
94
- const url = args?.url as string;
95
- if (!hardwareId) throw new Error('kiosk:multi:navigate requires args.hardwareId');
96
- if (!url) throw new Error('kiosk:multi:navigate requires args.url');
97
- logger.info(`kiosk:multi:navigate ${hardwareId} → ${url}`);
98
- await multiKiosk.navigateScreen(hardwareId, url, getIdentity());
99
- return { navigated: true, hardwareId, url };
100
- });
101
-
102
- register('kiosk:multi:restart', async () => {
103
- logger.info('kiosk:multi:restart');
104
- const status = await multiKiosk.restartAll(getIdentity());
105
- return status as unknown as Record<string, unknown>;
106
- });
107
-
108
- register('kiosk:multi:kill', async () => {
109
- logger.info('kiosk:multi:kill');
110
- await multiKiosk.killAll();
111
- return { killed: true };
112
- });
113
- }
1
+ import { z } from 'zod';
2
+ import type { CommandHandler, KioskStatus } from '../lib/types.js';
3
+ import type { KioskManager } from '../services/kiosk.js';
4
+ import type { MultiScreenKioskManager } from '../services/multiScreenKiosk.js';
5
+ import type { Logger } from '../lib/logger.js';
6
+
7
+ // --- Zod Schemas ---
8
+ const HttpUrlSchema = z.string().url().refine(
9
+ (val) => {
10
+ try {
11
+ const parsed = new URL(val);
12
+ return parsed.protocol === 'http:' || parsed.protocol === 'https:';
13
+ } catch {
14
+ return false;
15
+ }
16
+ },
17
+ { message: 'Must be a valid http/https URL' }
18
+ );
19
+
20
+ const LaunchArgsSchema = z.object({
21
+ url: HttpUrlSchema.optional(),
22
+ });
23
+
24
+ const NavigateArgsSchema = z.object({
25
+ url: HttpUrlSchema,
26
+ });
27
+
28
+ export function registerKioskCommands(
29
+ register: (command: string, handler: CommandHandler) => void,
30
+ kioskManager: KioskManager,
31
+ logger: Logger
32
+ ): void {
33
+ register('kiosk:launch', async (args) => {
34
+ const parsed = LaunchArgsSchema.safeParse(args ?? {});
35
+ if (!parsed.success) {
36
+ throw new Error('kiosk:launch requires a valid http/https URL');
37
+ }
38
+ const { url } = parsed.data;
39
+ logger.info(`kiosk:launch${url ? ` → ${url}` : ' (default URL)'}`);
40
+ const status: KioskStatus = await kioskManager.launch(url);
41
+ return status as unknown as Record<string, unknown>;
42
+ });
43
+
44
+ register('kiosk:kill', async () => {
45
+ logger.info('kiosk:kill');
46
+ await kioskManager.kill();
47
+ return { killed: true };
48
+ });
49
+
50
+ register('kiosk:navigate', async (args) => {
51
+ const parsed = NavigateArgsSchema.safeParse(args ?? {});
52
+ if (!parsed.success) {
53
+ const hasUrl = typeof args?.url === 'string' && args.url.length > 0;
54
+ if (!hasUrl) {
55
+ throw new Error('kiosk:navigate requires args.url');
56
+ }
57
+ throw new Error('kiosk:navigate requires a valid http/https URL');
58
+ }
59
+ const { url } = parsed.data;
60
+ logger.info(`kiosk:navigate → ${url}`);
61
+ await kioskManager.navigate(url);
62
+ return { navigated: true, url };
63
+ });
64
+
65
+ register('kiosk:restart', async () => {
66
+ logger.info('kiosk:restart');
67
+ const status: KioskStatus = await kioskManager.restart();
68
+ return status as unknown as Record<string, unknown>;
69
+ });
70
+
71
+ register('kiosk:status', async () => {
72
+ const status: KioskStatus = kioskManager.getStatus();
73
+ return status as unknown as Record<string, unknown>;
74
+ });
75
+ }
76
+
77
+ /**
78
+ * Register multi-screen kiosk commands.
79
+ * These allow the server to manage individual screens on multi-display devices.
80
+ */
81
+ export function registerMultiScreenKioskCommands(
82
+ register: (command: string, handler: CommandHandler) => void,
83
+ multiKiosk: MultiScreenKioskManager,
84
+ getIdentity: () => { deviceId: string; apiKey: string },
85
+ logger: Logger
86
+ ): void {
87
+ register('kiosk:multi:status', async () => {
88
+ const status = multiKiosk.getStatus();
89
+ return status as unknown as Record<string, unknown>;
90
+ });
91
+
92
+ register('kiosk:multi:navigate', async (args) => {
93
+ const hardwareId = args?.hardwareId as string;
94
+ const url = args?.url as string;
95
+ if (!hardwareId) throw new Error('kiosk:multi:navigate requires args.hardwareId');
96
+ if (!url) throw new Error('kiosk:multi:navigate requires args.url');
97
+ logger.info(`kiosk:multi:navigate ${hardwareId} → ${url}`);
98
+ await multiKiosk.navigateScreen(hardwareId, url, getIdentity());
99
+ return { navigated: true, hardwareId, url };
100
+ });
101
+
102
+ register('kiosk:multi:restart', async () => {
103
+ logger.info('kiosk:multi:restart');
104
+ const status = await multiKiosk.restartAll(getIdentity());
105
+ return status as unknown as Record<string, unknown>;
106
+ });
107
+
108
+ register('kiosk:multi:kill', async () => {
109
+ logger.info('kiosk:multi:kill');
110
+ await multiKiosk.killAll();
111
+ return { killed: true };
112
+ });
113
+ }
@@ -1,106 +1,106 @@
1
- import { execSync } from 'child_process';
2
- import { readFileSync, writeFileSync } from 'fs';
3
- import { resolve } from 'path';
4
- import type { CommandHandler } from '../lib/types.js';
5
- import type { Logger } from '../lib/logger.js';
6
- import type { Watchdog } from '../services/watchdog.js';
7
-
8
- export function registerMaintenanceCommands(
9
- register: (command: string, handler: CommandHandler) => void,
10
- watchdog: Watchdog,
11
- logger: Logger
12
- ): void {
13
- // maintenance:cleanup — Run disk cleanup immediately
14
- register('maintenance:cleanup', async () => {
15
- logger.info('Manual disk cleanup requested');
16
- const result = await watchdog.runDiskCleanup();
17
- return { ...result };
18
- });
19
-
20
- // maintenance:status — Get watchdog recovery stats
21
- register('maintenance:status', async () => {
22
- const stats = watchdog.getStats();
23
- const cooldowns = watchdog.getCooldowns();
24
- return { stats, cooldowns };
25
- });
26
-
27
- // maintenance:restore-desktop — Switch from shell replacement back to normal desktop
28
- // After this command, machine needs a reboot to take effect.
29
- register('maintenance:restore-desktop', async () => {
30
- if (process.platform !== 'win32') {
31
- return { restored: false, message: 'Only available on Windows' };
32
- }
33
-
34
- logger.warn('RESTORE DESKTOP: Switching Windows shell back to explorer.exe');
35
-
36
- try {
37
- // Restore HKLM shell to explorer.exe
38
- execSync(
39
- 'reg add "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon" /v Shell /d explorer.exe /f',
40
- { stdio: 'ignore', timeout: 10_000 }
41
- );
42
- // Remove HKCU shell override
43
- execSync(
44
- 'reg delete "HKCU\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon" /v Shell /f',
45
- { stdio: 'ignore', timeout: 10_000 }
46
- );
47
- } catch {
48
- // HKCU key may not exist, that's fine
49
- }
50
-
51
- // Also set shellMode: false in the config so agent resumes normal kiosk management
52
- try {
53
- const configPath = resolve(process.cwd(), 'agent.config.json');
54
- const raw = readFileSync(configPath, 'utf-8');
55
- const config = JSON.parse(raw);
56
- if (config.kiosk) {
57
- config.kiosk.shellMode = false;
58
- }
59
- writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
60
- logger.info('Config updated: shellMode set to false');
61
- } catch (err) {
62
- logger.error('Failed to update config:', err instanceof Error ? err.message : String(err));
63
- }
64
-
65
- logger.warn('Desktop restored. Machine needs a REBOOT to take effect.');
66
- return { restored: true, message: 'Desktop restored. Reboot required.' };
67
- });
68
-
69
- // maintenance:enable-shell — Switch from normal desktop to shell replacement mode
70
- // After this command, machine needs a reboot to take effect.
71
- register('maintenance:enable-shell', async () => {
72
- if (process.platform !== 'win32') {
73
- return { enabled: false, message: 'Only available on Windows' };
74
- }
75
-
76
- const shellBat = resolve(process.cwd(), 'lightman-shell.bat');
77
- logger.warn(`ENABLE SHELL: Switching Windows shell to ${shellBat}`);
78
-
79
- try {
80
- // Set shell to lightman-shell.bat
81
- execSync(
82
- `reg add "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon" /v Shell /d "\\"${shellBat}\\"" /f`,
83
- { stdio: 'ignore', timeout: 10_000 }
84
- );
85
- } catch (err) {
86
- return { enabled: false, message: `Registry update failed: ${err instanceof Error ? err.message : String(err)}` };
87
- }
88
-
89
- // Update config to shellMode: true
90
- try {
91
- const configPath = resolve(process.cwd(), 'agent.config.json');
92
- const raw = readFileSync(configPath, 'utf-8');
93
- const config = JSON.parse(raw);
94
- if (config.kiosk) {
95
- config.kiosk.shellMode = true;
96
- }
97
- writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
98
- logger.info('Config updated: shellMode set to true');
99
- } catch (err) {
100
- logger.error('Failed to update config:', err instanceof Error ? err.message : String(err));
101
- }
102
-
103
- logger.warn('Shell mode enabled. Machine needs a REBOOT to take effect.');
104
- return { enabled: true, message: 'Shell mode enabled. Reboot required.' };
105
- });
106
- }
1
+ import { execSync } from 'child_process';
2
+ import { readFileSync, writeFileSync } from 'fs';
3
+ import { resolve } from 'path';
4
+ import type { CommandHandler } from '../lib/types.js';
5
+ import type { Logger } from '../lib/logger.js';
6
+ import type { Watchdog } from '../services/watchdog.js';
7
+
8
+ export function registerMaintenanceCommands(
9
+ register: (command: string, handler: CommandHandler) => void,
10
+ watchdog: Watchdog,
11
+ logger: Logger
12
+ ): void {
13
+ // maintenance:cleanup — Run disk cleanup immediately
14
+ register('maintenance:cleanup', async () => {
15
+ logger.info('Manual disk cleanup requested');
16
+ const result = await watchdog.runDiskCleanup();
17
+ return { ...result };
18
+ });
19
+
20
+ // maintenance:status — Get watchdog recovery stats
21
+ register('maintenance:status', async () => {
22
+ const stats = watchdog.getStats();
23
+ const cooldowns = watchdog.getCooldowns();
24
+ return { stats, cooldowns };
25
+ });
26
+
27
+ // maintenance:restore-desktop — Switch from shell replacement back to normal desktop
28
+ // After this command, machine needs a reboot to take effect.
29
+ register('maintenance:restore-desktop', async () => {
30
+ if (process.platform !== 'win32') {
31
+ return { restored: false, message: 'Only available on Windows' };
32
+ }
33
+
34
+ logger.warn('RESTORE DESKTOP: Switching Windows shell back to explorer.exe');
35
+
36
+ try {
37
+ // Restore HKLM shell to explorer.exe
38
+ execSync(
39
+ 'reg add "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon" /v Shell /d explorer.exe /f',
40
+ { stdio: 'ignore', timeout: 10_000 }
41
+ );
42
+ // Remove HKCU shell override
43
+ execSync(
44
+ 'reg delete "HKCU\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon" /v Shell /f',
45
+ { stdio: 'ignore', timeout: 10_000 }
46
+ );
47
+ } catch {
48
+ // HKCU key may not exist, that's fine
49
+ }
50
+
51
+ // Also set shellMode: false in the config so agent resumes normal kiosk management
52
+ try {
53
+ const configPath = resolve(process.cwd(), 'agent.config.json');
54
+ const raw = readFileSync(configPath, 'utf-8');
55
+ const config = JSON.parse(raw);
56
+ if (config.kiosk) {
57
+ config.kiosk.shellMode = false;
58
+ }
59
+ writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
60
+ logger.info('Config updated: shellMode set to false');
61
+ } catch (err) {
62
+ logger.error('Failed to update config:', err instanceof Error ? err.message : String(err));
63
+ }
64
+
65
+ logger.warn('Desktop restored. Machine needs a REBOOT to take effect.');
66
+ return { restored: true, message: 'Desktop restored. Reboot required.' };
67
+ });
68
+
69
+ // maintenance:enable-shell — Switch from normal desktop to shell replacement mode
70
+ // After this command, machine needs a reboot to take effect.
71
+ register('maintenance:enable-shell', async () => {
72
+ if (process.platform !== 'win32') {
73
+ return { enabled: false, message: 'Only available on Windows' };
74
+ }
75
+
76
+ const shellBat = resolve(process.cwd(), 'lightman-shell.bat');
77
+ logger.warn(`ENABLE SHELL: Switching Windows shell to ${shellBat}`);
78
+
79
+ try {
80
+ // Set shell to lightman-shell.bat
81
+ execSync(
82
+ `reg add "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon" /v Shell /d "\\"${shellBat}\\"" /f`,
83
+ { stdio: 'ignore', timeout: 10_000 }
84
+ );
85
+ } catch (err) {
86
+ return { enabled: false, message: `Registry update failed: ${err instanceof Error ? err.message : String(err)}` };
87
+ }
88
+
89
+ // Update config to shellMode: true
90
+ try {
91
+ const configPath = resolve(process.cwd(), 'agent.config.json');
92
+ const raw = readFileSync(configPath, 'utf-8');
93
+ const config = JSON.parse(raw);
94
+ if (config.kiosk) {
95
+ config.kiosk.shellMode = true;
96
+ }
97
+ writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
98
+ logger.info('Config updated: shellMode set to true');
99
+ } catch (err) {
100
+ logger.error('Failed to update config:', err instanceof Error ? err.message : String(err));
101
+ }
102
+
103
+ logger.warn('Shell mode enabled. Machine needs a REBOOT to take effect.');
104
+ return { enabled: true, message: 'Shell mode enabled. Reboot required.' };
105
+ });
106
+ }