# qn
Run a command and send its completion result to an HTTP endpoint.
`qn` sends each wrapped command as a structured Markdown completion card containing the device name, command, exit code, elapsed time, and working directory. With `--attach-output`, standard output and standard error appear in a fenced code block. Output previews are limited to 1,000 characters; longer output is also sent as a `qn-output.txt` attachment. It sends a notification for every wrapped command, including commands that finish immediately; it does not apply a duration threshold. Notification failures are written to standard error and do not change the command's exit status.
## Installation
```bash
cargo install krust-qn
```
## Quick start
Configure an endpoint, token, and device name in an interactive terminal:
```bash
qn init
```
Then run a command through `qn`, or send text, Markdown, images, and files directly:
```bash
qn sleep 30
qn make -j12
qn --shell "git push && cargo build --release"
qn -t "预览环境部署完成"
qn -m "# 预览环境部署完成"
qn -i ./preview.png
qn -f ./report.pdf
```
Without configuration, `qn` still runs commands but prints a reminder and skips their notifications. Direct actions require configuration and fail if they cannot be sent.
## Usage
```
qn [-a|--attach-output] [--no-notify] <command> [args...]
qn [-a|--attach-output] [--no-notify] --shell <command-string>
qn -t|--text <content>
qn -m|--markdown <content>
qn -i|--image <path>
qn -f|--file <path>
qn --status
qn init
qn init-shell <fish|bash|zsh|powershell>
```
| Option | Behavior |
|--------|----------|
| `-a`, `--attach-output` | Capture standard output and standard error, replay them after the command exits, and show them in the Markdown completion card. Output is therefore not streamed live while the command runs. Previews longer than 1,000 characters are truncated and sent in full as a `qn-output.txt` attachment. |
| `--no-notify` | Run the command without checking configuration or sending a notification. |
| `-t <content>`, `--text <content>` | Send a plain-text message without running a command. The device name is prefixed to the content. |
| `-m <content>`, `--markdown <content>` | Send Markdown without rewriting its content. |
| `-i <path>`, `--image <path>` | Upload and send the path as an image message. |
| `-f <path>`, `--file <path>` | Upload and send the path as a downloadable file attachment. |
| `--status` | Print QQ Gateway and default-recipient binding status. |
| `--` | Stop parsing `qn` options; the remaining arguments are the command to run. |
`-t`, `-m`, `-i`, `-f`, and `--status` are direct actions. They cannot be combined with command-execution options. Image and file mode are explicit: `qn -f image.png` sends a downloadable attachment, while `qn -i unknown.bin` requests an image message.
Before the command name, every argument beginning with `-` belongs to `qn`. Unknown `qn` options are rejected with status `2` and never run the following command. After the command name, arguments are passed through unchanged:
```bash
qn --no-notify cargo build --release
qn -- -program-with-leading-hyphen argument
```
For ordinary command arguments, the executable starts the requested program directly. `qn` exits with that command's exit code; a failed notification does not replace it.
## Captured output encoding
For `--attach-output`, qn preserves valid UTF-8 output without conversion. Otherwise it decodes a Unicode byte-order mark when present, uses the known UTF-16LE encoding of Windows PowerShell 5.1 capture files, and on Windows tries the active console output code page before applying legacy-encoding detection. This keeps common PowerShell, CMD, and native-command output readable in the Markdown notification.
Unlabelled, short legacy-encoded output is inherently ambiguous: the same bytes can represent different text under different code pages. qn therefore favors the Windows console code page when one is available; output from a program that emits a different legacy encoding while redirected may still require that program to emit UTF-8.
## Configuration
`qn init` requires an interactive terminal and writes the configuration file to the platform-native user configuration directory:
| Platform | Path |
|----------|------|
| Linux and other Unix systems | `$XDG_CONFIG_HOME/qn/config`, or `~/.config/qn/config` when `XDG_CONFIG_HOME` is unset |
| macOS | `~/Library/Application Support/qn/config` |
| Windows | `%APPDATA%\qn\config` |
On Windows, `qn init` prompts through standard input and output; it does not require a `HOME` environment variable.
It obtains values in this order:
1. `QN_ENDPOINT` — the QQ Task Notifier server root URL. If unset, `qn init` prompts for it; press Enter to use `https://krust.iepose.cn`.
2. `QN_TOKEN` — the required Bearer token, always prompted by `qn init`.
3. Device name — defaults to the machine hostname. On macOS, if the hostname is `localhost`, qn uses the configured LocalHostName instead.
The resulting file contains `endpoint=...`, `token=...`, and `name=...`. On Unix, files created by qn are written with permission `0600`. Running `qn init` replaces the file with the newly entered values. Existing configurations ending in `/task-completed` must run `qn init` once to switch to the server root URL.
At notification time, these environment variables override the corresponding file values:
| Environment variable | Config key | Purpose |
|----------------------|------------|---------|
| `QN_ENDPOINT` | `endpoint` | QQ Task Notifier server root URL |
| `QN_TOKEN` | `token` | Bearer token |
`name` has no environment-variable override. If it is missing from an existing config file, `qn` uses the hostname and appends it to the file.
## Shell integration
Shell integration is optional. It makes `qn <command>` a shell function, so shell builtins and shell-specific command strings can be run in the current shell before `qn` sends the completion report.
Install it explicitly for one of the supported shells:
```text
qn init-shell fish
qn init-shell bash
qn init-shell zsh
qn init-shell powershell
```
`init-shell` does not auto-detect the shell. It writes a qn-managed function block to the selected startup file; rerunning it updates that block in place instead of appending a duplicate. Restart the selected shell after installation:
| Shell | Startup file |
|-------|--------------|
| Fish | `~/.config/fish/config.fish` |
| Bash | `~/.bashrc` |
| Zsh | `~/.zshrc` |
| PowerShell (Windows) | `$PROFILE.CurrentUserCurrentHost` |
`qn init-shell powershell` asks Windows PowerShell for its current-user profile path and writes the managed function block there. Open a new PowerShell session after installation.
After upgrading, run `qn init-shell <shell>` once for the current shell to update its function block, including direct-action and option validation support.
## Notification requests
qn sends wrapped commands as Markdown to `POST /v1/markdown` under `QN_ENDPOINT`:
`````http
Authorization: Bearer <token>
Content-Type: application/json
{"content":"## 任务完成\n\n**设备**:build-server\n\n**耗时**:2m 15s · **退出码**:0\n\n**工作目录**:/home/me/project\n\n### 命令\n````sh\nmake -j12\n````"}
`````
A nonzero command exit code changes `任务完成` to `任务失败`. With `--attach-output`, captured standard output and/or standard error are rendered in a fenced `text` block. If the preview exceeds 1,000 characters, qn sends its complete contents as a `qn-output.txt` file attachment after the completion card.
All native routes use the same server root:
| Action | Request |
|--------|---------|
| Wrapped command | `POST /v1/markdown` with the generated completion card |
| `qn -t ...` | `POST /v1/messages` with a plain-text message |
| `qn -m ...` | `POST /v1/markdown` with verbatim `content` |
| `qn -i ...` / `qn -f ...` | `POST /v1/media` multipart form with `file_type=image` / `file` |
| `qn --status` | `GET /status` |
[QQ Task Notifier](https://github.com/krustd/qq-task-notifier) binds exactly one default recipient; qn does not allow callers to override it.
## How it works
1. For a command invocation, `qn` runs the requested command and records its exit code and elapsed time.
2. Wrapped commands are rendered as Markdown completion cards; plain text adds the configured device name, while explicit Markdown content is sent unchanged.
3. With `--attach-output`, qn replays the captured output locally and adds a bounded preview to the card; long output is uploaded as a file attachment.
4. Direct actions submit their requested text, Markdown, image, file, or status operation immediately. A command-notification transport error or non-success HTTP status is reported as a warning; the wrapped command's exit code is retained. Direct-action delivery errors exit with status `1`.
## License
Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or [MIT license](LICENSE-MIT) at your option.