lightman-agent 1.0.18 → 1.0.21

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 (52) hide show
  1. package/agent.config.json +22 -23
  2. package/agent.config.template.json +30 -31
  3. package/bin/cms-agent.js +269 -248
  4. package/package.json +1 -1
  5. package/public/assets/index-CcBNCz6h.css +1 -1
  6. package/public/assets/index-D9QHMG8k.js +1 -1
  7. package/public/assets/index-H-8HDl46.js +1 -1
  8. package/public/assets/index-YodeiCia.css +1 -1
  9. package/public/assets/index-legacy-DWtNM8y7.js +41 -41
  10. package/public/assets/polyfills-legacy-DyVYWHbW.js +4 -4
  11. package/scripts/guardian.ps1 +50 -124
  12. package/scripts/install-windows.ps1 +60 -116
  13. package/scripts/lightman-agent.logrotate +12 -12
  14. package/scripts/lightman-agent.service +38 -38
  15. package/scripts/reinstall-windows.ps1 +26 -26
  16. package/scripts/restore-desktop.ps1 +32 -32
  17. package/scripts/setup.ps1 +17 -22
  18. package/scripts/sync-display.mjs +20 -20
  19. package/scripts/uninstall-windows.ps1 +54 -54
  20. package/src/commands/display.ts +177 -177
  21. package/src/commands/kiosk.ts +113 -113
  22. package/src/commands/maintenance.ts +106 -106
  23. package/src/commands/network.ts +129 -129
  24. package/src/commands/power.ts +163 -163
  25. package/src/commands/rpi.ts +45 -45
  26. package/src/commands/screenshot.ts +166 -166
  27. package/src/commands/serial.ts +17 -17
  28. package/src/commands/update.ts +124 -124
  29. package/src/index.ts +173 -90
  30. package/src/lib/config.ts +2 -3
  31. package/src/lib/identity.ts +40 -40
  32. package/src/lib/logger.ts +137 -137
  33. package/src/lib/platform.ts +10 -10
  34. package/src/lib/rpi.ts +180 -180
  35. package/src/lib/screenMap.ts +135 -0
  36. package/src/lib/screens.ts +128 -128
  37. package/src/lib/types.ts +176 -177
  38. package/src/services/commands.ts +107 -107
  39. package/src/services/health.ts +161 -161
  40. package/src/services/localEvents.ts +60 -60
  41. package/src/services/logForwarder.ts +72 -72
  42. package/src/services/multiScreenKiosk.ts +116 -83
  43. package/src/services/oscBridge.ts +186 -186
  44. package/src/services/powerScheduler.ts +260 -260
  45. package/src/services/provisioning.ts +120 -122
  46. package/src/services/serialBridge.ts +230 -230
  47. package/src/services/serviceLauncher.ts +183 -183
  48. package/src/services/staticServer.ts +226 -226
  49. package/src/services/updater.ts +249 -249
  50. package/src/services/watchdog.ts +310 -310
  51. package/src/services/websocket.ts +152 -152
  52. package/tsconfig.json +28 -28
