@bobfrankston/msger 0.1.1

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/README.md ADDED
@@ -0,0 +1,212 @@
1
+ # @bobfrankston/msger
2
+
3
+ Fast, lightweight, cross-platform message box - Rust-powered alternative to `@bobfrankston/msgview`.
4
+
5
+ This package provides a TypeScript/JavaScript API that wraps the native Rust implementation for:
6
+ - **10-20x faster startup** (~50-200ms vs ~2-3s)
7
+ - **50x smaller binary** (~2-5MB vs ~100MB)
8
+ - **5x less memory** (~20-50MB vs ~100-200MB)
9
+ - **Cross-platform**: Windows (WebView2), macOS (WebKit), Linux (WebKitGTK)
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install @bobfrankston/msger
15
+ ```
16
+
17
+ For global CLI usage:
18
+ ```bash
19
+ npm install -g @bobfrankston/msger
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ ### As a Library (Recommended)
25
+
26
+ ```typescript
27
+ import { showMessageBox } from '@bobfrankston/msger';
28
+
29
+ const result = await showMessageBox({
30
+ title: 'Confirm Action',
31
+ message: 'Are you sure you want to proceed?',
32
+ buttons: ['Cancel', 'OK']
33
+ });
34
+
35
+ console.log('User clicked:', result.button);
36
+
37
+ if (result.value) {
38
+ console.log('User entered:', result.value);
39
+ }
40
+ ```
41
+
42
+ ### As a CLI Tool
43
+
44
+ ```bash
45
+ # Simple message
46
+ msger "Hello, World!"
47
+
48
+ # With HTML content
49
+ msger "Enter your name:" "<p>Please provide your <strong>full name</strong></p>"
50
+
51
+ # From JSON stdin
52
+ echo '{"message":"Test","buttons":["Cancel","OK"]}' | msger
53
+ ```
54
+
55
+ ## API
56
+
57
+ ### `showMessageBox(options: MessageBoxOptions): Promise<MessageBoxResult>`
58
+
59
+ Display a message box dialog and wait for user response.
60
+
61
+ #### MessageBoxOptions
62
+
63
+ ```typescript
64
+ interface MessageBoxOptions {
65
+ title?: string; // Window title (default: "Message")
66
+ message: string; // Plain text message (required)
67
+ html?: string; // HTML content (overrides message display)
68
+ width?: number; // Window width in pixels (default: 600)
69
+ height?: number; // Window height in pixels (default: 400)
70
+ buttons?: string[]; // Button labels (default: ["OK"])
71
+ defaultValue?: string; // Default input value
72
+ allowInput?: boolean; // Show input field (default: false)
73
+ }
74
+ ```
75
+
76
+ #### MessageBoxResult
77
+
78
+ ```typescript
79
+ interface MessageBoxResult {
80
+ button: string; // Label of clicked button
81
+ value?: string; // Input value (if allowInput was true)
82
+ closed?: boolean; // True if window was closed
83
+ dismissed?: boolean; // True if dismissed with Escape
84
+ }
85
+ ```
86
+
87
+ ## Examples
88
+
89
+ ### Simple Confirmation Dialog
90
+
91
+ ```typescript
92
+ const result = await showMessageBox({
93
+ title: 'Delete File',
94
+ message: 'Are you sure you want to delete this file?',
95
+ buttons: ['Cancel', 'Delete']
96
+ });
97
+
98
+ if (result.button === 'Delete') {
99
+ // Perform deletion
100
+ }
101
+ ```
102
+
103
+ ### Input Dialog
104
+
105
+ ```typescript
106
+ const result = await showMessageBox({
107
+ title: 'Enter Name',
108
+ message: 'Please enter your name:',
109
+ allowInput: true,
110
+ defaultValue: 'John Doe',
111
+ buttons: ['Cancel', 'OK']
112
+ });
113
+
114
+ if (result.button === 'OK') {
115
+ console.log('Name entered:', result.value);
116
+ }
117
+ ```
118
+
119
+ ### HTML Content
120
+
121
+ ```typescript
122
+ const result = await showMessageBox({
123
+ title: 'Welcome',
124
+ message: 'Welcome message',
125
+ html: `
126
+ <div style="font-family: sans-serif;">
127
+ <h2 style="color: #2563eb;">Welcome!</h2>
128
+ <p>This supports <strong>HTML</strong> content.</p>
129
+ <ul>
130
+ <li>Rich formatting</li>
131
+ <li>Custom styles</li>
132
+ <li>Any HTML elements</li>
133
+ </ul>
134
+ </div>
135
+ `,
136
+ width: 700,
137
+ height: 500,
138
+ buttons: ['Close']
139
+ });
140
+ ```
141
+
142
+ ## Architecture
143
+
144
+ ```
145
+ @bobfrankston/msger (TypeScript wrapper)
146
+ ↓ spawns with JSON via stdin
147
+ @bobfrankston/msger-native (Rust binary)
148
+ ↓ creates native window with webview
149
+ ↓ displays HTML message with buttons
150
+ ↓ outputs JSON result to stdout
151
+ TypeScript wrapper returns Promise
152
+ ```
153
+
154
+ ## Building from Source
155
+
156
+ ```bash
157
+ # Build both Rust binary and TypeScript wrapper
158
+ npm run build
159
+
160
+ # Build only TypeScript (assumes Rust binary exists)
161
+ npm run build:ts
162
+
163
+ # Build only Rust binary
164
+ npm run build:native
165
+
166
+ # Watch mode for TypeScript development
167
+ npm run watch
168
+ ```
169
+
170
+ ## Development
171
+
172
+ The project consists of two parts:
173
+
174
+ 1. **`msger-native/`** - Pure Rust binary (subdirectory)
175
+ 2. **Root directory** - TypeScript wrapper (this project)
176
+
177
+ When you run `npm run build`, it:
178
+ 1. Builds the Rust binary in the `msger-native/` subdirectory
179
+ 2. Copies the binary to `./bin/`
180
+ 3. Compiles TypeScript to JavaScript
181
+
182
+ The binary is bundled with the npm package, so users get everything in one install.
183
+
184
+ ## Features
185
+
186
+ - ✅ Cross-platform native webview
187
+ - ✅ Full HTML/CSS support
188
+ - ✅ Customizable buttons
189
+ - ✅ Input field support
190
+ - ✅ Keyboard shortcuts (Enter, Escape)
191
+ - ✅ Window close detection
192
+ - ✅ TypeScript type definitions
193
+ - ✅ Small binary size (~2-5MB)
194
+ - ✅ Fast startup (~50-200ms)
195
+
196
+ ## Performance Comparison
197
+
198
+ | | **Electron (msgview)** | **Rust (msger)** | **Improvement** |
199
+ |---|---|---|---|
200
+ | **Binary Size** | ~100MB | ~2-5MB | **50x smaller** |
201
+ | **Startup Time** | ~2-3 seconds | ~50-200ms | **10-20x faster** |
202
+ | **Memory Usage** | ~100-200MB | ~20-50MB | **5x less** |
203
+ | **Cross-Platform** | ✅ Win/Mac/Linux | ✅ Win/Mac/Linux | Same |
204
+ | **Full HTML/CSS** | ✅ Chromium | ✅ WebView2/WebKit | Same |
205
+
206
+ ## License
207
+
208
+ ISC
209
+
210
+ ## Author
211
+
212
+ Bob Frankston
package/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export default function main(): Promise<void>;
3
+ //# sourceMappingURL=cli.d.ts.map
package/cli.js ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ import { showMessageBox } from './index.js';
3
+ import { parseCliArgs, showHelp } from './clihandler.js';
4
+ export default async function main() {
5
+ const args = process.argv.slice(2);
6
+ // No arguments - show help
7
+ if (args.length === 0) {
8
+ showHelp();
9
+ process.exit(0);
10
+ }
11
+ // Parse command line arguments
12
+ const { options, showHelp: shouldShowHelp } = parseCliArgs(args);
13
+ if (shouldShowHelp) {
14
+ showHelp();
15
+ process.exit(0);
16
+ }
17
+ try {
18
+ const result = await showMessageBox(options);
19
+ console.log(JSON.stringify(result, null, 2));
20
+ // Exit with code based on result
21
+ if (result.dismissed || result.closed) {
22
+ process.exit(1);
23
+ }
24
+ }
25
+ catch (error) {
26
+ console.error('Error:', error.message);
27
+ process.exit(1);
28
+ }
29
+ }
30
+ // Only run if this file is executed directly
31
+ if (import.meta.main) {
32
+ main();
33
+ }
34
+ //# sourceMappingURL=cli.js.map
package/cli.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEzD,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,IAAI;IAC9B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAEnC,2BAA2B;IAC3B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpB,QAAQ,EAAE,CAAC;QACX,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,+BAA+B;IAC/B,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAEjE,IAAI,cAAc,EAAE,CAAC;QACjB,QAAQ,EAAE,CAAC;QACX,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,IAAI,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,CAAC;QAC7C,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAE7C,iCAAiC;QACjC,IAAI,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YACpC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;IACL,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QACvC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACL,CAAC;AAED,6CAA6C;AAC7C,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IACnB,IAAI,EAAE,CAAC;AACX,CAAC"}
package/cli.ts ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { showMessageBox } from './index.js';
4
+ import { parseCliArgs, showHelp } from './clihandler.js';
5
+
6
+ export default async function main() {
7
+ const args = process.argv.slice(2);
8
+
9
+ // No arguments - show help
10
+ if (args.length === 0) {
11
+ showHelp();
12
+ process.exit(0);
13
+ }
14
+
15
+ // Parse command line arguments
16
+ const { options, showHelp: shouldShowHelp } = parseCliArgs(args);
17
+
18
+ if (shouldShowHelp) {
19
+ showHelp();
20
+ process.exit(0);
21
+ }
22
+
23
+ try {
24
+ const result = await showMessageBox(options);
25
+ console.log(JSON.stringify(result, null, 2));
26
+
27
+ // Exit with code based on result
28
+ if (result.dismissed || result.closed) {
29
+ process.exit(1);
30
+ }
31
+ } catch (error: any) {
32
+ console.error('Error:', error.message);
33
+ process.exit(1);
34
+ }
35
+ }
36
+
37
+ // Only run if this file is executed directly
38
+ if (import.meta.main) {
39
+ main();
40
+ }
@@ -0,0 +1,10 @@
1
+ import { MessageBoxOptions } from './index.js';
2
+ /**
3
+ * Parse command line arguments into message box options
4
+ */
5
+ export declare function parseCliArgs(args: string[]): {
6
+ options: MessageBoxOptions;
7
+ showHelp: boolean;
8
+ };
9
+ export declare function showHelp(): void;
10
+ //# sourceMappingURL=clihandler.d.ts.map
package/clihandler.js ADDED
@@ -0,0 +1,158 @@
1
+ const HELP_TEXT = `
2
+ msger - Fast, lightweight, cross-platform message box (Rust-powered)
3
+
4
+ Usage:
5
+ msger [options] [message words...]
6
+ msger -message "Your message"
7
+ echo '{"message":"text"}' | msger
8
+
9
+ Options:
10
+ -message <text> Specify message text (use quotes for multi-word)
11
+ -title <text> Set window title
12
+ -html <html> Display HTML formatted message
13
+ -buttons <label...> Specify button labels (e.g., -buttons Yes No Cancel)
14
+ -ok Add OK button
15
+ -cancel Add Cancel button
16
+ -input Include an input field
17
+ -default <text> Default value for input field
18
+ -size <width,height> Set window size (e.g., -size 800,600)
19
+ -timeout <seconds> Auto-close after specified seconds
20
+ -help, -?, --help Show this help message
21
+
22
+ Examples:
23
+ msger Hello World
24
+ msger -message "Save changes?" -buttons Yes No Cancel
25
+ msger -message "Enter your name:" -input -default "John Doe"
26
+ msger -html "<h1>Title</h1><p>Formatted content</p>"
27
+ msger -title "Alert" -message "Operation complete"
28
+ echo '{"message":"Test","buttons":["OK"]}' | msger
29
+
30
+ Notes:
31
+ - If no options are provided, all non-option arguments are concatenated as the message
32
+ - Press ESC to dismiss the dialog (returns button: "dismissed")
33
+ - Press Enter to click the last (default) button
34
+ - Default button is OK if no buttons specified
35
+ - Much faster than Electron-based msgview (~50-200ms vs ~2-3s startup)
36
+ `;
37
+ /**
38
+ * Parse command line arguments into message box options
39
+ */
40
+ export function parseCliArgs(args) {
41
+ const cli = {
42
+ buttons: [],
43
+ input: false,
44
+ help: false
45
+ };
46
+ let i = 0;
47
+ const messageWords = [];
48
+ while (i < args.length) {
49
+ const arg = args[i];
50
+ if (arg === '-help' || arg === '--help' || arg === '-?' || arg === '/?') {
51
+ cli.help = true;
52
+ i++;
53
+ }
54
+ else if (arg === '-message' || arg === '--message') {
55
+ cli.message = args[++i];
56
+ i++;
57
+ }
58
+ else if (arg === '-title' || arg === '--title') {
59
+ cli.title = args[++i];
60
+ i++;
61
+ }
62
+ else if (arg === '-html' || arg === '--html') {
63
+ cli.html = args[++i];
64
+ i++;
65
+ }
66
+ else if (arg === '-buttons' || arg === '--buttons') {
67
+ // Collect button labels until next option or end
68
+ i++;
69
+ while (i < args.length && !args[i].startsWith('-')) {
70
+ cli.buttons.push(args[i]);
71
+ i++;
72
+ }
73
+ }
74
+ else if (arg === '-ok' || arg === '--ok') {
75
+ if (!cli.buttons.includes('OK')) {
76
+ cli.buttons.push('OK');
77
+ }
78
+ i++;
79
+ }
80
+ else if (arg === '-cancel' || arg === '--cancel') {
81
+ if (!cli.buttons.includes('Cancel')) {
82
+ cli.buttons.push('Cancel');
83
+ }
84
+ i++;
85
+ }
86
+ else if (arg === '-input' || arg === '--input') {
87
+ cli.input = true;
88
+ i++;
89
+ }
90
+ else if (arg === '-default' || arg === '--default') {
91
+ cli.defaultValue = args[++i];
92
+ i++;
93
+ }
94
+ else if (arg === '-timeout' || arg === '--timeout') {
95
+ cli.timeout = parseInt(args[++i], 10);
96
+ i++;
97
+ }
98
+ else if (arg === '-size' || arg === '--size') {
99
+ const size = args[++i];
100
+ const [w, h] = size.split(',').map(s => parseInt(s.trim(), 10));
101
+ if (w)
102
+ cli.width = w;
103
+ if (h)
104
+ cli.height = h;
105
+ i++;
106
+ }
107
+ else if (!arg.startsWith('-')) {
108
+ // Non-option argument - part of the message
109
+ messageWords.push(arg);
110
+ i++;
111
+ }
112
+ else {
113
+ // Unknown option, skip it
114
+ console.warn(`Unknown option: ${arg}`);
115
+ i++;
116
+ }
117
+ }
118
+ // Build the message
119
+ let message;
120
+ let html;
121
+ if (cli.html) {
122
+ // HTML message specified
123
+ message = cli.html;
124
+ html = cli.html;
125
+ }
126
+ else if (cli.message) {
127
+ // Explicit message flag
128
+ message = cli.message;
129
+ }
130
+ else if (messageWords.length > 0) {
131
+ // Concatenate all non-option arguments
132
+ message = messageWords.join(' ');
133
+ }
134
+ else {
135
+ // No message provided
136
+ message = 'No message provided';
137
+ }
138
+ // Default buttons if none specified
139
+ if (cli.buttons.length === 0) {
140
+ cli.buttons = ['OK'];
141
+ }
142
+ const options = {
143
+ title: cli.title,
144
+ message,
145
+ html,
146
+ buttons: cli.buttons,
147
+ allowInput: cli.input,
148
+ defaultValue: cli.defaultValue,
149
+ width: cli.width,
150
+ height: cli.height,
151
+ timeout: cli.timeout
152
+ };
153
+ return { options, showHelp: cli.help || false };
154
+ }
155
+ export function showHelp() {
156
+ console.log(HELP_TEXT);
157
+ }
158
+ //# sourceMappingURL=clihandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clihandler.js","sourceRoot":"","sources":["clihandler.ts"],"names":[],"mappings":"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"}
package/clihandler.ts ADDED
@@ -0,0 +1,165 @@
1
+ import { MessageBoxOptions } from './index.js';
2
+
3
+ interface CliOptions {
4
+ message?: string;
5
+ title?: string;
6
+ html?: string;
7
+ buttons?: string[];
8
+ input?: boolean;
9
+ width?: number;
10
+ height?: number;
11
+ defaultValue?: string;
12
+ timeout?: number;
13
+ help?: boolean;
14
+ }
15
+
16
+ const HELP_TEXT = `
17
+ msger - Fast, lightweight, cross-platform message box (Rust-powered)
18
+
19
+ Usage:
20
+ msger [options] [message words...]
21
+ msger -message "Your message"
22
+ echo '{"message":"text"}' | msger
23
+
24
+ Options:
25
+ -message <text> Specify message text (use quotes for multi-word)
26
+ -title <text> Set window title
27
+ -html <html> Display HTML formatted message
28
+ -buttons <label...> Specify button labels (e.g., -buttons Yes No Cancel)
29
+ -ok Add OK button
30
+ -cancel Add Cancel button
31
+ -input Include an input field
32
+ -default <text> Default value for input field
33
+ -size <width,height> Set window size (e.g., -size 800,600)
34
+ -timeout <seconds> Auto-close after specified seconds
35
+ -help, -?, --help Show this help message
36
+
37
+ Examples:
38
+ msger Hello World
39
+ msger -message "Save changes?" -buttons Yes No Cancel
40
+ msger -message "Enter your name:" -input -default "John Doe"
41
+ msger -html "<h1>Title</h1><p>Formatted content</p>"
42
+ msger -title "Alert" -message "Operation complete"
43
+ echo '{"message":"Test","buttons":["OK"]}' | msger
44
+
45
+ Notes:
46
+ - If no options are provided, all non-option arguments are concatenated as the message
47
+ - Press ESC to dismiss the dialog (returns button: "dismissed")
48
+ - Press Enter to click the last (default) button
49
+ - Default button is OK if no buttons specified
50
+ - Much faster than Electron-based msgview (~50-200ms vs ~2-3s startup)
51
+ `;
52
+
53
+ /**
54
+ * Parse command line arguments into message box options
55
+ */
56
+ export function parseCliArgs(args: string[]): { options: MessageBoxOptions; showHelp: boolean } {
57
+ const cli: CliOptions = {
58
+ buttons: [],
59
+ input: false,
60
+ help: false
61
+ };
62
+
63
+ let i = 0;
64
+ const messageWords: string[] = [];
65
+
66
+ while (i < args.length) {
67
+ const arg = args[i];
68
+
69
+ if (arg === '-help' || arg === '--help' || arg === '-?' || arg === '/?') {
70
+ cli.help = true;
71
+ i++;
72
+ } else if (arg === '-message' || arg === '--message') {
73
+ cli.message = args[++i];
74
+ i++;
75
+ } else if (arg === '-title' || arg === '--title') {
76
+ cli.title = args[++i];
77
+ i++;
78
+ } else if (arg === '-html' || arg === '--html') {
79
+ cli.html = args[++i];
80
+ i++;
81
+ } else if (arg === '-buttons' || arg === '--buttons') {
82
+ // Collect button labels until next option or end
83
+ i++;
84
+ while (i < args.length && !args[i].startsWith('-')) {
85
+ cli.buttons!.push(args[i]);
86
+ i++;
87
+ }
88
+ } else if (arg === '-ok' || arg === '--ok') {
89
+ if (!cli.buttons!.includes('OK')) {
90
+ cli.buttons!.push('OK');
91
+ }
92
+ i++;
93
+ } else if (arg === '-cancel' || arg === '--cancel') {
94
+ if (!cli.buttons!.includes('Cancel')) {
95
+ cli.buttons!.push('Cancel');
96
+ }
97
+ i++;
98
+ } else if (arg === '-input' || arg === '--input') {
99
+ cli.input = true;
100
+ i++;
101
+ } else if (arg === '-default' || arg === '--default') {
102
+ cli.defaultValue = args[++i];
103
+ i++;
104
+ } else if (arg === '-timeout' || arg === '--timeout') {
105
+ cli.timeout = parseInt(args[++i], 10);
106
+ i++;
107
+ } else if (arg === '-size' || arg === '--size') {
108
+ const size = args[++i];
109
+ const [w, h] = size.split(',').map(s => parseInt(s.trim(), 10));
110
+ if (w) cli.width = w;
111
+ if (h) cli.height = h;
112
+ i++;
113
+ } else if (!arg.startsWith('-')) {
114
+ // Non-option argument - part of the message
115
+ messageWords.push(arg);
116
+ i++;
117
+ } else {
118
+ // Unknown option, skip it
119
+ console.warn(`Unknown option: ${arg}`);
120
+ i++;
121
+ }
122
+ }
123
+
124
+ // Build the message
125
+ let message: string;
126
+ let html: string | undefined;
127
+
128
+ if (cli.html) {
129
+ // HTML message specified
130
+ message = cli.html;
131
+ html = cli.html;
132
+ } else if (cli.message) {
133
+ // Explicit message flag
134
+ message = cli.message;
135
+ } else if (messageWords.length > 0) {
136
+ // Concatenate all non-option arguments
137
+ message = messageWords.join(' ');
138
+ } else {
139
+ // No message provided
140
+ message = 'No message provided';
141
+ }
142
+
143
+ // Default buttons if none specified
144
+ if (cli.buttons!.length === 0) {
145
+ cli.buttons = ['OK'];
146
+ }
147
+
148
+ const options: MessageBoxOptions = {
149
+ title: cli.title,
150
+ message,
151
+ html,
152
+ buttons: cli.buttons,
153
+ allowInput: cli.input,
154
+ defaultValue: cli.defaultValue,
155
+ width: cli.width,
156
+ height: cli.height,
157
+ timeout: cli.timeout
158
+ };
159
+
160
+ return { options, showHelp: cli.help || false };
161
+ }
162
+
163
+ export function showHelp(): void {
164
+ console.log(HELP_TEXT);
165
+ }
package/index.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ export interface MessageBoxOptions {
2
+ title?: string;
3
+ message: string;
4
+ html?: string;
5
+ width?: number;
6
+ height?: number;
7
+ buttons?: string[];
8
+ defaultValue?: string;
9
+ allowInput?: boolean;
10
+ timeout?: number;
11
+ }
12
+ export interface MessageBoxResult {
13
+ button: string;
14
+ value?: string;
15
+ form?: Record<string, any>;
16
+ closed?: boolean;
17
+ dismissed?: boolean;
18
+ timeout?: boolean;
19
+ }
20
+ /**
21
+ * Show a message box dialog using native Rust implementation
22
+ * @param options Message box configuration
23
+ * @returns Promise that resolves with user's response
24
+ */
25
+ export declare function showMessageBox(options: MessageBoxOptions): Promise<MessageBoxResult>;
26
+ //# sourceMappingURL=index.d.ts.map
package/index.js ADDED
@@ -0,0 +1,61 @@
1
+ import { spawn } from 'child_process';
2
+ import { platform } from 'os';
3
+ import path from 'path';
4
+ /**
5
+ * Show a message box dialog using native Rust implementation
6
+ * @param options Message box configuration
7
+ * @returns Promise that resolves with user's response
8
+ */
9
+ export async function showMessageBox(options) {
10
+ return new Promise((resolve, reject) => {
11
+ // Determine the binary name based on platform
12
+ const binaryName = platform() === 'win32' ? 'msger.exe' : 'msger';
13
+ const binaryPath = path.join(import.meta.dirname, 'bin', binaryName);
14
+ // Spawn the Rust binary
15
+ const child = spawn(binaryPath, [], {
16
+ stdio: ['pipe', 'pipe', 'pipe']
17
+ });
18
+ let stdout = '';
19
+ let stderr = '';
20
+ // Collect stdout
21
+ child.stdout.on('data', (data) => {
22
+ stdout += data.toString();
23
+ });
24
+ // Collect stderr
25
+ child.stderr.on('data', (data) => {
26
+ stderr += data.toString();
27
+ });
28
+ // Handle process completion
29
+ child.on('close', (code) => {
30
+ if (code !== 0) {
31
+ reject(new Error(`msger exited with code ${code}: ${stderr}`));
32
+ return;
33
+ }
34
+ try {
35
+ const result = JSON.parse(stdout.trim());
36
+ resolve(result);
37
+ }
38
+ catch (error) {
39
+ reject(new Error(`Failed to parse result: ${error.message}\nOutput: ${stdout}`));
40
+ }
41
+ });
42
+ // Handle process errors
43
+ child.on('error', (error) => {
44
+ reject(new Error(`Failed to spawn msger: ${error.message}`));
45
+ });
46
+ // Send options to stdin
47
+ try {
48
+ child.stdin.write(JSON.stringify(options));
49
+ child.stdin.end();
50
+ }
51
+ catch (error) {
52
+ reject(new Error(`Failed to write to stdin: ${error.message}`));
53
+ }
54
+ });
55
+ }
56
+ // If run directly (not imported), invoke CLI
57
+ if (import.meta.main) {
58
+ const { default: cliMain } = await import('./cli.js');
59
+ await cliMain();
60
+ }
61
+ //# sourceMappingURL=index.js.map
package/index.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AAC9B,OAAO,IAAI,MAAM,MAAM,CAAC;AAuBxB;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAA0B;IAC3D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACnC,8CAA8C;QAC9C,MAAM,UAAU,GAAG,QAAQ,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC;QAClE,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;QAErE,wBAAwB;QACxB,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,EAAE,EAAE,EAAE;YAChC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;SAClC,CAAC,CAAC;QAEH,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,MAAM,GAAG,EAAE,CAAC;QAEhB,iBAAiB;QACjB,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YAC7B,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC9B,CAAC,CAAC,CAAC;QAEH,iBAAiB;QACjB,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YAC7B,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC9B,CAAC,CAAC,CAAC;QAEH,4BAA4B;QAC5B,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;YACvB,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;gBACb,MAAM,CAAC,IAAI,KAAK,CAAC,0BAA0B,IAAI,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC;gBAC/D,OAAO;YACX,CAAC;YAED,IAAI,CAAC;gBACD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAqB,CAAC;gBAC7D,OAAO,CAAC,MAAM,CAAC,CAAC;YACpB,CAAC;YAAC,OAAO,KAAU,EAAE,CAAC;gBAClB,MAAM,CAAC,IAAI,KAAK,CAAC,2BAA2B,KAAK,CAAC,OAAO,aAAa,MAAM,EAAE,CAAC,CAAC,CAAC;YACrF,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,wBAAwB;QACxB,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YACxB,MAAM,CAAC,IAAI,KAAK,CAAC,0BAA0B,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACjE,CAAC,CAAC,CAAC;QAEH,wBAAwB;QACxB,IAAI,CAAC;YACD,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;YAC3C,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;QACtB,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YAClB,MAAM,CAAC,IAAI,KAAK,CAAC,6BAA6B,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACpE,CAAC;IACL,CAAC,CAAC,CAAC;AACP,CAAC;AAED,6CAA6C;AAC7C,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IACnB,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC;IACtD,MAAM,OAAO,EAAE,CAAC;AACpB,CAAC"}
package/index.ts ADDED
@@ -0,0 +1,89 @@
1
+ import { spawn } from 'child_process';
2
+ import { platform } from 'os';
3
+ import path from 'path';
4
+
5
+ export interface MessageBoxOptions {
6
+ title?: string;
7
+ message: string;
8
+ html?: string;
9
+ width?: number;
10
+ height?: number;
11
+ buttons?: string[];
12
+ defaultValue?: string;
13
+ allowInput?: boolean;
14
+ timeout?: number;
15
+ }
16
+
17
+ export interface MessageBoxResult {
18
+ button: string;
19
+ value?: string;
20
+ form?: Record<string, any>;
21
+ closed?: boolean;
22
+ dismissed?: boolean;
23
+ timeout?: boolean;
24
+ }
25
+
26
+ /**
27
+ * Show a message box dialog using native Rust implementation
28
+ * @param options Message box configuration
29
+ * @returns Promise that resolves with user's response
30
+ */
31
+ export async function showMessageBox(options: MessageBoxOptions): Promise<MessageBoxResult> {
32
+ return new Promise((resolve, reject) => {
33
+ // Determine the binary name based on platform
34
+ const binaryName = platform() === 'win32' ? 'msger.exe' : 'msger';
35
+ const binaryPath = path.join(import.meta.dirname, 'bin', binaryName);
36
+
37
+ // Spawn the Rust binary
38
+ const child = spawn(binaryPath, [], {
39
+ stdio: ['pipe', 'pipe', 'pipe']
40
+ });
41
+
42
+ let stdout = '';
43
+ let stderr = '';
44
+
45
+ // Collect stdout
46
+ child.stdout.on('data', (data) => {
47
+ stdout += data.toString();
48
+ });
49
+
50
+ // Collect stderr
51
+ child.stderr.on('data', (data) => {
52
+ stderr += data.toString();
53
+ });
54
+
55
+ // Handle process completion
56
+ child.on('close', (code) => {
57
+ if (code !== 0) {
58
+ reject(new Error(`msger exited with code ${code}: ${stderr}`));
59
+ return;
60
+ }
61
+
62
+ try {
63
+ const result = JSON.parse(stdout.trim()) as MessageBoxResult;
64
+ resolve(result);
65
+ } catch (error: any) {
66
+ reject(new Error(`Failed to parse result: ${error.message}\nOutput: ${stdout}`));
67
+ }
68
+ });
69
+
70
+ // Handle process errors
71
+ child.on('error', (error) => {
72
+ reject(new Error(`Failed to spawn msger: ${error.message}`));
73
+ });
74
+
75
+ // Send options to stdin
76
+ try {
77
+ child.stdin.write(JSON.stringify(options));
78
+ child.stdin.end();
79
+ } catch (error: any) {
80
+ reject(new Error(`Failed to write to stdin: ${error.message}`));
81
+ }
82
+ });
83
+ }
84
+
85
+ // If run directly (not imported), invoke CLI
86
+ if (import.meta.main) {
87
+ const { default: cliMain } = await import('./cli.js');
88
+ await cliMain();
89
+ }
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@bobfrankston/msger",
3
+ "version": "0.1.1",
4
+ "description": "Fast, lightweight, cross-platform message box - Rust-powered alternative to msgview",
5
+ "type": "module",
6
+ "main": "./index.js",
7
+ "bin": {
8
+ "msger": "./cli.js"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "import": "./index.js",
13
+ "types": "./index.d.ts"
14
+ }
15
+ },
16
+ "scripts": {
17
+ "build": "npm run build:ts && npm run build:native",
18
+ "build:native": "cd msger-native && npm run build",
19
+ "build:ts": "tsc",
20
+ "watch": "tsc -w",
21
+ "clean": "rm -rf *.js *.d.ts *.js.map bin && cd msger-native && cargo clean",
22
+ "test": "node test.js",
23
+ "prepublishOnly": "npm run build",
24
+ "prerelease:local": "git add -A && (git diff-index --quiet HEAD || git commit -m \"Pre-release commit\")",
25
+ "preversion": "npm run build && git add -A",
26
+ "postversion": "git push && git push --tags",
27
+ "release": "npm run prerelease:local && npm version patch && npm publish",
28
+ "installer": "npm run release && npm install -g @bobfrankston/msger && wsl npm install -g -force @bobfrankston/msger"
29
+ },
30
+ "keywords": [
31
+ "message-box",
32
+ "dialog",
33
+ "webview",
34
+ "rust",
35
+ "native",
36
+ "cross-platform",
37
+ "typescript"
38
+ ],
39
+ "author": "Bob Frankston",
40
+ "license": "ISC",
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^24.9.1"
46
+ },
47
+ "files": [
48
+ "*.js",
49
+ "*.d.ts",
50
+ "*.js.map",
51
+ "*.ts",
52
+ "bin/",
53
+ "README.md"
54
+ ]
55
+ }