@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 +212 -0
- package/cli.d.ts +3 -0
- package/cli.js +34 -0
- package/cli.js.map +1 -0
- package/cli.ts +40 -0
- package/clihandler.d.ts +10 -0
- package/clihandler.js +158 -0
- package/clihandler.js.map +1 -0
- package/clihandler.ts +165 -0
- package/index.d.ts +26 -0
- package/index.js +61 -0
- package/index.js.map +1 -0
- package/index.ts +89 -0
- package/package.json +55 -0
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
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
|
+
}
|
package/clihandler.d.ts
ADDED
|
@@ -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
|
+
}
|