package/agent.config.json CHANGED
@@ -1,23 +1,22 @@
1
- {
2
- "serverUrl": "http://192.168.10.100:3401",
3
- "deviceSlug": "a-av03",
4
- "healthIntervalMs": 60000,
5
- "logLevel": "debug",
6
- "logFile": "agent.log",
7
- "identityFile": ".lightman-identity.json",
8
- "pairingTimeoutSeconds": 900,
9
- "kiosk": {
10
- "browserPath": "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
11
- "defaultUrl": "http://localhost:3403/display/a-av03",
12
- "extraArgs": ["--start-fullscreen", "--disable-translate", "--disable-extensions", "--user-data-dir=C:\\ProgramData\\Lightman\\chrome-kiosk"],
13
- "pollIntervalMs": 10000,
14
- "maxCrashesInWindow": 10,
15
- "crashWindowMs": 300000
16
- },
17
- "powerSchedule": {
18
- "shutdownCron": "0 19 * * *",
19
- "startupCron": "0 8 * * *",
20
- "timezone": "Asia/Kolkata",
21
- "shutdownWarningSeconds": 60
22
- }
23
- }
1
+ {
2
+ "serverUrl": "http://192.168.10.100:3401",
3
+ "deviceSlug": "a-av03",
4
+ "healthIntervalMs": 60000,
5
+ "logLevel": "debug",
6
+ "logFile": "agent.log",
7
+ "identityFile": ".lightman-identity.json",
8
+ "kiosk": {
9
+ "browserPath": "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
10
+ "defaultUrl": "http://localhost:3403/display/a-av03",
11
+ "extraArgs": ["--start-fullscreen", "--disable-translate", "--disable-extensions", "--user-data-dir=C:\\ProgramData\\Lightman\\chrome-kiosk"],
12
+ "pollIntervalMs": 10000,
13
+ "maxCrashesInWindow": 10,
14
+ "crashWindowMs": 300000
15
+ },
16
+ "powerSchedule": {
17
+ "shutdownCron": "0 19 * * *",
18
+ "startupCron": "0 8 * * *",
19
+ "timezone": "Asia/Kolkata",
20
+ "shutdownWarningSeconds": 60
21
+ }
22
+ }
@@ -1,31 +1,30 @@
1
- {
2
- "serverUrl": "__SERVER_URL__",
3
- "deviceSlug": "__DEVICE_SLUG__",
4
- "healthIntervalMs": 60000,
5
- "logLevel": "info",
6
- "logFile": "agent.log",
7
- "identityFile": ".lightman-identity.json",
8
- "pairingTimeoutSeconds": __PAIRING_TIMEOUT_SECONDS__,
9
- "localServices": false,
10
- "kiosk": {
11
- "browserPath": "__BROWSER_PATH__",
12
- "defaultUrl": "__KIOSK_URL__",
13
- "extraArgs": [
14
- "--start-fullscreen",
15
- "--disable-translate",
16
- "--disable-extensions",
17
- "--autoplay-policy=no-user-gesture-required",
18
- "--user-data-dir=__CHROME_DATA_DIR__"
19
- ],
20
- "pollIntervalMs": 10000,
21
- "maxCrashesInWindow": 10,
22
- "crashWindowMs": 300000,
23
- "shellMode": false
24
- },
25
- "powerSchedule": {
26
- "shutdownCron": "0 19 * * *",
27
- "startupCron": "0 8 * * *",
28
- "timezone": "Asia/Kolkata",
29
- "shutdownWarningSeconds": 60
30
- }
31
- }
1
+ {
2
+ "serverUrl": "__SERVER_URL__",
3
+ "deviceSlug": "__DEVICE_SLUG__",
4
+ "healthIntervalMs": 60000,
5
+ "logLevel": "info",
6
+ "logFile": "agent.log",
7
+ "identityFile": ".lightman-identity.json",
8
+ "localServices": false,
9
+ "kiosk": {
10
+ "browserPath": "__BROWSER_PATH__",
11
+ "defaultUrl": "__KIOSK_URL__",
12
+ "extraArgs": [
13
+ "--start-fullscreen",
14
+ "--disable-translate",
15
+ "--disable-extensions",
16
+ "--autoplay-policy=no-user-gesture-required",
17
+ "--user-data-dir=__CHROME_DATA_DIR__"
18
+ ],
19
+ "pollIntervalMs": 10000,
20
+ "maxCrashesInWindow": 10,
21
+ "crashWindowMs": 300000,
22
+ "shellMode": false
23
+ },
24
+ "powerSchedule": {
25
+ "shutdownCron": "0 19 * * *",
26
+ "startupCron": "0 8 * * *",
27
+ "timezone": "Asia/Kolkata",
28
+ "shutdownWarningSeconds": 60
29
+ }
30
+ }
package/bin/cms-agent.js CHANGED
@@ -1,15 +1,16 @@
1
- #!/usr/bin/env node
2
-
3
- import { existsSync, readFileSync } from 'fs';
4
- import { networkInterfaces } from 'os';
5
- import { resolve, dirname } from 'path';
6
- import { fileURLToPath } from 'url';
7
- import { spawnSync } from 'child_process';
8
- import { createInterface } from 'readline/promises';
9
- import { stdin as input, stdout as output, cwd, platform, exit } from 'process';
10
-
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync, readFileSync } from 'fs';
4
+ import { networkInterfaces } from 'os';
5
+ import { resolve, dirname } from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ import { spawnSync } from 'child_process';
8
+ import { createInterface } from 'readline/promises';
9
+ import { stdin as input, stdout as output, cwd, platform, exit } from 'process';
10
+
11
11
  const DEFAULT_SERVER = 'http://192.168.10.100:3401';
