@bobfrankston/msger 0.1.170 → 0.1.173
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/cli-old.d.ts +2 -0
- package/cli-old.js +645 -0
- package/cli.d.ts +4 -1
- package/cli.js +11 -544
- package/index.js +2 -3
- package/package.json +2 -2
- package/shower.d.ts +1 -0
package/cli-old.d.ts
ADDED
package/cli-old.js
ADDED
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
//
|
|
3
|
+
// TODO: Migrate to use @bobfrankston/msgcommon for argument parsing
|
|
4
|
+
// Example:
|
|
5
|
+
// import { parseCommonArgs } from '@bobfrankston/msgcommon';
|
|
6
|
+
// const { options, messageWords, showHelp, showVersion } = parseCommonArgs(args);
|
|
7
|
+
//
|
|
8
|
+
import { showMessageBox } from './shower.js';
|
|
9
|
+
import packageJson from './package.json' with { type: 'json' };
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import { execSync } from 'child_process';
|
|
13
|
+
import JSON5 from 'json5';
|
|
14
|
+
// Uncomment when ready to migrate:
|
|
15
|
+
// import { parseCommonArgs } from '@bobfrankston/msgcommon';
|
|
16
|
+
export default async function main() {
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
// No arguments - show help
|
|
19
|
+
if (args.length === 0) {
|
|
20
|
+
showHelp();
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
// Parse command line arguments
|
|
24
|
+
let options, shouldShowHelp, shouldShowVersion, loadFile, saveFile, noshow, shouldPin;
|
|
25
|
+
try {
|
|
26
|
+
({ options, showHelp: shouldShowHelp, showVersion: shouldShowVersion, loadFile, saveFile, noshow, pin: shouldPin } = parseCliArgs(args));
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
console.error('Error:', error.message);
|
|
30
|
+
console.error('\nUse -help to see usage information.');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
// Helper to add .json extension if no extension specified
|
|
34
|
+
const ensureJsonExt = (filename) => {
|
|
35
|
+
const ext = path.extname(filename);
|
|
36
|
+
return ext ? filename : `${filename}.json`;
|
|
37
|
+
};
|
|
38
|
+
// Handle -pin: create pinnable shortcut (Windows only)
|
|
39
|
+
if (shouldPin) {
|
|
40
|
+
console.error('Error: -pin is not yet implemented');
|
|
41
|
+
console.error('Taskbar pinning requires packaged .exe files, not Node.js scripts');
|
|
42
|
+
console.error('This feature will be available once msger is packaged as a standalone executable');
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
// Load config file if specified
|
|
46
|
+
let finalOptions = options;
|
|
47
|
+
if (loadFile) {
|
|
48
|
+
try {
|
|
49
|
+
const loadedConfig = loadConfigFile(ensureJsonExt(loadFile));
|
|
50
|
+
finalOptions = { ...loadedConfig };
|
|
51
|
+
if (options.title)
|
|
52
|
+
finalOptions.title = options.title;
|
|
53
|
+
if (options.message)
|
|
54
|
+
finalOptions.message = options.message;
|
|
55
|
+
if (options.html)
|
|
56
|
+
finalOptions.html = options.html;
|
|
57
|
+
if (options.url)
|
|
58
|
+
finalOptions.url = options.url;
|
|
59
|
+
if (options.hash)
|
|
60
|
+
finalOptions.hash = options.hash;
|
|
61
|
+
if (options.size)
|
|
62
|
+
finalOptions.size = options.size;
|
|
63
|
+
if (options.pos)
|
|
64
|
+
finalOptions.pos = options.pos;
|
|
65
|
+
if (options.buttons && options.buttons.length > 0)
|
|
66
|
+
finalOptions.buttons = options.buttons;
|
|
67
|
+
if (options.allowInput !== undefined)
|
|
68
|
+
finalOptions.allowInput = options.allowInput;
|
|
69
|
+
if (options.defaultValue)
|
|
70
|
+
finalOptions.defaultValue = options.defaultValue;
|
|
71
|
+
if (options.inputPlaceholder)
|
|
72
|
+
finalOptions.inputPlaceholder = options.inputPlaceholder;
|
|
73
|
+
if (options.timeout !== undefined)
|
|
74
|
+
finalOptions.timeout = options.timeout;
|
|
75
|
+
if (options.alwaysOnTop !== undefined)
|
|
76
|
+
finalOptions.alwaysOnTop = options.alwaysOnTop;
|
|
77
|
+
if (options.fullscreen !== undefined)
|
|
78
|
+
finalOptions.fullscreen = options.fullscreen;
|
|
79
|
+
if (options.zoom !== undefined)
|
|
80
|
+
finalOptions.zoom = options.zoom;
|
|
81
|
+
if (options.icon)
|
|
82
|
+
finalOptions.icon = options.icon;
|
|
83
|
+
if (options.dev !== undefined)
|
|
84
|
+
finalOptions.dev = options.dev;
|
|
85
|
+
if (options.debug !== undefined)
|
|
86
|
+
finalOptions.debug = options.debug;
|
|
87
|
+
if (options.detach !== undefined)
|
|
88
|
+
finalOptions.detach = options.detach;
|
|
89
|
+
// Generate AppUserModelID for pinning support
|
|
90
|
+
if (process.platform === 'win32') {
|
|
91
|
+
try {
|
|
92
|
+
const { getAppUserModelIdForConfig } = await import('@bobfrankston/msgcommon/pinning');
|
|
93
|
+
const appId = getAppUserModelIdForConfig('msger', path.resolve(ensureJsonExt(loadFile)));
|
|
94
|
+
if (appId) {
|
|
95
|
+
finalOptions.appUserModelId = appId;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
// Silently ignore if msgcommon is not available
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
console.error('Error loading config:', error.message);
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (!finalOptions.buttons || finalOptions.buttons.length === 0) {
|
|
109
|
+
finalOptions.buttons = ['OK'];
|
|
110
|
+
}
|
|
111
|
+
// Help always takes precedence and exits
|
|
112
|
+
if (shouldShowHelp) {
|
|
113
|
+
showHelp();
|
|
114
|
+
process.exit(0);
|
|
115
|
+
}
|
|
116
|
+
// Version only exits if there's nothing else meaningful to do
|
|
117
|
+
// (no actual message/url/html content was provided - the default message doesn't count)
|
|
118
|
+
const hasRealContent = args.some(arg => !arg.startsWith('-') ||
|
|
119
|
+
arg === '-message' ||
|
|
120
|
+
arg === '-html' ||
|
|
121
|
+
arg === '-url');
|
|
122
|
+
if (shouldShowVersion && !hasRealContent) {
|
|
123
|
+
showVersion();
|
|
124
|
+
process.exit(0);
|
|
125
|
+
}
|
|
126
|
+
// If version was requested along with other options, add it to title
|
|
127
|
+
if (shouldShowVersion && hasRealContent) {
|
|
128
|
+
finalOptions.showVersion = true;
|
|
129
|
+
}
|
|
130
|
+
if (saveFile) {
|
|
131
|
+
try {
|
|
132
|
+
let saveFilePath;
|
|
133
|
+
if (saveFile === true) {
|
|
134
|
+
if (!loadFile) {
|
|
135
|
+
console.error('Error: -save without filename requires -load to be specified');
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
saveFilePath = ensureJsonExt(loadFile);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
saveFilePath = ensureJsonExt(saveFile);
|
|
142
|
+
}
|
|
143
|
+
const toSave = saveFile === true ? finalOptions : options;
|
|
144
|
+
saveConfigFile(saveFilePath, toSave);
|
|
145
|
+
console.log(`Configuration saved to: ${saveFilePath}`);
|
|
146
|
+
if (noshow) {
|
|
147
|
+
process.exit(0);
|
|
148
|
+
}
|
|
149
|
+
if (!finalOptions.message && !finalOptions.url && !finalOptions.html) {
|
|
150
|
+
process.exit(0);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
console.error('Error saving config:', error.message);
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// If no actual message/content was provided, show help
|
|
159
|
+
if (!finalOptions.message && !finalOptions.url && !finalOptions.html) {
|
|
160
|
+
showHelp();
|
|
161
|
+
process.exit(0);
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
const result = await showMessageBox(finalOptions);
|
|
165
|
+
// If detached, exit immediately without printing result
|
|
166
|
+
if (finalOptions.detach) {
|
|
167
|
+
process.exit(0);
|
|
168
|
+
}
|
|
169
|
+
console.log(JSON.stringify(result, null, 2));
|
|
170
|
+
// Exit with code based on result
|
|
171
|
+
if (result.dismissed || result.closed) {
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
console.error('Error:', error.message);
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const HELP_TEXT = `
|
|
181
|
+
msger v${packageJson.version} - Fast, lightweight, cross-platform message box (Rust-powered)
|
|
182
|
+
|
|
183
|
+
Usage:
|
|
184
|
+
msger [options] [message words...]
|
|
185
|
+
msger -message "Your message"
|
|
186
|
+
echo '{"message":"text"}' | msger
|
|
187
|
+
|
|
188
|
+
Options:
|
|
189
|
+
-message <text> Specify message text (use quotes for multi-word)
|
|
190
|
+
Supports ANSI color escape sequences (e.g., \\x1b[31mRed\\x1b[0m)
|
|
191
|
+
-title <text> Set window title
|
|
192
|
+
-html <html> Display HTML formatted message (inline)
|
|
193
|
+
-htmlfrom <file|url> Fetch HTML from file/URL and embed in template (has buttons & msger API)
|
|
194
|
+
-url <url> Load URL directly in webview (no template, no buttons)
|
|
195
|
+
-hash <fragment> Hash fragment to append to URL (requires -url). Leading # optional.
|
|
196
|
+
-buttons <label...> Specify button labels (e.g., -buttons Yes No Cancel)
|
|
197
|
+
-ok Add OK button
|
|
198
|
+
-cancel Add Cancel button
|
|
199
|
+
-input [placeholder] Include an input field with optional placeholder text
|
|
200
|
+
-default <text> Default value for input field
|
|
201
|
+
-size <width,height> Set window size (e.g., -size 800,600)
|
|
202
|
+
-zoom <percent> Set zoom level as percentage (e.g., -zoom 150 for 150%)
|
|
203
|
+
-pos <x,y> Set window position (e.g., -pos 100,200)
|
|
204
|
+
-screen <number> [Windows only] Screen index for multi-monitor (0=primary, 1=second, etc.)
|
|
205
|
+
-icon <path> Set window icon (PNG file). Default: icon.png in current directory
|
|
206
|
+
-timeout <seconds> Auto-close after specified seconds
|
|
207
|
+
-detach Leave window open after app exits (parent process returns immediately)
|
|
208
|
+
-fullscreen Start window in fullscreen mode (F11 to toggle, Escape to exit)
|
|
209
|
+
-no-escape-closes Prevent Escape key from closing window (still exits fullscreen)
|
|
210
|
+
-reset Clear localStorage on startup (useful for resetting web content state)
|
|
211
|
+
-ontop Keep window always on top of other windows
|
|
212
|
+
-load <file> Load options from JSON file (supports comments)
|
|
213
|
+
-save <file> Save current options to JSON file
|
|
214
|
+
-noshow Save config without showing message box (use with -save)
|
|
215
|
+
-pin [Not yet implemented] Create pinnable taskbar shortcut
|
|
216
|
+
-dev Open DevTools (F12) automatically on startup (for debugging)
|
|
217
|
+
-debug Return debug info (HTML, size) in result
|
|
218
|
+
-v, -version, --version Show version number (alone: print and exit; with options: show in title)
|
|
219
|
+
-help, -?, --help Show this help message
|
|
220
|
+
|
|
221
|
+
Examples:
|
|
222
|
+
msger Hello World
|
|
223
|
+
msger -message "Save changes?" -buttons Yes No Cancel
|
|
224
|
+
msger -message "Enter your name:" -input "Your name" -default "John Doe"
|
|
225
|
+
msger -html "<h1>Title</h1><p>Formatted content</p>"
|
|
226
|
+
msger -htmlfrom page.html -buttons OK Cancel
|
|
227
|
+
msger -htmlfrom "https://example.com" -buttons OK
|
|
228
|
+
msger -url "https://example.com"
|
|
229
|
+
msger -url "https://example.com" -hash section1
|
|
230
|
+
msger -url "https://example.com" -detach
|
|
231
|
+
msger -title "Alert" -message "Operation complete"
|
|
232
|
+
echo -e "\\x1b[31mError:\\x1b[0m Something failed" | msger
|
|
233
|
+
echo '{"message":"Test","buttons":["OK"]}' | msger
|
|
234
|
+
|
|
235
|
+
Notes:
|
|
236
|
+
- If no options are provided, all non-option arguments are concatenated as the message
|
|
237
|
+
- Message text supports ANSI color codes (automatically converted to HTML)
|
|
238
|
+
- Press ESC to dismiss the dialog (returns button: "dismissed")
|
|
239
|
+
- Press Enter to click the last (default) button
|
|
240
|
+
- Default button is OK if no buttons specified
|
|
241
|
+
- Much faster than Electron-based msgview (~50-200ms vs ~2-3s startup)
|
|
242
|
+
|
|
243
|
+
Security Note:
|
|
244
|
+
- msger is designed for displaying trusted, friendly content (local apps, your own HTML)
|
|
245
|
+
- It is NOT a secure sandbox for untrusted/hostile web content
|
|
246
|
+
- Use -htmlfrom and -url with trusted sources only
|
|
247
|
+
`;
|
|
248
|
+
function parseCliArgs(args) {
|
|
249
|
+
const cli = {
|
|
250
|
+
buttons: [],
|
|
251
|
+
input: false,
|
|
252
|
+
help: false
|
|
253
|
+
};
|
|
254
|
+
let i = 0;
|
|
255
|
+
const messageWords = [];
|
|
256
|
+
while (i < args.length) {
|
|
257
|
+
const arg = args[i];
|
|
258
|
+
if (arg === '-help' || arg === '--help' || arg === '-?' || arg === '/?') {
|
|
259
|
+
cli.help = true;
|
|
260
|
+
i++;
|
|
261
|
+
}
|
|
262
|
+
else if (arg === '-v' || arg === '-version' || arg === '--version') {
|
|
263
|
+
cli.version = true;
|
|
264
|
+
i++;
|
|
265
|
+
}
|
|
266
|
+
else if (arg === '-message' || arg === '--message') {
|
|
267
|
+
if (i + 1 >= args.length) {
|
|
268
|
+
throw new Error('-message requires a text argument');
|
|
269
|
+
}
|
|
270
|
+
cli.message = args[++i];
|
|
271
|
+
i++;
|
|
272
|
+
}
|
|
273
|
+
else if (arg === '-title' || arg === '--title') {
|
|
274
|
+
if (i + 1 >= args.length) {
|
|
275
|
+
throw new Error('-title requires a text argument');
|
|
276
|
+
}
|
|
277
|
+
cli.title = args[++i];
|
|
278
|
+
i++;
|
|
279
|
+
}
|
|
280
|
+
else if (arg === '-html' || arg === '--html') {
|
|
281
|
+
if (i + 1 >= args.length) {
|
|
282
|
+
throw new Error('-html requires an HTML string argument');
|
|
283
|
+
}
|
|
284
|
+
cli.html = args[++i];
|
|
285
|
+
i++;
|
|
286
|
+
}
|
|
287
|
+
else if (arg === '-htmlfrom' || arg === '--htmlfrom') {
|
|
288
|
+
if (i + 1 >= args.length) {
|
|
289
|
+
throw new Error('-htmlfrom requires a file path or URL argument');
|
|
290
|
+
}
|
|
291
|
+
cli.htmlFrom = args[++i];
|
|
292
|
+
i++;
|
|
293
|
+
}
|
|
294
|
+
else if (arg === '-url' || arg === '--url') {
|
|
295
|
+
if (i + 1 >= args.length) {
|
|
296
|
+
throw new Error('-url requires a URL argument');
|
|
297
|
+
}
|
|
298
|
+
cli.url = args[++i];
|
|
299
|
+
i++;
|
|
300
|
+
}
|
|
301
|
+
else if (arg === '-hash' || arg === '--hash') {
|
|
302
|
+
if (i + 1 >= args.length) {
|
|
303
|
+
throw new Error('-hash requires a fragment argument');
|
|
304
|
+
}
|
|
305
|
+
cli.hash = args[++i];
|
|
306
|
+
i++;
|
|
307
|
+
}
|
|
308
|
+
else if (arg === '-buttons' || arg === '--buttons') {
|
|
309
|
+
i++;
|
|
310
|
+
while (i < args.length && !args[i].startsWith('-')) {
|
|
311
|
+
cli.buttons.push(args[i]);
|
|
312
|
+
i++;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
else if (arg === '-ok' || arg === '--ok') {
|
|
316
|
+
if (!cli.buttons.includes('OK')) {
|
|
317
|
+
cli.buttons.push('OK');
|
|
318
|
+
}
|
|
319
|
+
i++;
|
|
320
|
+
}
|
|
321
|
+
else if (arg === '-cancel' || arg === '--cancel') {
|
|
322
|
+
if (!cli.buttons.includes('Cancel')) {
|
|
323
|
+
cli.buttons.push('Cancel');
|
|
324
|
+
}
|
|
325
|
+
i++;
|
|
326
|
+
}
|
|
327
|
+
else if (arg === '-input' || arg === '--input') {
|
|
328
|
+
cli.input = true;
|
|
329
|
+
if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
|
|
330
|
+
cli.inputPlaceholder = args[++i];
|
|
331
|
+
}
|
|
332
|
+
i++;
|
|
333
|
+
}
|
|
334
|
+
else if (arg === '-default' || arg === '--default') {
|
|
335
|
+
if (i + 1 >= args.length) {
|
|
336
|
+
throw new Error('-default requires a text argument');
|
|
337
|
+
}
|
|
338
|
+
cli.defaultValue = args[++i];
|
|
339
|
+
i++;
|
|
340
|
+
}
|
|
341
|
+
else if (arg === '-timeout' || arg === '--timeout') {
|
|
342
|
+
if (i + 1 >= args.length) {
|
|
343
|
+
throw new Error('-timeout requires a number argument');
|
|
344
|
+
}
|
|
345
|
+
cli.timeout = parseInt(args[++i], 10);
|
|
346
|
+
i++;
|
|
347
|
+
}
|
|
348
|
+
else if (arg === '-detach' || arg === '--detach') {
|
|
349
|
+
cli.detach = true;
|
|
350
|
+
i++;
|
|
351
|
+
}
|
|
352
|
+
else if (arg === '-fullscreen' || arg === '--fullscreen') {
|
|
353
|
+
cli.fullscreen = true;
|
|
354
|
+
i++;
|
|
355
|
+
}
|
|
356
|
+
else if (arg === '-full' || arg === '--full') {
|
|
357
|
+
cli.full = true;
|
|
358
|
+
i++;
|
|
359
|
+
}
|
|
360
|
+
else if (arg === '-no-escape-closes' || arg === '--no-escape-closes') {
|
|
361
|
+
cli.escapeCloses = false;
|
|
362
|
+
i++;
|
|
363
|
+
}
|
|
364
|
+
else if (arg === '-reset' || arg === '--reset') {
|
|
365
|
+
cli.reset = true;
|
|
366
|
+
i++;
|
|
367
|
+
}
|
|
368
|
+
else if (arg === '-ontop' || arg === '--ontop' || arg === '-alwaysontop' || arg === '--alwaysontop') {
|
|
369
|
+
cli.alwaysOnTop = true;
|
|
370
|
+
i++;
|
|
371
|
+
}
|
|
372
|
+
else if (arg === '-size' || arg === '--size') {
|
|
373
|
+
if (i + 1 >= args.length) {
|
|
374
|
+
throw new Error('-size requires a width,height argument');
|
|
375
|
+
}
|
|
376
|
+
const size = args[++i];
|
|
377
|
+
const [w, h] = size.split(',').map(s => parseInt(s.trim(), 10));
|
|
378
|
+
if (w)
|
|
379
|
+
cli.width = w;
|
|
380
|
+
if (h)
|
|
381
|
+
cli.height = h;
|
|
382
|
+
i++;
|
|
383
|
+
}
|
|
384
|
+
else if (arg === '-zoom' || arg === '--zoom') {
|
|
385
|
+
if (i + 1 >= args.length) {
|
|
386
|
+
throw new Error('-zoom requires a percentage argument');
|
|
387
|
+
}
|
|
388
|
+
cli.zoom = parseFloat(args[++i]);
|
|
389
|
+
i++;
|
|
390
|
+
}
|
|
391
|
+
else if (arg === '-pos' || arg === '--pos') {
|
|
392
|
+
if (i + 1 >= args.length) {
|
|
393
|
+
throw new Error('-pos requires an x,y or x,y,screen argument');
|
|
394
|
+
}
|
|
395
|
+
const pos = args[++i];
|
|
396
|
+
const parts = pos.split(',').map(s => parseInt(s.trim(), 10));
|
|
397
|
+
if (!isNaN(parts[0]))
|
|
398
|
+
cli.posX = parts[0];
|
|
399
|
+
if (!isNaN(parts[1]))
|
|
400
|
+
cli.posY = parts[1];
|
|
401
|
+
if (!isNaN(parts[2]))
|
|
402
|
+
cli.screen = parts[2];
|
|
403
|
+
i++;
|
|
404
|
+
}
|
|
405
|
+
else if (arg === '-screen' || arg === '--screen') {
|
|
406
|
+
if (i + 1 >= args.length) {
|
|
407
|
+
throw new Error('-screen requires a number argument');
|
|
408
|
+
}
|
|
409
|
+
cli.screen = parseInt(args[++i], 10);
|
|
410
|
+
i++;
|
|
411
|
+
}
|
|
412
|
+
else if (arg === '-icon' || arg === '--icon') {
|
|
413
|
+
if (i + 1 >= args.length) {
|
|
414
|
+
throw new Error('-icon requires a file path argument');
|
|
415
|
+
}
|
|
416
|
+
cli.icon = args[++i];
|
|
417
|
+
i++;
|
|
418
|
+
}
|
|
419
|
+
else if (arg === '-dev' || arg === '--dev') {
|
|
420
|
+
cli.dev = true;
|
|
421
|
+
i++;
|
|
422
|
+
}
|
|
423
|
+
else if (arg === '-debug' || arg === '--debug') {
|
|
424
|
+
cli.debug = true;
|
|
425
|
+
i++;
|
|
426
|
+
}
|
|
427
|
+
else if (arg === '-load' || arg === '--load') {
|
|
428
|
+
if (i + 1 >= args.length) {
|
|
429
|
+
throw new Error('-load requires a file path argument');
|
|
430
|
+
}
|
|
431
|
+
cli.load = args[++i];
|
|
432
|
+
i++;
|
|
433
|
+
}
|
|
434
|
+
else if (arg === '-save' || arg === '--save') {
|
|
435
|
+
const nextArg = args[i + 1];
|
|
436
|
+
if (nextArg && !nextArg.startsWith('-')) {
|
|
437
|
+
cli.save = nextArg;
|
|
438
|
+
i += 2;
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
cli.save = true;
|
|
442
|
+
i++;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
else if (arg === '-noshow' || arg === '--noshow') {
|
|
446
|
+
cli.noshow = true;
|
|
447
|
+
i++;
|
|
448
|
+
}
|
|
449
|
+
else if (arg === '-pin' || arg === '--pin') {
|
|
450
|
+
cli.pin = true;
|
|
451
|
+
i++;
|
|
452
|
+
}
|
|
453
|
+
else if (!arg.startsWith('-')) {
|
|
454
|
+
messageWords.push(arg);
|
|
455
|
+
i++;
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
console.warn(`Unknown option: ${arg}`);
|
|
459
|
+
i++;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
if (cli.full && cli.url) {
|
|
463
|
+
throw new Error('-full and -url cannot be used together. Did you mean -fullscreen?');
|
|
464
|
+
}
|
|
465
|
+
let message;
|
|
466
|
+
let html;
|
|
467
|
+
let url;
|
|
468
|
+
if (cli.url) {
|
|
469
|
+
message = cli.url;
|
|
470
|
+
url = cli.url;
|
|
471
|
+
}
|
|
472
|
+
else if (cli.htmlFrom) {
|
|
473
|
+
if (cli.htmlFrom.startsWith('http://') || cli.htmlFrom.startsWith('https://')) {
|
|
474
|
+
try {
|
|
475
|
+
const fetchCommand = `node --input-type=module -e "const res = await fetch('${cli.htmlFrom}'); console.log(await res.text())"`;
|
|
476
|
+
const htmlContent = execSync(fetchCommand, { encoding: 'utf-8' });
|
|
477
|
+
message = htmlContent;
|
|
478
|
+
html = htmlContent;
|
|
479
|
+
}
|
|
480
|
+
catch (error) {
|
|
481
|
+
throw new Error(`Failed to fetch HTML from URL: ${error.message}`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
const htmlPath = path.resolve(cli.htmlFrom);
|
|
486
|
+
if (!fs.existsSync(htmlPath)) {
|
|
487
|
+
throw new Error(`HTML file not found: ${htmlPath}`);
|
|
488
|
+
}
|
|
489
|
+
try {
|
|
490
|
+
const htmlContent = fs.readFileSync(htmlPath, 'utf-8');
|
|
491
|
+
message = htmlContent;
|
|
492
|
+
html = htmlContent;
|
|
493
|
+
}
|
|
494
|
+
catch (error) {
|
|
495
|
+
throw new Error(`Failed to read HTML file: ${error.message}`);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
else if (cli.html) {
|
|
500
|
+
message = cli.html;
|
|
501
|
+
html = cli.html;
|
|
502
|
+
}
|
|
503
|
+
else if (cli.message) {
|
|
504
|
+
message = cli.message;
|
|
505
|
+
}
|
|
506
|
+
else if (messageWords.length > 0) {
|
|
507
|
+
message = messageWords.join(' ');
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
message = '';
|
|
511
|
+
}
|
|
512
|
+
const options = {
|
|
513
|
+
title: cli.title,
|
|
514
|
+
message,
|
|
515
|
+
html,
|
|
516
|
+
url,
|
|
517
|
+
hash: cli.hash,
|
|
518
|
+
buttons: cli.buttons,
|
|
519
|
+
allowInput: cli.input,
|
|
520
|
+
defaultValue: cli.defaultValue,
|
|
521
|
+
inputPlaceholder: cli.inputPlaceholder,
|
|
522
|
+
timeout: cli.timeout,
|
|
523
|
+
detach: cli.detach,
|
|
524
|
+
fullscreen: cli.fullscreen,
|
|
525
|
+
alwaysOnTop: cli.alwaysOnTop,
|
|
526
|
+
zoom: cli.zoom,
|
|
527
|
+
icon: cli.icon,
|
|
528
|
+
dev: cli.dev,
|
|
529
|
+
debug: cli.debug
|
|
530
|
+
};
|
|
531
|
+
if (cli.width !== undefined || cli.height !== undefined) {
|
|
532
|
+
const defaultWidth = url ? 1024 : 600;
|
|
533
|
+
const defaultHeight = url ? 768 : 400;
|
|
534
|
+
options.size = {
|
|
535
|
+
width: cli.width !== undefined ? cli.width : defaultWidth,
|
|
536
|
+
height: cli.height !== undefined ? cli.height : defaultHeight,
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
else if (url) {
|
|
540
|
+
options.size = {
|
|
541
|
+
width: 1024,
|
|
542
|
+
height: 768,
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
if (cli.posX !== undefined && cli.posY !== undefined) {
|
|
546
|
+
options.pos = { x: cli.posX, y: cli.posY };
|
|
547
|
+
if (cli.screen !== undefined) {
|
|
548
|
+
options.pos.screen = cli.screen;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return {
|
|
552
|
+
options,
|
|
553
|
+
showHelp: cli.help || false,
|
|
554
|
+
showVersion: cli.version || false,
|
|
555
|
+
loadFile: cli.load,
|
|
556
|
+
saveFile: cli.save,
|
|
557
|
+
noshow: cli.noshow || false,
|
|
558
|
+
pin: cli.pin || false
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
function loadConfigFile(filePath) {
|
|
562
|
+
const absolutePath = path.resolve(filePath);
|
|
563
|
+
if (!fs.existsSync(absolutePath)) {
|
|
564
|
+
throw new Error(`Config file not found: ${absolutePath}`);
|
|
565
|
+
}
|
|
566
|
+
const fileContent = fs.readFileSync(absolutePath, 'utf-8');
|
|
567
|
+
try {
|
|
568
|
+
const config = JSON5.parse(fileContent);
|
|
569
|
+
if (config.pos && typeof config.pos === 'string') {
|
|
570
|
+
const parts = config.pos.split(',').map((s) => parseInt(s.trim(), 10));
|
|
571
|
+
const posObj = { x: parts[0], y: parts[1] };
|
|
572
|
+
if (!isNaN(parts[2])) {
|
|
573
|
+
posObj.screen = parts[2];
|
|
574
|
+
}
|
|
575
|
+
config.pos = posObj;
|
|
576
|
+
}
|
|
577
|
+
// Resolve icon path relative to JSON directory
|
|
578
|
+
if (config.icon && !path.isAbsolute(config.icon)) {
|
|
579
|
+
const jsonDir = path.dirname(absolutePath);
|
|
580
|
+
config.icon = path.resolve(jsonDir, config.icon);
|
|
581
|
+
}
|
|
582
|
+
return config;
|
|
583
|
+
}
|
|
584
|
+
catch (error) {
|
|
585
|
+
throw new Error(`Failed to parse config file ${filePath}: ${error.message}`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
function saveConfigFile(filePath, options) {
|
|
589
|
+
const absolutePath = path.resolve(filePath);
|
|
590
|
+
const config = {};
|
|
591
|
+
if (options.title && options.title !== 'Message')
|
|
592
|
+
config.title = options.title;
|
|
593
|
+
if (options.message)
|
|
594
|
+
config.message = options.message;
|
|
595
|
+
if (options.html)
|
|
596
|
+
config.html = options.html;
|
|
597
|
+
// Save URL without hash fragment (hash is specified via -hash option)
|
|
598
|
+
if (options.url)
|
|
599
|
+
config.url = options.url.split('#')[0];
|
|
600
|
+
if (options.hash)
|
|
601
|
+
config.hash = options.hash;
|
|
602
|
+
if (options.size)
|
|
603
|
+
config.size = options.size;
|
|
604
|
+
if (options.pos) {
|
|
605
|
+
config.pos = { x: options.pos.x, y: options.pos.y };
|
|
606
|
+
if (options.pos.screen !== undefined) {
|
|
607
|
+
config.pos.screen = options.pos.screen;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
if (options.buttons && options.buttons.length > 0 && JSON.stringify(options.buttons) !== '["OK"]') {
|
|
611
|
+
config.buttons = options.buttons;
|
|
612
|
+
}
|
|
613
|
+
if (options.allowInput)
|
|
614
|
+
config.allowInput = options.allowInput;
|
|
615
|
+
if (options.defaultValue)
|
|
616
|
+
config.defaultValue = options.defaultValue;
|
|
617
|
+
if (options.inputPlaceholder)
|
|
618
|
+
config.inputPlaceholder = options.inputPlaceholder;
|
|
619
|
+
if (options.timeout)
|
|
620
|
+
config.timeout = options.timeout;
|
|
621
|
+
if (options.alwaysOnTop)
|
|
622
|
+
config.alwaysOnTop = options.alwaysOnTop;
|
|
623
|
+
if (options.fullscreen)
|
|
624
|
+
config.fullscreen = options.fullscreen;
|
|
625
|
+
if (options.zoom && options.zoom !== 100)
|
|
626
|
+
config.zoom = options.zoom;
|
|
627
|
+
if (options.icon)
|
|
628
|
+
config.icon = options.icon;
|
|
629
|
+
if (options.dev)
|
|
630
|
+
config.dev = options.dev;
|
|
631
|
+
if (options.debug)
|
|
632
|
+
config.debug = options.debug;
|
|
633
|
+
const jsonContent = JSON.stringify(config, null, 4) + '\n';
|
|
634
|
+
fs.writeFileSync(absolutePath, jsonContent, 'utf-8');
|
|
635
|
+
}
|
|
636
|
+
function showHelp() {
|
|
637
|
+
console.log(HELP_TEXT);
|
|
638
|
+
}
|
|
639
|
+
function showVersion() {
|
|
640
|
+
console.log(packageJson.version);
|
|
641
|
+
}
|
|
642
|
+
// Run main when file is executed (either directly or as a bin script)
|
|
643
|
+
if (import.meta.main) {
|
|
644
|
+
await main();
|
|
645
|
+
}
|
package/cli.d.ts
CHANGED
package/cli.js
CHANGED
|
@@ -1,162 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
// const { options, messageWords, showHelp, showVersion } = parseCommonArgs(args);
|
|
7
|
-
//
|
|
2
|
+
/**
|
|
3
|
+
* msger CLI - uses universal CLI from msgcommon
|
|
4
|
+
*/
|
|
5
|
+
import { runCli } from '@bobfrankston/msgcommon';
|
|
8
6
|
import { showMessageBox } from './shower.js';
|
|
9
7
|
import packageJson from './package.json' with { type: 'json' };
|
|
10
|
-
import fs from 'fs';
|
|
11
|
-
import path from 'path';
|
|
12
|
-
import { execSync } from 'child_process';
|
|
13
|
-
import JSON5 from 'json5';
|
|
14
|
-
// Uncomment when ready to migrate:
|
|
15
|
-
// import { parseCommonArgs } from '@bobfrankston/msgcommon';
|
|
16
|
-
export default async function main() {
|
|
17
|
-
const args = process.argv.slice(2);
|
|
18
|
-
// No arguments - show help
|
|
19
|
-
if (args.length === 0) {
|
|
20
|
-
showHelp();
|
|
21
|
-
process.exit(0);
|
|
22
|
-
}
|
|
23
|
-
// Parse command line arguments
|
|
24
|
-
let options, shouldShowHelp, shouldShowVersion, loadFile, saveFile, noshow;
|
|
25
|
-
try {
|
|
26
|
-
({ options, showHelp: shouldShowHelp, showVersion: shouldShowVersion, loadFile, saveFile, noshow } = parseCliArgs(args));
|
|
27
|
-
}
|
|
28
|
-
catch (error) {
|
|
29
|
-
console.error('Error:', error.message);
|
|
30
|
-
console.error('\nUse -help to see usage information.');
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
33
|
-
// Helper to add .json extension if no extension specified
|
|
34
|
-
const ensureJsonExt = (filename) => {
|
|
35
|
-
const ext = path.extname(filename);
|
|
36
|
-
return ext ? filename : `${filename}.json`;
|
|
37
|
-
};
|
|
38
|
-
// Load config file if specified
|
|
39
|
-
let finalOptions = options;
|
|
40
|
-
if (loadFile) {
|
|
41
|
-
try {
|
|
42
|
-
const loadedConfig = loadConfigFile(ensureJsonExt(loadFile));
|
|
43
|
-
finalOptions = { ...loadedConfig };
|
|
44
|
-
if (options.title)
|
|
45
|
-
finalOptions.title = options.title;
|
|
46
|
-
if (options.message)
|
|
47
|
-
finalOptions.message = options.message;
|
|
48
|
-
if (options.html)
|
|
49
|
-
finalOptions.html = options.html;
|
|
50
|
-
if (options.url)
|
|
51
|
-
finalOptions.url = options.url;
|
|
52
|
-
if (options.hash)
|
|
53
|
-
finalOptions.hash = options.hash;
|
|
54
|
-
if (options.size)
|
|
55
|
-
finalOptions.size = options.size;
|
|
56
|
-
if (options.pos)
|
|
57
|
-
finalOptions.pos = options.pos;
|
|
58
|
-
if (options.buttons && options.buttons.length > 0)
|
|
59
|
-
finalOptions.buttons = options.buttons;
|
|
60
|
-
if (options.allowInput !== undefined)
|
|
61
|
-
finalOptions.allowInput = options.allowInput;
|
|
62
|
-
if (options.defaultValue)
|
|
63
|
-
finalOptions.defaultValue = options.defaultValue;
|
|
64
|
-
if (options.inputPlaceholder)
|
|
65
|
-
finalOptions.inputPlaceholder = options.inputPlaceholder;
|
|
66
|
-
if (options.timeout !== undefined)
|
|
67
|
-
finalOptions.timeout = options.timeout;
|
|
68
|
-
if (options.alwaysOnTop !== undefined)
|
|
69
|
-
finalOptions.alwaysOnTop = options.alwaysOnTop;
|
|
70
|
-
if (options.fullscreen !== undefined)
|
|
71
|
-
finalOptions.fullscreen = options.fullscreen;
|
|
72
|
-
if (options.zoom !== undefined)
|
|
73
|
-
finalOptions.zoom = options.zoom;
|
|
74
|
-
if (options.icon)
|
|
75
|
-
finalOptions.icon = options.icon;
|
|
76
|
-
if (options.dev !== undefined)
|
|
77
|
-
finalOptions.dev = options.dev;
|
|
78
|
-
if (options.debug !== undefined)
|
|
79
|
-
finalOptions.debug = options.debug;
|
|
80
|
-
if (options.detach !== undefined)
|
|
81
|
-
finalOptions.detach = options.detach;
|
|
82
|
-
}
|
|
83
|
-
catch (error) {
|
|
84
|
-
console.error('Error loading config:', error.message);
|
|
85
|
-
process.exit(1);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
if (!finalOptions.buttons || finalOptions.buttons.length === 0) {
|
|
89
|
-
finalOptions.buttons = ['OK'];
|
|
90
|
-
}
|
|
91
|
-
// Help always takes precedence and exits
|
|
92
|
-
if (shouldShowHelp) {
|
|
93
|
-
showHelp();
|
|
94
|
-
process.exit(0);
|
|
95
|
-
}
|
|
96
|
-
// Version only exits if there's nothing else meaningful to do
|
|
97
|
-
// (no actual message/url/html content was provided - the default message doesn't count)
|
|
98
|
-
const hasRealContent = args.some(arg => !arg.startsWith('-') ||
|
|
99
|
-
arg === '-message' ||
|
|
100
|
-
arg === '-html' ||
|
|
101
|
-
arg === '-url');
|
|
102
|
-
if (shouldShowVersion && !hasRealContent) {
|
|
103
|
-
showVersion();
|
|
104
|
-
process.exit(0);
|
|
105
|
-
}
|
|
106
|
-
// If version was requested along with other options, add it to title
|
|
107
|
-
if (shouldShowVersion && hasRealContent) {
|
|
108
|
-
finalOptions.showVersion = true;
|
|
109
|
-
}
|
|
110
|
-
if (saveFile) {
|
|
111
|
-
try {
|
|
112
|
-
let saveFilePath;
|
|
113
|
-
if (saveFile === true) {
|
|
114
|
-
if (!loadFile) {
|
|
115
|
-
console.error('Error: -save without filename requires -load to be specified');
|
|
116
|
-
process.exit(1);
|
|
117
|
-
}
|
|
118
|
-
saveFilePath = ensureJsonExt(loadFile);
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
saveFilePath = ensureJsonExt(saveFile);
|
|
122
|
-
}
|
|
123
|
-
const toSave = saveFile === true ? finalOptions : options;
|
|
124
|
-
saveConfigFile(saveFilePath, toSave);
|
|
125
|
-
console.log(`Configuration saved to: ${saveFilePath}`);
|
|
126
|
-
if (noshow) {
|
|
127
|
-
process.exit(0);
|
|
128
|
-
}
|
|
129
|
-
if (!finalOptions.message && !finalOptions.url && !finalOptions.html) {
|
|
130
|
-
process.exit(0);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
catch (error) {
|
|
134
|
-
console.error('Error saving config:', error.message);
|
|
135
|
-
process.exit(1);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
// If no actual message/content was provided, show help
|
|
139
|
-
if (!finalOptions.message && !finalOptions.url && !finalOptions.html) {
|
|
140
|
-
showHelp();
|
|
141
|
-
process.exit(0);
|
|
142
|
-
}
|
|
143
|
-
try {
|
|
144
|
-
const result = await showMessageBox(finalOptions);
|
|
145
|
-
// If detached, exit immediately without printing result
|
|
146
|
-
if (finalOptions.detach) {
|
|
147
|
-
process.exit(0);
|
|
148
|
-
}
|
|
149
|
-
console.log(JSON.stringify(result, null, 2));
|
|
150
|
-
// Exit with code based on result
|
|
151
|
-
if (result.dismissed || result.closed) {
|
|
152
|
-
process.exit(1);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
catch (error) {
|
|
156
|
-
console.error('Error:', error.message);
|
|
157
|
-
process.exit(1);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
8
|
const HELP_TEXT = `
|
|
161
9
|
msger v${packageJson.version} - Fast, lightweight, cross-platform message box (Rust-powered)
|
|
162
10
|
|
|
@@ -192,6 +40,7 @@ Options:
|
|
|
192
40
|
-load <file> Load options from JSON file (supports comments)
|
|
193
41
|
-save <file> Save current options to JSON file
|
|
194
42
|
-noshow Save config without showing message box (use with -save)
|
|
43
|
+
-pin [Not yet implemented] Create pinnable taskbar shortcut
|
|
195
44
|
-dev Open DevTools (F12) automatically on startup (for debugging)
|
|
196
45
|
-debug Return debug info (HTML, size) in result
|
|
197
46
|
-v, -version, --version Show version number (alone: print and exit; with options: show in title)
|
|
@@ -224,391 +73,9 @@ Security Note:
|
|
|
224
73
|
- It is NOT a secure sandbox for untrusted/hostile web content
|
|
225
74
|
- Use -htmlfrom and -url with trusted sources only
|
|
226
75
|
`;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
let i = 0;
|
|
234
|
-
const messageWords = [];
|
|
235
|
-
while (i < args.length) {
|
|
236
|
-
const arg = args[i];
|
|
237
|
-
if (arg === '-help' || arg === '--help' || arg === '-?' || arg === '/?') {
|
|
238
|
-
cli.help = true;
|
|
239
|
-
i++;
|
|
240
|
-
}
|
|
241
|
-
else if (arg === '-v' || arg === '-version' || arg === '--version') {
|
|
242
|
-
cli.version = true;
|
|
243
|
-
i++;
|
|
244
|
-
}
|
|
245
|
-
else if (arg === '-message' || arg === '--message') {
|
|
246
|
-
if (i + 1 >= args.length) {
|
|
247
|
-
throw new Error('-message requires a text argument');
|
|
248
|
-
}
|
|
249
|
-
cli.message = args[++i];
|
|
250
|
-
i++;
|
|
251
|
-
}
|
|
252
|
-
else if (arg === '-title' || arg === '--title') {
|
|
253
|
-
if (i + 1 >= args.length) {
|
|
254
|
-
throw new Error('-title requires a text argument');
|
|
255
|
-
}
|
|
256
|
-
cli.title = args[++i];
|
|
257
|
-
i++;
|
|
258
|
-
}
|
|
259
|
-
else if (arg === '-html' || arg === '--html') {
|
|
260
|
-
if (i + 1 >= args.length) {
|
|
261
|
-
throw new Error('-html requires an HTML string argument');
|
|
262
|
-
}
|
|
263
|
-
cli.html = args[++i];
|
|
264
|
-
i++;
|
|
265
|
-
}
|
|
266
|
-
else if (arg === '-htmlfrom' || arg === '--htmlfrom') {
|
|
267
|
-
if (i + 1 >= args.length) {
|
|
268
|
-
throw new Error('-htmlfrom requires a file path or URL argument');
|
|
269
|
-
}
|
|
270
|
-
cli.htmlFrom = args[++i];
|
|
271
|
-
i++;
|
|
272
|
-
}
|
|
273
|
-
else if (arg === '-url' || arg === '--url') {
|
|
274
|
-
if (i + 1 >= args.length) {
|
|
275
|
-
throw new Error('-url requires a URL argument');
|
|
276
|
-
}
|
|
277
|
-
cli.url = args[++i];
|
|
278
|
-
i++;
|
|
279
|
-
}
|
|
280
|
-
else if (arg === '-hash' || arg === '--hash') {
|
|
281
|
-
if (i + 1 >= args.length) {
|
|
282
|
-
throw new Error('-hash requires a fragment argument');
|
|
283
|
-
}
|
|
284
|
-
cli.hash = args[++i];
|
|
285
|
-
i++;
|
|
286
|
-
}
|
|
287
|
-
else if (arg === '-buttons' || arg === '--buttons') {
|
|
288
|
-
i++;
|
|
289
|
-
while (i < args.length && !args[i].startsWith('-')) {
|
|
290
|
-
cli.buttons.push(args[i]);
|
|
291
|
-
i++;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
else if (arg === '-ok' || arg === '--ok') {
|
|
295
|
-
if (!cli.buttons.includes('OK')) {
|
|
296
|
-
cli.buttons.push('OK');
|
|
297
|
-
}
|
|
298
|
-
i++;
|
|
299
|
-
}
|
|
300
|
-
else if (arg === '-cancel' || arg === '--cancel') {
|
|
301
|
-
if (!cli.buttons.includes('Cancel')) {
|
|
302
|
-
cli.buttons.push('Cancel');
|
|
303
|
-
}
|
|
304
|
-
i++;
|
|
305
|
-
}
|
|
306
|
-
else if (arg === '-input' || arg === '--input') {
|
|
307
|
-
cli.input = true;
|
|
308
|
-
if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
|
|
309
|
-
cli.inputPlaceholder = args[++i];
|
|
310
|
-
}
|
|
311
|
-
i++;
|
|
312
|
-
}
|
|
313
|
-
else if (arg === '-default' || arg === '--default') {
|
|
314
|
-
if (i + 1 >= args.length) {
|
|
315
|
-
throw new Error('-default requires a text argument');
|
|
316
|
-
}
|
|
317
|
-
cli.defaultValue = args[++i];
|
|
318
|
-
i++;
|
|
319
|
-
}
|
|
320
|
-
else if (arg === '-timeout' || arg === '--timeout') {
|
|
321
|
-
if (i + 1 >= args.length) {
|
|
322
|
-
throw new Error('-timeout requires a number argument');
|
|
323
|
-
}
|
|
324
|
-
cli.timeout = parseInt(args[++i], 10);
|
|
325
|
-
i++;
|
|
326
|
-
}
|
|
327
|
-
else if (arg === '-detach' || arg === '--detach') {
|
|
328
|
-
cli.detach = true;
|
|
329
|
-
i++;
|
|
330
|
-
}
|
|
331
|
-
else if (arg === '-fullscreen' || arg === '--fullscreen') {
|
|
332
|
-
cli.fullscreen = true;
|
|
333
|
-
i++;
|
|
334
|
-
}
|
|
335
|
-
else if (arg === '-full' || arg === '--full') {
|
|
336
|
-
cli.full = true;
|
|
337
|
-
i++;
|
|
338
|
-
}
|
|
339
|
-
else if (arg === '-no-escape-closes' || arg === '--no-escape-closes') {
|
|
340
|
-
cli.escapeCloses = false;
|
|
341
|
-
i++;
|
|
342
|
-
}
|
|
343
|
-
else if (arg === '-reset' || arg === '--reset') {
|
|
344
|
-
cli.reset = true;
|
|
345
|
-
i++;
|
|
346
|
-
}
|
|
347
|
-
else if (arg === '-ontop' || arg === '--ontop' || arg === '-alwaysontop' || arg === '--alwaysontop') {
|
|
348
|
-
cli.alwaysOnTop = true;
|
|
349
|
-
i++;
|
|
350
|
-
}
|
|
351
|
-
else if (arg === '-size' || arg === '--size') {
|
|
352
|
-
if (i + 1 >= args.length) {
|
|
353
|
-
throw new Error('-size requires a width,height argument');
|
|
354
|
-
}
|
|
355
|
-
const size = args[++i];
|
|
356
|
-
const [w, h] = size.split(',').map(s => parseInt(s.trim(), 10));
|
|
357
|
-
if (w)
|
|
358
|
-
cli.width = w;
|
|
359
|
-
if (h)
|
|
360
|
-
cli.height = h;
|
|
361
|
-
i++;
|
|
362
|
-
}
|
|
363
|
-
else if (arg === '-zoom' || arg === '--zoom') {
|
|
364
|
-
if (i + 1 >= args.length) {
|
|
365
|
-
throw new Error('-zoom requires a percentage argument');
|
|
366
|
-
}
|
|
367
|
-
cli.zoom = parseFloat(args[++i]);
|
|
368
|
-
i++;
|
|
369
|
-
}
|
|
370
|
-
else if (arg === '-pos' || arg === '--pos') {
|
|
371
|
-
if (i + 1 >= args.length) {
|
|
372
|
-
throw new Error('-pos requires an x,y or x,y,screen argument');
|
|
373
|
-
}
|
|
374
|
-
const pos = args[++i];
|
|
375
|
-
const parts = pos.split(',').map(s => parseInt(s.trim(), 10));
|
|
376
|
-
if (!isNaN(parts[0]))
|
|
377
|
-
cli.posX = parts[0];
|
|
378
|
-
if (!isNaN(parts[1]))
|
|
379
|
-
cli.posY = parts[1];
|
|
380
|
-
if (!isNaN(parts[2]))
|
|
381
|
-
cli.screen = parts[2];
|
|
382
|
-
i++;
|
|
383
|
-
}
|
|
384
|
-
else if (arg === '-screen' || arg === '--screen') {
|
|
385
|
-
if (i + 1 >= args.length) {
|
|
386
|
-
throw new Error('-screen requires a number argument');
|
|
387
|
-
}
|
|
388
|
-
cli.screen = parseInt(args[++i], 10);
|
|
389
|
-
i++;
|
|
390
|
-
}
|
|
391
|
-
else if (arg === '-icon' || arg === '--icon') {
|
|
392
|
-
if (i + 1 >= args.length) {
|
|
393
|
-
throw new Error('-icon requires a file path argument');
|
|
394
|
-
}
|
|
395
|
-
cli.icon = args[++i];
|
|
396
|
-
i++;
|
|
397
|
-
}
|
|
398
|
-
else if (arg === '-dev' || arg === '--dev') {
|
|
399
|
-
cli.dev = true;
|
|
400
|
-
i++;
|
|
401
|
-
}
|
|
402
|
-
else if (arg === '-debug' || arg === '--debug') {
|
|
403
|
-
cli.debug = true;
|
|
404
|
-
i++;
|
|
405
|
-
}
|
|
406
|
-
else if (arg === '-load' || arg === '--load') {
|
|
407
|
-
if (i + 1 >= args.length) {
|
|
408
|
-
throw new Error('-load requires a file path argument');
|
|
409
|
-
}
|
|
410
|
-
cli.load = args[++i];
|
|
411
|
-
i++;
|
|
412
|
-
}
|
|
413
|
-
else if (arg === '-save' || arg === '--save') {
|
|
414
|
-
const nextArg = args[i + 1];
|
|
415
|
-
if (nextArg && !nextArg.startsWith('-')) {
|
|
416
|
-
cli.save = nextArg;
|
|
417
|
-
i += 2;
|
|
418
|
-
}
|
|
419
|
-
else {
|
|
420
|
-
cli.save = true;
|
|
421
|
-
i++;
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
else if (arg === '-noshow' || arg === '--noshow') {
|
|
425
|
-
cli.noshow = true;
|
|
426
|
-
i++;
|
|
427
|
-
}
|
|
428
|
-
else if (!arg.startsWith('-')) {
|
|
429
|
-
messageWords.push(arg);
|
|
430
|
-
i++;
|
|
431
|
-
}
|
|
432
|
-
else {
|
|
433
|
-
console.warn(`Unknown option: ${arg}`);
|
|
434
|
-
i++;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
if (cli.full && cli.url) {
|
|
438
|
-
throw new Error('-full and -url cannot be used together. Did you mean -fullscreen?');
|
|
439
|
-
}
|
|
440
|
-
let message;
|
|
441
|
-
let html;
|
|
442
|
-
let url;
|
|
443
|
-
if (cli.url) {
|
|
444
|
-
message = cli.url;
|
|
445
|
-
url = cli.url;
|
|
446
|
-
}
|
|
447
|
-
else if (cli.htmlFrom) {
|
|
448
|
-
if (cli.htmlFrom.startsWith('http://') || cli.htmlFrom.startsWith('https://')) {
|
|
449
|
-
try {
|
|
450
|
-
const fetchCommand = `node --input-type=module -e "const res = await fetch('${cli.htmlFrom}'); console.log(await res.text())"`;
|
|
451
|
-
const htmlContent = execSync(fetchCommand, { encoding: 'utf-8' });
|
|
452
|
-
message = htmlContent;
|
|
453
|
-
html = htmlContent;
|
|
454
|
-
}
|
|
455
|
-
catch (error) {
|
|
456
|
-
throw new Error(`Failed to fetch HTML from URL: ${error.message}`);
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
else {
|
|
460
|
-
const htmlPath = path.resolve(cli.htmlFrom);
|
|
461
|
-
if (!fs.existsSync(htmlPath)) {
|
|
462
|
-
throw new Error(`HTML file not found: ${htmlPath}`);
|
|
463
|
-
}
|
|
464
|
-
try {
|
|
465
|
-
const htmlContent = fs.readFileSync(htmlPath, 'utf-8');
|
|
466
|
-
message = htmlContent;
|
|
467
|
-
html = htmlContent;
|
|
468
|
-
}
|
|
469
|
-
catch (error) {
|
|
470
|
-
throw new Error(`Failed to read HTML file: ${error.message}`);
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
else if (cli.html) {
|
|
475
|
-
message = cli.html;
|
|
476
|
-
html = cli.html;
|
|
477
|
-
}
|
|
478
|
-
else if (cli.message) {
|
|
479
|
-
message = cli.message;
|
|
480
|
-
}
|
|
481
|
-
else if (messageWords.length > 0) {
|
|
482
|
-
message = messageWords.join(' ');
|
|
483
|
-
}
|
|
484
|
-
else {
|
|
485
|
-
message = '';
|
|
486
|
-
}
|
|
487
|
-
const options = {
|
|
488
|
-
title: cli.title,
|
|
489
|
-
message,
|
|
490
|
-
html,
|
|
491
|
-
url,
|
|
492
|
-
hash: cli.hash,
|
|
493
|
-
buttons: cli.buttons,
|
|
494
|
-
allowInput: cli.input,
|
|
495
|
-
defaultValue: cli.defaultValue,
|
|
496
|
-
inputPlaceholder: cli.inputPlaceholder,
|
|
497
|
-
timeout: cli.timeout,
|
|
498
|
-
detach: cli.detach,
|
|
499
|
-
fullscreen: cli.fullscreen,
|
|
500
|
-
alwaysOnTop: cli.alwaysOnTop,
|
|
501
|
-
zoom: cli.zoom,
|
|
502
|
-
icon: cli.icon,
|
|
503
|
-
dev: cli.dev,
|
|
504
|
-
debug: cli.debug
|
|
505
|
-
};
|
|
506
|
-
if (cli.width !== undefined || cli.height !== undefined) {
|
|
507
|
-
const defaultWidth = url ? 1024 : 600;
|
|
508
|
-
const defaultHeight = url ? 768 : 400;
|
|
509
|
-
options.size = {
|
|
510
|
-
width: cli.width !== undefined ? cli.width : defaultWidth,
|
|
511
|
-
height: cli.height !== undefined ? cli.height : defaultHeight,
|
|
512
|
-
};
|
|
513
|
-
}
|
|
514
|
-
else if (url) {
|
|
515
|
-
options.size = {
|
|
516
|
-
width: 1024,
|
|
517
|
-
height: 768,
|
|
518
|
-
};
|
|
519
|
-
}
|
|
520
|
-
if (cli.posX !== undefined && cli.posY !== undefined) {
|
|
521
|
-
options.pos = { x: cli.posX, y: cli.posY };
|
|
522
|
-
if (cli.screen !== undefined) {
|
|
523
|
-
options.pos.screen = cli.screen;
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
return {
|
|
527
|
-
options,
|
|
528
|
-
showHelp: cli.help || false,
|
|
529
|
-
showVersion: cli.version || false,
|
|
530
|
-
loadFile: cli.load,
|
|
531
|
-
saveFile: cli.save,
|
|
532
|
-
noshow: cli.noshow || false
|
|
533
|
-
};
|
|
534
|
-
}
|
|
535
|
-
function loadConfigFile(filePath) {
|
|
536
|
-
const absolutePath = path.resolve(filePath);
|
|
537
|
-
if (!fs.existsSync(absolutePath)) {
|
|
538
|
-
throw new Error(`Config file not found: ${absolutePath}`);
|
|
539
|
-
}
|
|
540
|
-
const fileContent = fs.readFileSync(absolutePath, 'utf-8');
|
|
541
|
-
try {
|
|
542
|
-
const config = JSON5.parse(fileContent);
|
|
543
|
-
if (config.pos && typeof config.pos === 'string') {
|
|
544
|
-
const parts = config.pos.split(',').map((s) => parseInt(s.trim(), 10));
|
|
545
|
-
const posObj = { x: parts[0], y: parts[1] };
|
|
546
|
-
if (!isNaN(parts[2])) {
|
|
547
|
-
posObj.screen = parts[2];
|
|
548
|
-
}
|
|
549
|
-
config.pos = posObj;
|
|
550
|
-
}
|
|
551
|
-
return config;
|
|
552
|
-
}
|
|
553
|
-
catch (error) {
|
|
554
|
-
throw new Error(`Failed to parse config file ${filePath}: ${error.message}`);
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
function saveConfigFile(filePath, options) {
|
|
558
|
-
const absolutePath = path.resolve(filePath);
|
|
559
|
-
const config = {};
|
|
560
|
-
if (options.title && options.title !== 'Message')
|
|
561
|
-
config.title = options.title;
|
|
562
|
-
if (options.message)
|
|
563
|
-
config.message = options.message;
|
|
564
|
-
if (options.html)
|
|
565
|
-
config.html = options.html;
|
|
566
|
-
// Save URL without hash fragment (hash is specified via -hash option)
|
|
567
|
-
if (options.url)
|
|
568
|
-
config.url = options.url.split('#')[0];
|
|
569
|
-
if (options.hash)
|
|
570
|
-
config.hash = options.hash;
|
|
571
|
-
if (options.size)
|
|
572
|
-
config.size = options.size;
|
|
573
|
-
if (options.pos) {
|
|
574
|
-
config.pos = { x: options.pos.x, y: options.pos.y };
|
|
575
|
-
if (options.pos.screen !== undefined) {
|
|
576
|
-
config.pos.screen = options.pos.screen;
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
if (options.buttons && options.buttons.length > 0 && JSON.stringify(options.buttons) !== '["OK"]') {
|
|
580
|
-
config.buttons = options.buttons;
|
|
581
|
-
}
|
|
582
|
-
if (options.allowInput)
|
|
583
|
-
config.allowInput = options.allowInput;
|
|
584
|
-
if (options.defaultValue)
|
|
585
|
-
config.defaultValue = options.defaultValue;
|
|
586
|
-
if (options.inputPlaceholder)
|
|
587
|
-
config.inputPlaceholder = options.inputPlaceholder;
|
|
588
|
-
if (options.timeout)
|
|
589
|
-
config.timeout = options.timeout;
|
|
590
|
-
if (options.alwaysOnTop)
|
|
591
|
-
config.alwaysOnTop = options.alwaysOnTop;
|
|
592
|
-
if (options.fullscreen)
|
|
593
|
-
config.fullscreen = options.fullscreen;
|
|
594
|
-
if (options.zoom && options.zoom !== 100)
|
|
595
|
-
config.zoom = options.zoom;
|
|
596
|
-
if (options.icon)
|
|
597
|
-
config.icon = options.icon;
|
|
598
|
-
if (options.dev)
|
|
599
|
-
config.dev = options.dev;
|
|
600
|
-
if (options.debug)
|
|
601
|
-
config.debug = options.debug;
|
|
602
|
-
const jsonContent = JSON.stringify(config, null, 4) + '\n';
|
|
603
|
-
fs.writeFileSync(absolutePath, jsonContent, 'utf-8');
|
|
604
|
-
}
|
|
605
|
-
function showHelp() {
|
|
606
|
-
console.log(HELP_TEXT);
|
|
607
|
-
}
|
|
608
|
-
function showVersion() {
|
|
609
|
-
console.log(packageJson.version);
|
|
610
|
-
}
|
|
611
|
-
// Run main when file is executed (either directly or as a bin script)
|
|
612
|
-
if (import.meta.main) {
|
|
613
|
-
await main();
|
|
614
|
-
}
|
|
76
|
+
await runCli({
|
|
77
|
+
appName: 'msger',
|
|
78
|
+
version: packageJson.version,
|
|
79
|
+
helpText: HELP_TEXT,
|
|
80
|
+
showMessage: showMessageBox
|
|
81
|
+
});
|
package/index.js
CHANGED
|
@@ -3,7 +3,6 @@ export { showMessageBox } from "./shower.js";
|
|
|
3
3
|
// If run directly (e.g., `node .` or `node index.js`), run the CLI
|
|
4
4
|
// @ts-ignore - import.meta.main is available in Node.js 20+
|
|
5
5
|
if (import.meta.main) {
|
|
6
|
-
// Import
|
|
7
|
-
|
|
8
|
-
await main();
|
|
6
|
+
// Import CLI - it runs automatically via top-level await
|
|
7
|
+
import('./cli.js');
|
|
9
8
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/msger",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.173",
|
|
4
4
|
"description": "Fast, lightweight, cross-platform message box - Rust-powered alternative to msgview",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"@types/node": "^24.9.1"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@bobfrankston/msgcommon": "^0.1.
|
|
53
|
+
"@bobfrankston/msgcommon": "^0.1.6",
|
|
54
54
|
"ansi-to-html": "^0.7.2",
|
|
55
55
|
"json5": "^2.2.3"
|
|
56
56
|
},
|
package/shower.d.ts
CHANGED
|
@@ -40,6 +40,7 @@ export interface MessageBoxOptions {
|
|
|
40
40
|
dev?: boolean; /** Open DevTools automatically on startup (default: false) */
|
|
41
41
|
debug?: boolean; /** Return debug information (HTML, size) in result (default: false) */
|
|
42
42
|
showVersion?: boolean; /** Show version in window title (default: false) */
|
|
43
|
+
appUserModelId?: string; /** Windows AppUserModelID for taskbar pinning (internal use) */
|
|
43
44
|
}
|
|
44
45
|
export interface MessageBoxResult {
|
|
45
46
|
button: string;
|