# @bobfrankston/msger

Fast, lightweight message boxes for Node.js applications.

Display native message boxes, dialogs, and HTML content with a simple JavaScript API. Works on Windows, Linux (x64), and Linux (ARM64/Raspberry Pi).

## Quick Links

- **[Installation](#installation)** - Get started
- **[Command Line Usage](#command-line-usage)** - Use from shell scripts
- **[Node.js API](#nodejs-api-reference)** - Use from JavaScript/TypeScript
- **[For Developers](DEVELOPERS.md)** - Building & architecture
- **[Roadmap](TODO.md)** - Technical status & planned features

## Installation

```bash
npm install @bobfrankston/msger
```

For global CLI usage:
```bash
npm install -g @bobfrankston/msger
```

**Supported Platforms:**
- Windows (x64)
- Linux (x64)
- Linux (ARM64 / Raspberry Pi)

---

# Command Line Usage

The `msger` command-line tool provides a quick way to display message boxes from shell scripts, batch files, or terminal.

## Basic Examples

```bash
# Simple message
msger -message "Hello, World!"
msger Hello World  # shorthand - words become message

# With HTML content
msger -html "<h1>Welcome!</h1><p>This is <strong>HTML</strong></p>"

# Colored text with inline styles
msger -html "<span style='color:red;background-color:yellow'>Alert!</span>"

# Load a URL (web page or local file)
msger -url "https://example.com"
msger -url "file:///path/to/page.html"

# Render a local markdown file — use the mdview wrapper (msger doesn't auto-render .md)
mdview todo.md

# Display arbitrary HTML as a complete page (no template, no buttons) — used by mdview
msger -raw -html "<!doctype html><html>...</html>"

# Load URL with hash fragment
msger -url "https://example.com" -hash section1
msger -url "https://example.com" -hash "#intro"

# With input field and placeholder
msger -message "Enter your name:" -input "Your name here" -default "John"

# Custom buttons
msger -message "Save changes?" -buttons Yes No Cancel

# With custom size and position
msger -message "Hello" -size 800,600 -pos 100,100

# Always on top with zoom
msger -message "Important!" -ontop -zoom 150

# Detached URL browser (stays open after script exits)
msger -url "https://example.com" -detach

# Fullscreen mode
msger -fullscreen -url "https://example.com"

# Multi-monitor (Windows only - show on second screen)
msger -message "Second screen" -pos 0,100 -screen 1

# Auto-close timeout
msger -message "Closing in 5 seconds..." -timeout 5

# Open with DevTools for debugging (automatically opens DevTools panel)
msger -url "https://example.com" -dev

# See all options
msger --help
```

### Headless mode (`-noshow`) — use the WebView as a CSS/JS engine

`-noshow` doesn't open a visible window, but the WebView still loads HTML, runs `<script>` blocks, and resolves CSS — so an embedded script can query the engine and post a result back via `window.ipc.postMessage`. Combined with `-result <fieldname>` to print just one field, this lets you use msger as a headless rendering engine for things like `contrast-color()`, `getComputedStyle()`, and `CSS.supports()`.

```bash
# Resolve contrast-color() — needs a DOM element + getComputedStyle because
# CSS functions only resolve through the cascade.
msger -noshow -html '<div id=x style="color:contrast-color(steelblue)"></div>
<script>
  requestAnimationFrame(() => requestAnimationFrame(() => {
    window.ipc.postMessage(JSON.stringify({
      button: "r",
      value: getComputedStyle(document.getElementById("x")).color
    }));
  }));
</script>' -result value
# → rgb(255, 255, 255)
```

The Node API works the same way — pass `noshow: true` and `html: "..."` to `showMessageBox`, then read `result.value` from the resolved promise.

Notes:
- `-result <fieldname>` prints just that field's value (string as-is, other types JSON-stringified) instead of the full `MessageBoxResult` JSON. Useful for shell consumption.
- The two `requestAnimationFrame` calls let layout settle before reading computed style — needed for `contrast-color`, gradients, etc.
- The original `-noshow -save` behavior is unchanged: it still exits after saving without ever creating a WebView.

## JSON Configuration Files

You can store message box configuration in a JSON file for reuse. JSON files support comments (JSON5 format).

```bash
# Load configuration from file
msger -load config.json

# Override specific values from loaded config
msger -load config.json -title "New Title" -size 800,600

# Save current command-line options to a file (only saves explicitly specified values)
msger -title "My Dialog" -message "Hello" -buttons Yes No -save my-config.json

# Save config without showing the message box (uses -noshow)
msger -title "My Dialog" -message "Hello" -buttons Yes No -save my-config -noshow

# Load base config, override, and save the overrides
msger -load base.json -title "Modified" -save modified.json

# Save and show (saves config then displays the message box)
msger -title "Test" -message "Saving config..." -save test.json
```

**Notes:**
- The `-save` option saves only the explicitly specified command-line arguments, not derived defaults. This keeps config files minimal and allows defaults to evolve.
- `-noshow` with `-save` writes the config and exits without ever creating a WebView. Without `-save`, `-noshow` creates the WebView and runs scripts but doesn't open a visible window — useful for headless CSS/JS resolution (see "Headless mode" below).
- File extensions: If you don't specify `.json`, it will be added automatically (e.g., `myconfig` becomes `myconfig.json`).

### Complete JSON Structure

```json
{
    "title": "Window Title",
    "message": "Plain text message (supports ANSI color codes)",
    "html": "<h1>HTML content</h1><p>Rich formatting</p>",
    "url": "https://example.com",
    "hash": "section1",
    "size": {
        "width": 600,
        "height": 400
    },
    "pos": {
        "x": 100,
        "y": 100,
        "screen": 0
    },
    "buttons": ["Cancel", "OK"],
    "defaultValue": "default input text",
    "inputPlaceholder": "Enter text here...",
    "allowInput": false,
    "timeout": 30,
    "autoSize": false,
    "alwaysOnTop": false,
    "fullscreen": false,
    "zoom": 100,
    "debug": false,
    "icon": "path/to/icon.png",
    "dev": false
}
```

### Field Reference

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `title` | string | "Message" | Window title |
| `message` | string | null | Plain text message (supports ANSI color codes) |
| `html` | string | null | HTML content to display |
| `url` | string | null | URL to load (web or file://) |
| `hash` | string | null | Hash fragment to append to URL (requires url). Leading # is optional. |
| `size` | object | `{"width": 600, "height": 400}` | Window size |
| `size.width` | number | 600 (or 1024 for URLs) | Window width in pixels |
| `size.height` | number | 400 (or 768 for URLs) | Window height in pixels |
| `pos` | object | null | Window position |
| `pos.x` | number | - | X coordinate in pixels |
| `pos.y` | number | - | Y coordinate in pixels |
| `pos.screen` | number | null | [Windows only] Screen index (0=primary, 1=second, etc.) |
| `buttons` | string[] | ["OK"] | Button labels |
| `defaultValue` | string | null | Default input value |
| `inputPlaceholder` | string | null | Placeholder text for input field |
| `allowInput` | boolean | false | Show input field |
| `timeout` | number | null | Auto-close after N seconds |
| `autoSize` | boolean | true when no size | Auto-resize window to fit content |
| `alwaysOnTop` | boolean | false | Keep window on top |
| `fullscreen` | boolean | false | Start in fullscreen mode |
| `zoom` | number | 100 | Initial zoom level (100=100%, 150=150%) |
| `debug` | boolean | false | Return debug info in result |
| `noResult` | boolean | false | Suppress JSON result on stdout (errors still go to stderr) |
| `icon` | string | null | Path to window icon (.png, .ico) |
| `dev` | boolean | false | Open DevTools automatically (for debugging HTML/URL content) |

**Content Priority:** Only one content type will be displayed (in order):
1. `url` - If provided, loads URL (ignores message/html)
2. `html` - If provided, displays HTML (ignores message)
3. `message` - Plain text with ANSI color support

**Backward Compatibility:** The old `width` and `height` fields (at the top level) are still supported for backward compatibility, but the `size` object is preferred. If both are provided, `size` takes precedence.

### JSON Examples

**Simple dialog:**
```json
{
    "title": "Confirm",
    "message": "Are you sure?",
    "buttons": ["Cancel", "OK"]
}
```

**Input dialog:**
```json
{
    "title": "Enter Name",
    "message": "Please enter your name:",
    "allowInput": true,
    "inputPlaceholder": "Your name here",
    "defaultValue": "John Doe",
    "buttons": ["Cancel", "Submit"]
}
```

**HTML content:**
```json
{
    "title": "Welcome",
    "html": "<h1>Welcome!</h1><p>Getting started...</p>",
    "size": {
        "width": 700,
        "height": 500
    }
}
```

**Positioned window on second monitor:**
```json
{
    "message": "Second screen",
    "pos": {
        "x": 0,
        "y": 100,
        "screen": 1
    }
}
```

## CLI Options Reference

```
Usage: msger [options] [message words...]

Options:
  -message, --message <text>      Plain text message
  -title, --title <text>          Window title
  -html, --html <html>            HTML content
  -url, --url <url>               URL to load (web or file://)
  -hash, --hash <fragment>        Hash fragment to append to URL (requires -url). Leading # optional.
  -buttons, --buttons <labels>    Button labels (e.g., -buttons Yes No Cancel)
  -ok, --ok                       Add OK button
  -cancel, --cancel               Add Cancel button
  -input, --input [placeholder]   Include input field with optional placeholder text
  -default, --default <text>      Default value for input field
  -size, --size <width,height>    Window size (e.g., -size 800,600)
  -pos, --pos <x,y>               Window position (e.g., -pos 100,200)
  -screen, --screen <number>      [Windows only] Screen index (0=primary, 1=second, etc.)
  -zoom, --zoom <percent>         Zoom level as percentage (e.g., -zoom 150 for 150%)
  -timeout, --timeout <seconds>   Auto-close after specified seconds
  -ontop, --ontop                 Keep window always on top
  -detach, --detach               Launch window independently (parent returns immediately)
  -fullscreen, --fullscreen       Start window in fullscreen mode (F11 to toggle, Escape to exit)
  -raw, --raw                     With -html: load HTML as-is, no msger template/buttons (alias: -full)
  -debug, --debug                 Return debug info (HTML, size, autoSize) in result
  -noshow, --noshow               Don't display the window. With -save, exits after saving.
                                  Otherwise the WebView still loads + runs scripts so a page
                                  can post a result via window.ipc.postMessage; only the
                                  window isn't shown. Useful for headless CSS/JS resolution.
  -noresult, --noresult           Suppress JSON result output on stdout (errors still go to stderr)
  -result, --result [fieldname]   Re-enable result output. With <fieldname>, print just that
                                  field (e.g. -result value → bare string). Strings print as-is,
                                  other types JSON-stringify. Missing field → exit 2.
  -v, -version, --version         Show version number
  -help, -?, --help               Show help message

Keyboard Shortcuts:
  - Enter: Click default (last) button
  - Escape: Dismiss dialog or exit fullscreen
  - F11: Toggle fullscreen mode
  - F12: Open developer tools (DevTools/Inspect)
  - Ctrl+Wheel/Plus/Minus: Zoom in/out
  - Ctrl+0: Reset zoom to 100%

Debug Mode:
  - Set environment variable MSGER_DEBUG=1 for diagnostic output
  - Shows: startup info, keyboard events, IPC messages, button clicks
  - Example (PowerShell): $env:MSGER_DEBUG=1; msger "Test"
  - Zero performance impact when disabled

Notes:
  - If no options provided, all non-option arguments are concatenated as message
  - Press ESC to dismiss (returns button: "dismissed")
  - Press Enter to click last (default) button
  - OK button is green by default
  - URLs default to 1024x768 window size
  - Window title automatically updates from loaded page's <title> tag when using -url
  - Windows auto-size to fit content when size not specified
  - Runtime zoom: Ctrl+Wheel, Ctrl+Plus, Ctrl+Minus
```

---

# Node.js API Reference

Use msger in your Node.js or TypeScript applications with a simple Promise-based API.

## Quick Start

```typescript
import { showMessageBox } from '@bobfrankston/msger';

const result = await showMessageBox({
    title: 'Confirm Action',
    message: 'Are you sure you want to proceed?',
    buttons: ['Cancel', 'OK']
});

console.log('User clicked:', result.button);

if (result.value) {
    console.log('User entered:', result.value);
}
```

### Programmatic Close API (New!)

Close message boxes programmatically from the parent process:

**Handle-based API (Recommended):**
```typescript
import { showMessageBoxEx } from '@bobfrankston/msger';

const handle = showMessageBoxEx({
    message: 'Processing...',
    buttons: ['Cancel']
});

console.log('Dialog PID:', handle.pid);

// Close after 5 seconds
setTimeout(() => {
    handle.close();
}, 5000);

// Wait for result
const result = await handle.result;
// Result: {button: 'closed', closed: true} if closed programmatically
// Result: {button: 'Cancel'} if user clicked Cancel
```

**Standalone close function:**
```typescript
import { showMessageBoxEx, closeMessageBox } from '@bobfrankston/msger';

const handle = showMessageBoxEx({ message: 'Working...' });
const pid = handle.pid;

// Close by PID from anywhere
closeMessageBox(pid);
```

**Use cases:**
- Progress dialogs that auto-close when task completes
- Timeout-based notifications
- Managing multiple dialogs by PID
- Detached dialogs closed remotely

See [CLOSE-API.md](CLOSE-API.md) for complete documentation.

## API Functions

### `showMessageBox(options: MessageBoxOptions): Promise<MessageBoxResult>`

Display a message box dialog and wait for user response.

#### MessageBoxOptions

```typescript
interface MessageBoxOptions {
    title?: string;           // Window title (default: "Message")
    message?: string;         // Plain text message (optional if url or html provided)
    html?: string;            // HTML content to display
    url?: string;             // URL to load (web URL or file:// path)
    hash?: string;            // Hash fragment to append to URL (requires url). Leading # optional.
    size?: {                  // Window size in pixels
        width: number;        // Window width (default: 600, or 1024 for URLs)
        height: number;       // Window height (default: 400, or 768 for URLs)
    };
    pos?: {                   // Window position
        x: number;            // X coordinate in pixels
        y: number;            // Y coordinate in pixels
        screen?: number;      // [Windows only] Screen index (0=primary, 1=second, etc.)
    };
    buttons?: string[];       // Button labels (default: ["OK"])
    defaultValue?: string;    // Default input value
    inputPlaceholder?: string; // Placeholder text (gray hint) for input field
    allowInput?: boolean;     // Show input field (default: false)
    autoSize?: boolean;       // Auto-resize window to fit content (default: true when no size specified)
    alwaysOnTop?: boolean;    // Keep window on top of other windows (default: false)
    zoom?: number;            // Initial zoom level (100=100%, 150=150%, 50=50%)
    timeout?: number;         // Auto-close after N seconds
    detach?: boolean;         // Launch detached from parent process
    fullscreen?: boolean;     // Start window in fullscreen mode (F11 to toggle)
    rawHtml?: boolean;        // With html: load HTML as-is, no msger template/buttons (used by mdview). CLI: -raw
    debug?: boolean;          // Return debug info (HTML, size, autoSize) in result
    noResult?: boolean;       // Suppress JSON result on stdout (CLI: -noresult, -result re-enables)
}
```

#### MessageBoxResult

```typescript
interface MessageBoxResult {
    button: string;       // Label of clicked button
    value?: string;       // Input value (if allowInput was true)
    form?: object;        // Form data (if form elements present)
    closed?: boolean;     // True if closed programmatically via handle.close()
    dismissed?: boolean;  // True if user pressed Escape
    timeout?: boolean;    // True if closed due to timeout
    bounds?: {            // Virtual-desktop window geometry at close (pixels).
                          // x,y are single-plane coords across all monitors — NOT
                          // monitor-relative + screen index. Feed straight back into
                          // `-pos bounds.x,bounds.y`. See msgcommon/README.md.
        x: number;
        y: number;
        width: number;
        height: number;
    };
    debug?: {             // Debug info (if debug option was true)
        html: string;     // Generated HTML content
        width: number;    // Window width
        height: number;   // Window height
        autoSize: boolean; // Whether auto-sizing is enabled
    };
}
```

## API Examples

### Simple Confirmation Dialog

```typescript
const result = await showMessageBox({
    title: 'Delete File',
    message: 'Are you sure you want to delete this file?',
    buttons: ['Cancel', 'Delete']
});

if (result.button === 'Delete') {
    // Perform deletion
}
```

### Input Dialog

```typescript
const result = await showMessageBox({
    title: 'Enter Name',
    message: 'Please enter your name:',
    allowInput: true,
    inputPlaceholder: 'Your name here',
    defaultValue: 'John Doe',
    buttons: ['Cancel', 'OK']
});

if (result.button === 'OK') {
    console.log('Name entered:', result.value);
}
```

### HTML Content

```typescript
const result = await showMessageBox({
    title: 'Welcome',
    html: `
        <div style="font-family: sans-serif;">
            <h2 style="color: #2563eb;">Welcome!</h2>
            <p>This supports <strong>HTML</strong> content.</p>
            <ul>
                <li>Rich formatting</li>
                <li>Custom styles</li>
                <li>Any HTML elements</li>
            </ul>
        </div>
    `,
    size: { width: 700, height: 500 },
    buttons: ['Close']
});
```

### ANSI Color Codes

Plain text messages automatically support ANSI color escape sequences, which are converted to HTML with proper colors:

```typescript
const result = await showMessageBox({
    title: 'Color Test',
    message: '\x1b[31mRed text\x1b[0m\n\x1b[32mGreen text\x1b[0m\n\x1b[1m\x1b[34mBold blue\x1b[0m',
    buttons: ['OK']
});
```

Supported ANSI codes:
- **Colors**: Red (31), Green (32), Yellow (33), Blue (34), Magenta (35), Cyan (36), White (37)
- **Styles**: Bold (1), Underline (4), Reset (0)
- **Backgrounds**: 40-47 (same color numbers as foreground)

CLI example:
```bash
echo -e "\x1b[31mError:\x1b[0m Something went wrong" | msger
```

### Display a Web Page

```typescript
// Load external website
// Note: Window title automatically updates from page's <title> tag
await showMessageBox({
    url: 'https://github.com/BobFrankston/msger',
    size: { width: 1024, height: 768 }
});

// Load local HTML file
await showMessageBox({
    url: 'file:///path/to/help.html',
    size: { width: 800, height: 600 }
});

// Load with DevTools opened automatically (for debugging)
await showMessageBox({
    url: 'https://example.com',
    dev: true,  // Opens DevTools automatically
    size: { width: 1024, height: 768 }
});
```

### Auto-Close with Timeout

```typescript
// Show notification that auto-closes after 3 seconds
// A translucent countdown indicator appears in the top-right corner
const result = await showMessageBox({
    title: 'Notification',
    message: 'File saved successfully!',
    timeout: 3,
    buttons: ['OK']
});

if (result.timeout) {
    console.log('Notification auto-closed');
}
```

The countdown timer displays as a subtle yellow badge in the top-right corner, showing the remaining seconds (e.g., "10s", "9s", "8s"...).

### HTML Forms

```typescript
const result = await showMessageBox({
    title: 'User Registration',
    html: `
        <form>
            <label>Name: <input name="name" required /></label><br>
            <label>Email: <input name="email" type="email" required /></label><br>
            <label>Age: <input name="age" type="number" /></label>
        </form>
    `,
    buttons: ['Cancel', 'Submit']
});

if (result.button === 'Submit' && result.form) {
    console.log('Name:', result.form.name);
    console.log('Email:', result.form.email);
    console.log('Age:', result.form.age);
}
```

### Window Positioning

```typescript
// Position at specific coordinates
await showMessageBox({
    message: 'Positioned window',
    pos: { x: 100, y: 100 }
});

// Position on second monitor (Windows only)
await showMessageBox({
    message: 'On second screen',
    pos: { x: 0, y: 100, screen: 1 }
});
```

### Always On Top & Zoom

```typescript
// Keep window on top with 150% zoom
await showMessageBox({
    title: 'Important Alert',
    message: 'This window stays on top!',
    alwaysOnTop: true,
    zoom: 150
});
```

### Auto-Sizing Window

```typescript
// Window automatically sizes to fit content
await showMessageBox({
    message: 'Short message',
    autoSize: true  // default when size not specified
});
```

### Detached URL Browser

```typescript
// Launch URL window that stays open after script exits
await showMessageBox({
    url: 'https://example.com',
    size: { width: 1200, height: 800 },
    detach: true,
    alwaysOnTop: true
});
// Script continues/exits immediately, window stays open
```

### Fullscreen Mode

```typescript
// Launch window in fullscreen mode (like F11 in browser)
await showMessageBox({
    url: 'https://example.com',
    fullscreen: true
});
// Users can press F11 to toggle fullscreen or Escape to exit

// Fullscreen with message
await showMessageBox({
    message: 'Full screen presentation',
    fullscreen: true,
    buttons: ['Close']
});
```

## Features

- ✅ Display plain text, HTML content, or load URLs
- ✅ Full HTML/CSS support
- ✅ ANSI color codes (colored terminal output)
- ✅ Customizable buttons
- ✅ Input fields and forms
- ✅ Auto-close with timeout
- ✅ Window positioning and sizing
- ✅ Multi-monitor support (Windows)
- ✅ Always on top mode
- ✅ Zoom control
- ✅ Fullscreen mode
- ✅ TypeScript type definitions
- ✅ Keyboard shortcuts (Enter, Escape, F11, F12)

## Why msger?

- **Fast** - Opens in under 200ms
- **Small** - Only a few MB installed
- **Simple** - Easy Promise-based API
- **Flexible** - Plain text, HTML, or load URLs
- **Cross-platform** - Works on Windows and Linux

For technical details and performance metrics, see [DEVELOPERS.md](DEVELOPERS.md).

## msger vs msgview

This package is the **Rust/wry-based** implementation. There's also **[@bobfrankston/msgview](https://www.npmjs.com/package/@bobfrankston/msgview)** - an Electron-based alternative with the same API.

| Aspect | msger (this) | msgview |
|--------|--------------|---------|
| **Startup** | ~50-200ms | ~2-3 seconds |
| **Size** | ~5-10MB | ~200MB |
| **Technology** | Rust + wry (WebView2/webkit2gtk) | Electron |
| **Pi/Linux** | ❌ Rendering issues | ✅ Perfect |
| **Windows/WSL** | ✅ Works | ✅ Works |

### When to use msger (this package)
- Speed is critical (CLI tools, automation)
- Small binary size matters
- Windows or WSL environment

### When to use msgview
- Running on Raspberry Pi or Linux
- Need reliable cross-platform rendering
- Prefer Electron ecosystem

### window.msgapi

msger injects `window.msgapi` into loaded pages via `msger-api.js`, providing window control, UDP networking, and HTTP fetch. File system and shell operations are not yet implemented in msger.

See **[msgapidefs README](../msgapidefs/README.md)** for the full `window.msgapi` API reference, implementation status, and security notes. APIs are experimental and subject to change.

---

# Additional Information

## Important Notes

### Taskbar Icons

**Icon Behavior Differences:**
- **Window Title Bar**: Shows custom icon from `-icon` flag or JSON config
- **Windows Taskbar**: Shows the embedded msger.exe icon (cannot be changed at runtime)

This is a Windows limitation for native applications. The taskbar icon comes from the executable's embedded resource, not the runtime window icon. In contrast, Electron-based msgview can set taskbar icons dynamically.

**Workaround:** The planned `-pin` feature with AppUserModelID will allow each pinned shortcut to have its own taskbar icon.

### Security

⚠️ **msger is designed for displaying trusted, friendly content** (local apps, your HTML files). It is NOT a secure sandbox for untrusted/hostile web content. Use `-htmlfrom` and `-url` with trusted sources only.

### msger JavaScript API

**Minimal API** - Most functionality uses native browser APIs:
- `msger.isAvailable()` - Feature detection (returns true in msger)
- `msger.close()` - Same as `window.close()` (native API preferred)

**Recommendation:** Use native browser APIs (`localStorage`, `window.close()`) for compatibility. Code will work in any browser context, not just msger.

## For Developers

- **Building & Architecture**: See [DEVELOPERS.md](DEVELOPERS.md)
- **Technical Status & Roadmap**: See [TODO.md](TODO.md)
- **Native Binary Documentation**: See [msger-native/README.md](msger-native/README.md)

## License

ISC

## Author

Bob Frankston
