@bobfrankston/msger 0.1.141 → 0.1.143

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/bbt.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "message": "https://rmf39.aaz.lt/bbt",
3
+ "url": "https://rmf39.aaz.lt/bbt",
4
+ "hash": "masto",
5
+ "size": {
6
+ "width": 600,
7
+ "height": 800
8
+ },
9
+ "pos": "100,100,1"
10
+ }
package/cli.js CHANGED
@@ -1,7 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { showMessageBox } from './shower.js';
3
- import { parseCliArgs, showHelp, showVersion, loadConfigFile, saveConfigFile } from './clihandler.js';
3
+ import packageJson from './package.json' with { type: 'json' };
4
+ import fs from 'fs';
4
5
  import path from 'path';
6
+ import { execSync } from 'child_process';
7
+ import JSON5 from 'json5';
5
8
  export default async function main() {
6
9
  const args = process.argv.slice(2);
7
10
  // No arguments - show help
@@ -10,7 +13,15 @@ export default async function main() {
10
13
  process.exit(0);
11
14
  }
12
15
  // Parse command line arguments
13
- const { options, showHelp: shouldShowHelp, showVersion: shouldShowVersion, loadFile, saveFile, noshow } = parseCliArgs(args);
16
+ let options, shouldShowHelp, shouldShowVersion, loadFile, saveFile, noshow;
17
+ try {
18
+ ({ options, showHelp: shouldShowHelp, showVersion: shouldShowVersion, loadFile, saveFile, noshow } = parseCliArgs(args));
19
+ }
20
+ catch (error) {
21
+ console.error('Error:', error.message);
22
+ console.error('\nUse -help to see usage information.');
23
+ process.exit(1);
24
+ }
14
25
  // Helper to add .json extension if no extension specified
15
26
  const ensureJsonExt = (filename) => {
16
27
  const ext = path.extname(filename);
@@ -136,6 +147,457 @@ export default async function main() {
136
147
  process.exit(1);
137
148
  }
138
149
  }
150
+ const HELP_TEXT = `
151
+ msger v${packageJson.version} - Fast, lightweight, cross-platform message box (Rust-powered)
152
+
153
+ Usage:
154
+ msger [options] [message words...]
155
+ msger -message "Your message"
156
+ echo '{"message":"text"}' | msger
157
+
158
+ Options:
159
+ -message <text> Specify message text (use quotes for multi-word)
160
+ Supports ANSI color escape sequences (e.g., \\x1b[31mRed\\x1b[0m)
161
+ -title <text> Set window title
162
+ -html <html> Display HTML formatted message (inline)
163
+ -htmlfrom <file|url> Fetch HTML from file/URL and embed in template (has buttons & msger API)
164
+ -url <url> Load URL directly in webview (no template, no buttons)
165
+ -hash <fragment> Hash fragment to append to URL (requires -url). Leading # optional.
166
+ -buttons <label...> Specify button labels (e.g., -buttons Yes No Cancel)
167
+ -ok Add OK button
168
+ -cancel Add Cancel button
169
+ -input [placeholder] Include an input field with optional placeholder text
170
+ -default <text> Default value for input field
171
+ -size <width,height> Set window size (e.g., -size 800,600)
172
+ -zoom <percent> Set zoom level as percentage (e.g., -zoom 150 for 150%)
173
+ -pos <x,y> Set window position (e.g., -pos 100,200)
174
+ -screen <number> [Windows only] Screen index for multi-monitor (0=primary, 1=second, etc.)
175
+ -icon <path> Set window icon (PNG file). Default: icon.png in current directory
176
+ -timeout <seconds> Auto-close after specified seconds
177
+ -detach Leave window open after app exits (parent process returns immediately)
178
+ -fullscreen Start window in fullscreen mode (F11 to toggle, Escape to exit)
179
+ -no-escape-closes Prevent Escape key from closing window (still exits fullscreen)
180
+ -reset Clear localStorage on startup (useful for resetting web content state)
181
+ -ontop Keep window always on top of other windows
182
+ -load <file> Load options from JSON file (supports comments)
183
+ -save <file> Save current options to JSON file
184
+ -noshow Save config without showing message box (use with -save)
185
+ -dev Open DevTools (F12) automatically on startup (for debugging)
186
+ -debug Return debug info (HTML, size) in result
187
+ -v, -version, --version Show version number
188
+ -help, -?, --help Show this help message
189
+
190
+ Examples:
191
+ msger Hello World
192
+ msger -message "Save changes?" -buttons Yes No Cancel
193
+ msger -message "Enter your name:" -input "Your name" -default "John Doe"
194
+ msger -html "<h1>Title</h1><p>Formatted content</p>"
195
+ msger -htmlfrom page.html -buttons OK Cancel
196
+ msger -htmlfrom "https://example.com" -buttons OK
197
+ msger -url "https://example.com"
198
+ msger -url "https://example.com" -hash section1
199
+ msger -url "https://example.com" -detach
200
+ msger -title "Alert" -message "Operation complete"
201
+ echo -e "\\x1b[31mError:\\x1b[0m Something failed" | msger
202
+ echo '{"message":"Test","buttons":["OK"]}' | msger
203
+
204
+ Notes:
205
+ - If no options are provided, all non-option arguments are concatenated as the message
206
+ - Message text supports ANSI color codes (automatically converted to HTML)
207
+ - Press ESC to dismiss the dialog (returns button: "dismissed")
208
+ - Press Enter to click the last (default) button
209
+ - Default button is OK if no buttons specified
210
+ - Much faster than Electron-based msgview (~50-200ms vs ~2-3s startup)
211
+
212
+ Security Note:
213
+ - msger is designed for displaying trusted, friendly content (local apps, your own HTML)
214
+ - It is NOT a secure sandbox for untrusted/hostile web content
215
+ - Use -htmlfrom and -url with trusted sources only
216
+ `;
217
+ function parseCliArgs(args) {
218
+ const cli = {
219
+ buttons: [],
220
+ input: false,
221
+ help: false
222
+ };
223
+ let i = 0;
224
+ const messageWords = [];
225
+ while (i < args.length) {
226
+ const arg = args[i];
227
+ if (arg === '-help' || arg === '--help' || arg === '-?' || arg === '/?') {
228
+ cli.help = true;
229
+ i++;
230
+ }
231
+ else if (arg === '-v' || arg === '-version' || arg === '--version') {
232
+ cli.version = true;
233
+ i++;
234
+ }
235
+ else if (arg === '-message' || arg === '--message') {
236
+ if (i + 1 >= args.length) {
237
+ throw new Error('-message requires a text argument');
238
+ }
239
+ cli.message = args[++i];
240
+ i++;
241
+ }
242
+ else if (arg === '-title' || arg === '--title') {
243
+ if (i + 1 >= args.length) {
244
+ throw new Error('-title requires a text argument');
245
+ }
246
+ cli.title = args[++i];
247
+ i++;
248
+ }
249
+ else if (arg === '-html' || arg === '--html') {
250
+ if (i + 1 >= args.length) {
251
+ throw new Error('-html requires an HTML string argument');
252
+ }
253
+ cli.html = args[++i];
254
+ i++;
255
+ }
256
+ else if (arg === '-htmlfrom' || arg === '--htmlfrom') {
257
+ if (i + 1 >= args.length) {
258
+ throw new Error('-htmlfrom requires a file path or URL argument');
259
+ }
260
+ cli.htmlFrom = args[++i];
261
+ i++;
262
+ }
263
+ else if (arg === '-url' || arg === '--url') {
264
+ if (i + 1 >= args.length) {
265
+ throw new Error('-url requires a URL argument');
266
+ }
267
+ cli.url = args[++i];
268
+ i++;
269
+ }
270
+ else if (arg === '-hash' || arg === '--hash') {
271
+ if (i + 1 >= args.length) {
272
+ throw new Error('-hash requires a fragment argument');
273
+ }
274
+ cli.hash = args[++i];
275
+ i++;
276
+ }
277
+ else if (arg === '-buttons' || arg === '--buttons') {
278
+ i++;
279
+ while (i < args.length && !args[i].startsWith('-')) {
280
+ cli.buttons.push(args[i]);
281
+ i++;
282
+ }
283
+ }
284
+ else if (arg === '-ok' || arg === '--ok') {
285
+ if (!cli.buttons.includes('OK')) {
286
+ cli.buttons.push('OK');
287
+ }
288
+ i++;
289
+ }
290
+ else if (arg === '-cancel' || arg === '--cancel') {
291
+ if (!cli.buttons.includes('Cancel')) {
292
+ cli.buttons.push('Cancel');
293
+ }
294
+ i++;
295
+ }
296
+ else if (arg === '-input' || arg === '--input') {
297
+ cli.input = true;
298
+ if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
299
+ cli.inputPlaceholder = args[++i];
300
+ }
301
+ i++;
302
+ }
303
+ else if (arg === '-default' || arg === '--default') {
304
+ if (i + 1 >= args.length) {
305
+ throw new Error('-default requires a text argument');
306
+ }
307
+ cli.defaultValue = args[++i];
308
+ i++;
309
+ }
310
+ else if (arg === '-timeout' || arg === '--timeout') {
311
+ if (i + 1 >= args.length) {
312
+ throw new Error('-timeout requires a number argument');
313
+ }
314
+ cli.timeout = parseInt(args[++i], 10);
315
+ i++;
316
+ }
317
+ else if (arg === '-detach' || arg === '--detach') {
318
+ cli.detach = true;
319
+ i++;
320
+ }
321
+ else if (arg === '-fullscreen' || arg === '--fullscreen') {
322
+ cli.fullscreen = true;
323
+ i++;
324
+ }
325
+ else if (arg === '-full' || arg === '--full') {
326
+ cli.full = true;
327
+ i++;
328
+ }
329
+ else if (arg === '-no-escape-closes' || arg === '--no-escape-closes') {
330
+ cli.escapeCloses = false;
331
+ i++;
332
+ }
333
+ else if (arg === '-reset' || arg === '--reset') {
334
+ cli.reset = true;
335
+ i++;
336
+ }
337
+ else if (arg === '-ontop' || arg === '--ontop' || arg === '-alwaysontop' || arg === '--alwaysontop') {
338
+ cli.alwaysOnTop = true;
339
+ i++;
340
+ }
341
+ else if (arg === '-size' || arg === '--size') {
342
+ if (i + 1 >= args.length) {
343
+ throw new Error('-size requires a width,height argument');
344
+ }
345
+ const size = args[++i];
346
+ const [w, h] = size.split(',').map(s => parseInt(s.trim(), 10));
347
+ if (w)
348
+ cli.width = w;
349
+ if (h)
350
+ cli.height = h;
351
+ i++;
352
+ }
353
+ else if (arg === '-zoom' || arg === '--zoom') {
354
+ if (i + 1 >= args.length) {
355
+ throw new Error('-zoom requires a percentage argument');
356
+ }
357
+ cli.zoomPercent = parseFloat(args[++i]);
358
+ i++;
359
+ }
360
+ else if (arg === '-pos' || arg === '--pos') {
361
+ if (i + 1 >= args.length) {
362
+ throw new Error('-pos requires an x,y or x,y,screen argument');
363
+ }
364
+ const pos = args[++i];
365
+ const parts = pos.split(',').map(s => parseInt(s.trim(), 10));
366
+ if (!isNaN(parts[0]))
367
+ cli.posX = parts[0];
368
+ if (!isNaN(parts[1]))
369
+ cli.posY = parts[1];
370
+ if (!isNaN(parts[2]))
371
+ cli.screen = parts[2];
372
+ i++;
373
+ }
374
+ else if (arg === '-screen' || arg === '--screen') {
375
+ if (i + 1 >= args.length) {
376
+ throw new Error('-screen requires a number argument');
377
+ }
378
+ cli.screen = parseInt(args[++i], 10);
379
+ i++;
380
+ }
381
+ else if (arg === '-icon' || arg === '--icon') {
382
+ if (i + 1 >= args.length) {
383
+ throw new Error('-icon requires a file path argument');
384
+ }
385
+ cli.icon = args[++i];
386
+ i++;
387
+ }
388
+ else if (arg === '-dev' || arg === '--dev') {
389
+ cli.dev = true;
390
+ i++;
391
+ }
392
+ else if (arg === '-debug' || arg === '--debug') {
393
+ cli.debug = true;
394
+ i++;
395
+ }
396
+ else if (arg === '-load' || arg === '--load') {
397
+ if (i + 1 >= args.length) {
398
+ throw new Error('-load requires a file path argument');
399
+ }
400
+ cli.load = args[++i];
401
+ i++;
402
+ }
403
+ else if (arg === '-save' || arg === '--save') {
404
+ const nextArg = args[i + 1];
405
+ if (nextArg && !nextArg.startsWith('-')) {
406
+ cli.save = nextArg;
407
+ i += 2;
408
+ }
409
+ else {
410
+ cli.save = true;
411
+ i++;
412
+ }
413
+ }
414
+ else if (arg === '-noshow' || arg === '--noshow') {
415
+ cli.noshow = true;
416
+ i++;
417
+ }
418
+ else if (!arg.startsWith('-')) {
419
+ messageWords.push(arg);
420
+ i++;
421
+ }
422
+ else {
423
+ console.warn(`Unknown option: ${arg}`);
424
+ i++;
425
+ }
426
+ }
427
+ if (cli.full && cli.url) {
428
+ throw new Error('-full and -url cannot be used together. Did you mean -fullscreen?');
429
+ }
430
+ let message;
431
+ let html;
432
+ let url;
433
+ if (cli.url) {
434
+ message = cli.url;
435
+ url = cli.url;
436
+ }
437
+ else if (cli.htmlFrom) {
438
+ if (cli.htmlFrom.startsWith('http://') || cli.htmlFrom.startsWith('https://')) {
439
+ try {
440
+ const fetchCommand = `node --input-type=module -e "const res = await fetch('${cli.htmlFrom}'); console.log(await res.text())"`;
441
+ const htmlContent = execSync(fetchCommand, { encoding: 'utf-8' });
442
+ message = htmlContent;
443
+ html = htmlContent;
444
+ }
445
+ catch (error) {
446
+ throw new Error(`Failed to fetch HTML from URL: ${error.message}`);
447
+ }
448
+ }
449
+ else {
450
+ const htmlPath = path.resolve(cli.htmlFrom);
451
+ if (!fs.existsSync(htmlPath)) {
452
+ throw new Error(`HTML file not found: ${htmlPath}`);
453
+ }
454
+ try {
455
+ const htmlContent = fs.readFileSync(htmlPath, 'utf-8');
456
+ message = htmlContent;
457
+ html = htmlContent;
458
+ }
459
+ catch (error) {
460
+ throw new Error(`Failed to read HTML file: ${error.message}`);
461
+ }
462
+ }
463
+ }
464
+ else if (cli.html) {
465
+ message = cli.html;
466
+ html = cli.html;
467
+ }
468
+ else if (cli.message) {
469
+ message = cli.message;
470
+ }
471
+ else if (messageWords.length > 0) {
472
+ message = messageWords.join(' ');
473
+ }
474
+ else {
475
+ message = '';
476
+ }
477
+ const options = {
478
+ title: cli.title,
479
+ message,
480
+ html,
481
+ url,
482
+ hash: cli.hash,
483
+ buttons: cli.buttons,
484
+ allowInput: cli.input,
485
+ defaultValue: cli.defaultValue,
486
+ inputPlaceholder: cli.inputPlaceholder,
487
+ timeout: cli.timeout,
488
+ detach: cli.detach,
489
+ fullscreen: cli.fullscreen,
490
+ alwaysOnTop: cli.alwaysOnTop,
491
+ zoomPercent: cli.zoomPercent,
492
+ icon: cli.icon,
493
+ dev: cli.dev,
494
+ debug: cli.debug
495
+ };
496
+ if (cli.width !== undefined || cli.height !== undefined) {
497
+ const defaultWidth = url ? 1024 : 600;
498
+ const defaultHeight = url ? 768 : 400;
499
+ options.size = {
500
+ width: cli.width !== undefined ? cli.width : defaultWidth,
501
+ height: cli.height !== undefined ? cli.height : defaultHeight,
502
+ };
503
+ }
504
+ else if (url) {
505
+ options.size = {
506
+ width: 1024,
507
+ height: 768,
508
+ };
509
+ }
510
+ if (cli.posX !== undefined && cli.posY !== undefined) {
511
+ options.pos = { x: cli.posX, y: cli.posY };
512
+ if (cli.screen !== undefined) {
513
+ options.pos.screen = cli.screen;
514
+ }
515
+ }
516
+ return {
517
+ options,
518
+ showHelp: cli.help || false,
519
+ showVersion: cli.version || false,
520
+ loadFile: cli.load,
521
+ saveFile: cli.save,
522
+ noshow: cli.noshow || false
523
+ };
524
+ }
525
+ function loadConfigFile(filePath) {
526
+ const absolutePath = path.resolve(filePath);
527
+ if (!fs.existsSync(absolutePath)) {
528
+ throw new Error(`Config file not found: ${absolutePath}`);
529
+ }
530
+ const fileContent = fs.readFileSync(absolutePath, 'utf-8');
531
+ try {
532
+ const config = JSON5.parse(fileContent);
533
+ if (config.pos && typeof config.pos === 'string') {
534
+ const parts = config.pos.split(',').map((s) => parseInt(s.trim(), 10));
535
+ const posObj = { x: parts[0], y: parts[1] };
536
+ if (!isNaN(parts[2])) {
537
+ posObj.screen = parts[2];
538
+ }
539
+ config.pos = posObj;
540
+ }
541
+ return config;
542
+ }
543
+ catch (error) {
544
+ throw new Error(`Failed to parse config file ${filePath}: ${error.message}`);
545
+ }
546
+ }
547
+ function saveConfigFile(filePath, options) {
548
+ const absolutePath = path.resolve(filePath);
549
+ const config = {};
550
+ if (options.title && options.title !== 'Message')
551
+ config.title = options.title;
552
+ if (options.message)
553
+ config.message = options.message;
554
+ if (options.html)
555
+ config.html = options.html;
556
+ if (options.url)
557
+ config.url = options.url;
558
+ if (options.hash)
559
+ config.hash = options.hash;
560
+ if (options.size)
561
+ config.size = options.size;
562
+ if (options.pos) {
563
+ let posStr = `${options.pos.x},${options.pos.y}`;
564
+ if (options.pos.screen !== undefined) {
565
+ posStr += `,${options.pos.screen}`;
566
+ }
567
+ config.pos = posStr;
568
+ }
569
+ if (options.buttons && options.buttons.length > 0 && JSON.stringify(options.buttons) !== '["OK"]') {
570
+ config.buttons = options.buttons;
571
+ }
572
+ if (options.allowInput)
573
+ config.allowInput = options.allowInput;
574
+ if (options.defaultValue)
575
+ config.defaultValue = options.defaultValue;
576
+ if (options.inputPlaceholder)
577
+ config.inputPlaceholder = options.inputPlaceholder;
578
+ if (options.timeout)
579
+ config.timeout = options.timeout;
580
+ if (options.alwaysOnTop)
581
+ config.alwaysOnTop = options.alwaysOnTop;
582
+ if (options.fullscreen)
583
+ config.fullscreen = options.fullscreen;
584
+ if (options.zoomPercent && options.zoomPercent !== 100)
585
+ config.zoomPercent = options.zoomPercent;
586
+ if (options.icon)
587
+ config.icon = options.icon;
588
+ if (options.dev)
589
+ config.dev = options.dev;
590
+ if (options.debug)
591
+ config.debug = options.debug;
592
+ const jsonContent = JSON.stringify(config, null, 4) + '\n';
593
+ fs.writeFileSync(absolutePath, jsonContent, 'utf-8');
594
+ }
595
+ function showHelp() {
596
+ console.log(HELP_TEXT);
597
+ }
598
+ function showVersion() {
599
+ console.log(packageJson.version);
600
+ }
139
601
  // Run main when file is executed (either directly or as a bin script)
140
602
  if (import.meta.main) {
141
603
  await main();