12
12
  const INSTALL_CONFIG_PATH = 'C:\\Program Files\\Lightman\\Agent\\agent.config.json';
13
+ const INSTALL_PACKAGE_PATH = 'C:\\Program Files\\Lightman\\Agent\\package.json';
13
14
 
14
15
  function printUsage() {
15
16
  console.log(`
@@ -19,6 +20,7 @@ Commands:
19
20
  install Prompt slug and server IP, install agent with ShellReplace, reboot
20
21
  setup Alias of install
21
22
  update Reinstall/update using installed config, reboot
23
+ version Show CLI package version and installed agent version
22
24
 
23
25
  Options:
24
26
  --slug <value> Device slug (example: C-AV01)
@@ -26,242 +28,256 @@ Options:
26
28
  --timezone <tz> Timezone override (default: Asia/Kolkata)
27
29
  --pair-timeout <s> Wait time for pairing in seconds (default: 900, 0 = no timeout)
28
30
  --no-restart Skip reboot after successful install/update
31
+ -v, --version Show version
29
32
  -h, --help Show help
30
33
  `);
31
34
  }
32
-
33
- function parseArgs(argv) {
34
- const args = {};
35
- for (let i = 0; i < argv.length; i += 1) {
36
- const part = argv[i];
37
- if (!part.startsWith('-')) {
38
- continue;
39
- }
40
- if (part === '--no-restart') {
41
- args.noRestart = true;
42
- continue;
43
- }
44
- if (part === '--help' || part === '-h') {
45
- args.help = true;
46
- continue;
47
- }
48
- const next = argv[i + 1];
49
- if (!next || next.startsWith('-')) {
50
- continue;
51
- }
52
- if (part === '--slug') {
53
- args.slug = next.trim();
54
- i += 1;
55
- } else if (part === '--server') {
56
- args.server = next.trim();
57
- i += 1;
58
- } else if (part === '--timezone') {
59
- args.timezone = next.trim();
60
- i += 1;
61
- } else if (part === '--pair-timeout') {
62
- args.pairTimeout = next.trim();
63
- i += 1;
64
- }
65
- }
66
- return args;
67
- }
68
-
69
- function safeReadJson(path) {
70
- try {
71
- if (!existsSync(path)) return null;
72
- return JSON.parse(readFileSync(path, 'utf8'));
73
- } catch {
74
- return null;
75
- }
76
- }
77
-
78
- function isValidSlug(slug) {
79
- return /^[A-Za-z0-9][A-Za-z0-9-]{0,62}$/.test(slug);
80
- }
81
-
82
- function normalizeServer(server) {
83
- const value = (server || '').trim();
84
- if (!value) {
85
- return '';
86
- }
87
-
88
- const hasProtocol = /^[a-z]+:\/\//i.test(value);
89
- const candidate = hasProtocol ? value : `http://${value}`;
90
-
91
- let parsed;
92
- try {
93
- parsed = new URL(candidate);
94
- } catch {
95
- throw new Error('Invalid server IP/URL. Use an IP like 192.168.10.100 or a URL like http://192.168.10.100:3401.');
96
- }
97
-
98
- if (!parsed.port) {
99
- parsed.port = '3401';
100
- }
101
-
102
- if (parsed.pathname === '/') {
103
- parsed.pathname = '';
104
- }
105
-
106
- return parsed.toString().replace(/\/$/, '');
107
- }
108
-
109
- function collectMacAddresses() {
110
- const nets = networkInterfaces();
111
- const macs = new Set();
112
- for (const netName of Object.keys(nets)) {
113
- const ifaceList = nets[netName] || [];
114
- for (const iface of ifaceList) {
115
- const mac = (iface.mac || '').trim().toUpperCase();
116
- if (!iface.internal && mac && mac !== '00:00:00:00:00:00') {
117
- macs.add(`${netName}: ${mac}`);
118
- }
119
- }
120
- }
121
- return [...macs];
122
- }
123
-
124
- function isWindowsAdmin() {
125
- const cmd = '[bool]([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)';
126
- const result = spawnSync('powershell.exe', ['-NoProfile', '-Command', cmd], { encoding: 'utf8' });
127
- return result.status === 0 && result.stdout.trim().toLowerCase() === 'true';
128
- }
129
-
130
- function runOrFail(command, args) {
131
- const result = spawnSync(command, args, { stdio: 'inherit', shell: false });
132
- if (result.error) {
133
- throw result.error;
134
- }
135
- if (result.status !== 0) {
136
- throw new Error(`Command failed with exit code ${result.status}: ${command} ${args.join(' ')}`);
137
- }
138
- }
139
-
140
- async function promptSlug(defaultSlug) {
141
- const rl = createInterface({ input, output });
142
- try {
143
- while (true) {
144
- const prompt = defaultSlug
145
- ? `Enter device slug [${defaultSlug}]: `
146
- : 'Enter device slug (example C-AV01): ';
147
- const answer = (await rl.question(prompt)).trim();
148
- const slug = answer || defaultSlug || '';
149
- if (isValidSlug(slug)) {
150
- return slug;
151
- }
152
- console.error('Invalid slug. Use letters, numbers, and hyphens only.');
153
- }
154
- } finally {
155
- rl.close();
156
- }
157
- }
158
-
159
- async function promptServer(defaultServer) {
160
- const rl = createInterface({ input, output });
161
- try {
162
- while (true) {
163
- const prompt = defaultServer
164
- ? `Enter server IP or URL [${defaultServer}]: `
165
- : 'Enter server IP or URL (example 192.168.10.100 or http://192.168.10.100:3401): ';
166
- const answer = (await rl.question(prompt)).trim();
167
- const server = answer || defaultServer || '';
168
-
169
- try {
170
- return normalizeServer(server);
171
- } catch (error) {
172
- console.error(error instanceof Error ? error.message : String(error));
173
- }
174
- }
175
- } finally {
176
- rl.close();
177
- }
178
- }
179
-
35
+
36
+ function parseArgs(argv) {
37
+ const args = {};
38
+ for (let i = 0; i < argv.length; i += 1) {
39
+ const part = argv[i];
40
+ if (!part.startsWith('-')) {
41
+ continue;
42
+ }
43
+ if (part === '--no-restart') {
44
+ args.noRestart = true;
45
+ continue;
46
+ }
47
+ if (part === '--help' || part === '-h') {
48
+ args.help = true;
49
+ continue;
50
+ }
51
+ const next = argv[i + 1];
52
+ if (!next || next.startsWith('-')) {
53
+ continue;
54
+ }
55
+ if (part === '--slug') {
56
+ args.slug = next.trim();
57
+ i += 1;
58
+ } else if (part === '--server') {
59
+ args.server = next.trim();
60
+ i += 1;
61
+ } else if (part === '--timezone') {
62
+ args.timezone = next.trim();
63
+ i += 1;
64
+ } else if (part === '--pair-timeout') {
65
+ args.pairTimeout = next.trim();
66
+ i += 1;
67
+ }
68
+ }
69
+ return args;
70
+ }
71
+
72
+ function safeReadJson(path) {
73
+ try {
74
+ if (!existsSync(path)) return null;
75
+ return JSON.parse(readFileSync(path, 'utf8'));
76
+ } catch {
77
+ return null;
78
+ }
79
+ }
80
+
81
+ function isValidSlug(slug) {
82
+ return /^[A-Za-z0-9][A-Za-z0-9-]{0,62}$/.test(slug);
83
+ }
84
+
85
+ function normalizeServer(server) {
86
+ const value = (server || '').trim();
87
+ if (!value) {
88
+ return '';
89
+ }
90
+
91
+ const hasProtocol = /^[a-z]+:\/\//i.test(value);
92
+ const candidate = hasProtocol ? value : `http://${value}`;
93
+
94
+ let parsed;
95
+ try {
96
+ parsed = new URL(candidate);
97
+ } catch {
98
+ throw new Error('Invalid server IP/URL. Use an IP like 192.168.10.100 or a URL like http://192.168.10.100:3401.');
99
+ }
100
+
101
+ if (!parsed.port) {
102
+ parsed.port = '3401';
103
+ }
104
+
105
+ if (parsed.pathname === '/') {
106
+ parsed.pathname = '';
107
+ }
108
+
109
+ return parsed.toString().replace(/\/$/, '');
110
+ }
111
+
112
+ function collectMacAddresses() {
113
+ const nets = networkInterfaces();
114
+ const macs = new Set();
115
+ for (const netName of Object.keys(nets)) {
116
+ const ifaceList = nets[netName] || [];
117
+ for (const iface of ifaceList) {
118
+ const mac = (iface.mac || '').trim().toUpperCase();
119
+ if (!iface.internal && mac && mac !== '00:00:00:00:00:00') {
120
+ macs.add(`${netName}: ${mac}`);
121
+ }
122
+ }
123
+ }
124
+ return [...macs];
125
+ }
126
+
127
+ function isWindowsAdmin() {
128
+ const cmd = '[bool]([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)';
129
+ const result = spawnSync('powershell.exe', ['-NoProfile', '-Command', cmd], { encoding: 'utf8' });
130
+ return result.status === 0 && result.stdout.trim().toLowerCase() === 'true';
131
+ }
132
+
133
+ function runOrFail(command, args) {
134
+ const result = spawnSync(command, args, { stdio: 'inherit', shell: false });
135
+ if (result.error) {
136
+ throw result.error;
137
+ }
138
+ if (result.status !== 0) {
139
+ throw new Error(`Command failed with exit code ${result.status}: ${command} ${args.join(' ')}`);
140
+ }
141
+ }
142
+
143
+ async function promptSlug(defaultSlug) {
144
+ const rl = createInterface({ input, output });
145
+ try {
146
+ while (true) {
147
+ const prompt = defaultSlug
148
+ ? `Enter device slug [${defaultSlug}]: `
149
+ : 'Enter device slug (example C-AV01): ';
150
+ const answer = (await rl.question(prompt)).trim();
151
+ const slug = answer || defaultSlug || '';
152
+ if (isValidSlug(slug)) {
153
+ return slug;
154
+ }
155
+ console.error('Invalid slug. Use letters, numbers, and hyphens only.');
156
+ }
157
+ } finally {
158
+ rl.close();
159
+ }
160
+ }
161
+
162
+ async function promptServer(defaultServer) {
163
+ const rl = createInterface({ input, output });
164
+ try {
165
+ while (true) {
166
+ const prompt = defaultServer
167
+ ? `Enter server IP or URL [${defaultServer}]: `
168
+ : 'Enter server IP or URL (example 192.168.10.100 or http://192.168.10.100:3401): ';
169
+ const answer = (await rl.question(prompt)).trim();
170
+ const server = answer || defaultServer || '';
171
+
172
+ try {
173
+ return normalizeServer(server);
174
+ } catch (error) {
175
+ console.error(error instanceof Error ? error.message : String(error));
176
+ }
177
+ }
178
+ } finally {
179
+ rl.close();
180
+ }
181
+ }
182
+
180
183
  function resolveInstallScript() {
181
184
  const here = dirname(fileURLToPath(import.meta.url));
182
185
  const packaged = resolve(here, '../scripts/install-windows.ps1');
183
- const localRepo = resolve(cwd(), 'scripts/install-windows.ps1');
184
- if (existsSync(packaged)) return packaged;
185
- if (existsSync(localRepo)) return localRepo;
186
+ const localRepo = resolve(cwd(), 'scripts/install-windows.ps1');
187
+ if (existsSync(packaged)) return packaged;
188
+ if (existsSync(localRepo)) return localRepo;
186
189
  throw new Error('install-windows.ps1 not found. Expected in package scripts/ or current folder scripts/.');
187
190
  }
188
191
 
189
- function installUsingPowerShell({ scriptPath, slug, server, timezone, pairingTimeoutSeconds, noRestart }) {
190
- console.log(`powershell -ExecutionPolicy Bypass -File scripts\\install-windows.ps1 -Slug "${slug}" -Server "${server}" -ShellReplace`);
191
- const args = ['-ExecutionPolicy', 'Bypass', '-File', scriptPath, '-Slug', slug, '-Server', server, '-ShellReplace'];
192
- if (timezone) {
193
- args.push('-Timezone', timezone);
194
- }
195
- if (Number.isInteger(pairingTimeoutSeconds) && pairingTimeoutSeconds >= 0) {
196
- args.push('-PairingTimeoutSeconds', String(pairingTimeoutSeconds));
197
- }
198
- runOrFail('powershell.exe', args);
199
-
200
- if (!noRestart) {
201
- console.log('Installation completed. Rebooting now...');
202
- runOrFail('shutdown.exe', ['/r', '/t', '0', '/c', 'CMS Agent installation complete']);
203
- }
204
- }
205
-
206
- async function runInstall(opts) {
207
- if (platform !== 'win32') {
208
- throw new Error('cms-agent install is currently supported on Windows only.');
209
- }
210
- if (!isWindowsAdmin()) {
211
- throw new Error('Please run this command from an Administrator PowerShell or CMD.');
212
- }
213
-
214
- const localConfig = safeReadJson(resolve(cwd(), 'agent.config.json')) || {};
215
- const installedConfig = safeReadJson(INSTALL_CONFIG_PATH) || {};
216
- const defaultSlug = opts.slug || localConfig.deviceSlug || installedConfig.deviceSlug || '';
217
- const defaultServer = localConfig.serverUrl || installedConfig.serverUrl || DEFAULT_SERVER;
218
- const timezone = opts.timezone || localConfig?.powerSchedule?.timezone || installedConfig?.powerSchedule?.timezone || 'Asia/Kolkata';
219
- const pairingTimeoutSeconds = Number.isFinite(Number(opts.pairTimeout)) ? Number.parseInt(String(opts.pairTimeout), 10) : 900;
220
- const noRestart = Boolean(opts.noRestart);
221
-
222
- console.log('Detected MAC addresses:');
223
- const macs = collectMacAddresses();
224
- if (macs.length === 0) {
225
- console.log(' (no active external interface found)');
226
- } else {
227
- for (const item of macs) {
228
- console.log(` ${item}`);
229
- }
230
- }
231
- console.log('');
232
-
233
- const slug = opts.slug || await promptSlug(defaultSlug);
234
- const server = opts.server ? normalizeServer(opts.server) : await promptServer(defaultServer);
235
- const scriptPath = resolveInstallScript();
236
-
237
- console.log(`Installing with slug=${slug}, server=${server}, shellReplace=true`);
238
- installUsingPowerShell({ scriptPath, slug, server, timezone, pairingTimeoutSeconds, noRestart });
239
- }
240
-
241
- async function runUpdate(opts) {
242
- if (platform !== 'win32') {
243
- throw new Error('cms-agent update is currently supported on Windows only.');
244
- }
245
- if (!isWindowsAdmin()) {
246
- throw new Error('Please run this command from an Administrator PowerShell or CMD.');
247
- }
248
-
249
- const installedConfig = safeReadJson(INSTALL_CONFIG_PATH) || {};
250
- const slug = opts.slug || installedConfig.deviceSlug;
251
- const server = normalizeServer(opts.server || installedConfig.serverUrl || DEFAULT_SERVER);
252
- const timezone = opts.timezone || installedConfig?.powerSchedule?.timezone || 'Asia/Kolkata';
253
- const pairingTimeoutSeconds = Number.isFinite(Number(opts.pairTimeout)) ? Number.parseInt(String(opts.pairTimeout), 10) : 900;
254
- const noRestart = Boolean(opts.noRestart);
255
- const scriptPath = resolveInstallScript();
192
+ function runVersion() {
193
+ const here = dirname(fileURLToPath(import.meta.url));
194
+ const bundledPkg = safeReadJson(resolve(here, '../package.json')) || {};
195
+ const installedPkg = safeReadJson(INSTALL_PACKAGE_PATH) || {};
256
196
 
257
- if (!slug) {
258
- throw new Error('No installed slug found. Use cms-agent install first or pass --slug.');
259
- }
197
+ const cliVersion = typeof bundledPkg.version === 'string' ? bundledPkg.version : 'unknown';
198
+ const installedVersion = typeof installedPkg.version === 'string' ? installedPkg.version : 'not-installed';
260
199
 
261
- console.log(`Updating with slug=${slug}, server=${server}, shellReplace=true`);
262
- installUsingPowerShell({ scriptPath, slug, server, timezone, pairingTimeoutSeconds, noRestart });
200
+ console.log(`lightman-agent cli: ${cliVersion}`);
201
+ console.log(`lightman-agent installed: ${installedVersion}`);
202
+ console.log(`node: ${process.version}`);
263
203
  }
264
-
204
+
205
+ function installUsingPowerShell({ scriptPath, slug, server, timezone, pairingTimeoutSeconds, noRestart }) {
206
+ console.log(`powershell -ExecutionPolicy Bypass -File scripts\\install-windows.ps1 -Slug "${slug}" -Server "${server}" -ShellReplace`);
207
+ const args = ['-ExecutionPolicy', 'Bypass', '-File', scriptPath, '-Slug', slug, '-Server', server, '-ShellReplace'];
208
+ if (timezone) {
209
+ args.push('-Timezone', timezone);
210
+ }
211
+ if (Number.isInteger(pairingTimeoutSeconds) && pairingTimeoutSeconds >= 0) {
212
+ args.push('-PairingTimeoutSeconds', String(pairingTimeoutSeconds));
213
+ }
214
+ runOrFail('powershell.exe', args);
215
+
216
+ if (!noRestart) {
217
+ console.log('Installation completed. Rebooting now...');
218
+ runOrFail('shutdown.exe', ['/r', '/t', '0', '/c', 'CMS Agent installation complete']);
219
+ }
220
+ }
221
+
222
+ async function runInstall(opts) {
223
+ if (platform !== 'win32') {
224
+ throw new Error('cms-agent install is currently supported on Windows only.');
225
+ }
226
+ if (!isWindowsAdmin()) {
227
+ throw new Error('Please run this command from an Administrator PowerShell or CMD.');
228
+ }
229
+
230
+ const localConfig = safeReadJson(resolve(cwd(), 'agent.config.json')) || {};
231
+ const installedConfig = safeReadJson(INSTALL_CONFIG_PATH) || {};
232
+ const defaultSlug = opts.slug || localConfig.deviceSlug || installedConfig.deviceSlug || '';
233
+ const defaultServer = localConfig.serverUrl || installedConfig.serverUrl || DEFAULT_SERVER;
234
+ const timezone = opts.timezone || localConfig?.powerSchedule?.timezone || installedConfig?.powerSchedule?.timezone || 'Asia/Kolkata';
235
+ const pairingTimeoutSeconds = Number.isFinite(Number(opts.pairTimeout)) ? Number.parseInt(String(opts.pairTimeout), 10) : 900;
236
+ const noRestart = Boolean(opts.noRestart);
237
+
238
+ console.log('Detected MAC addresses:');
239
+ const macs = collectMacAddresses();
240
+ if (macs.length === 0) {
241
+ console.log(' (no active external interface found)');
242
+ } else {
243
+ for (const item of macs) {
244
+ console.log(` ${item}`);
245
+ }
246
+ }
247
+ console.log('');
248
+
249
+ const slug = opts.slug || await promptSlug(defaultSlug);
250
+ const server = opts.server ? normalizeServer(opts.server) : await promptServer(defaultServer);
251
+ const scriptPath = resolveInstallScript();
252
+
253
+ console.log(`Installing with slug=${slug}, server=${server}, shellReplace=true`);
254
+ installUsingPowerShell({ scriptPath, slug, server, timezone, pairingTimeoutSeconds, noRestart });
255
+ }
256
+
257
+ async function runUpdate(opts) {
258
+ if (platform !== 'win32') {
259
+ throw new Error('cms-agent update is currently supported on Windows only.');
260
+ }
261
+ if (!isWindowsAdmin()) {
262
+ throw new Error('Please run this command from an Administrator PowerShell or CMD.');
263
+ }
264
+
265
+ const installedConfig = safeReadJson(INSTALL_CONFIG_PATH) || {};
266
+ const slug = opts.slug || installedConfig.deviceSlug;
267
+ const server = normalizeServer(opts.server || installedConfig.serverUrl || DEFAULT_SERVER);
268
+ const timezone = opts.timezone || installedConfig?.powerSchedule?.timezone || 'Asia/Kolkata';
269
+ const pairingTimeoutSeconds = Number.isFinite(Number(opts.pairTimeout)) ? Number.parseInt(String(opts.pairTimeout), 10) : 900;
270
+ const noRestart = Boolean(opts.noRestart);
271
+ const scriptPath = resolveInstallScript();
272
+
273
+ if (!slug) {
274
+ throw new Error('No installed slug found. Use cms-agent install first or pass --slug.');
275
+ }
276
+
277
+ console.log(`Updating with slug=${slug}, server=${server}, shellReplace=true`);
278
+ installUsingPowerShell({ scriptPath, slug, server, timezone, pairingTimeoutSeconds, noRestart });
279
+ }
280
+
265
281
  async function main() {
266
282
  const [, , commandRaw, ...rest] = process.argv;
267
283
  const command = commandRaw || '';
@@ -272,20 +288,25 @@ async function main() {
272
288
  return;
273
289
  }
274
290
 
275
- if (command === 'install' || command === 'setup') {
276
- await runInstall(opts);
277
- return;
278
- }
279
- if (command === 'update') {
280
- await runUpdate(opts);
291
+ if (command === 'version' || command === '--version' || command === '-v') {
292
+ runVersion();
281
293
  return;
282
294
  }
283
295
 
284
- printUsage();
285
- throw new Error(`Unknown command: ${command}`);
286
- }
287
-
288
- main().catch((err) => {
289
- console.error(err instanceof Error ? err.message : String(err));
290
- exit(1);
291
- });
296
+ if (command === 'install' || command === 'setup') {
297
+ await runInstall(opts);
298
+ return;
299
+ }
300
+ if (command === 'update') {
301
+ await runUpdate(opts);
302
+ return;
303
+ }
304
+
305
+ printUsage();
306
+ throw new Error(`Unknown command: ${command}`);
307
+ }
308
+
309
+ main().catch((err) => {
310
+ console.error(err instanceof Error ? err.message : String(err));
311
+ exit(1);
312
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lightman-agent",
3
- "version": "1.0.18",
3
+ "version": "1.0.21",
4
4
  "description": "LIGHTMAN Agent - System-level daemon for museum display machines",
5
5
  "private": false,
6
6
  "type": "module",