Metadata-Version: 2.4
Name: powerrules
Version: 0.6.0b4
Summary: A rule-based command automation tool
License-Expression: MIT
License-File: LICENSE
Keywords: automation,cli,commands,hibernate,linux,macos,management,manager,policy,power,reboot,rules,shutdown,sleep,windows
Author: LeoTN
Author-email: LeoTN.GitHub@gmx.net
Requires-Python: >=3.11,<4.0
Classifier: Topic :: Utilities
Classifier: Environment :: Console
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS
Requires-Dist: psutil (>=7.0,<8.0)
Requires-Dist: pydantic (>=2.11,<3.0)
Requires-Dist: pyyaml (>=6.0,<7.0)
Requires-Dist: rich (>=14.0)
Requires-Dist: typer (>=0.27.1,<0.28.0)
Project-URL: Bug Tracker, https://github.com/LeoTN/PowerRules/issues
Project-URL: Homepage, https://github.com/LeoTN/PowerRules#readme
Project-URL: Repository, https://github.com/LeoTN/PowerRules
Description-Content-Type: text/markdown

<div align="center">

[![PowerRules](https://raw.githubusercontent.com/LeoTN/PowerRules/main/assets/logo/readme_logo.svg)](https://github.com/LeoTN/PowerRules)

[![latest-version](https://img.shields.io/github/v/release/LeoTN/PowerRules?&filter=*.*.*&display_name=release&style=for-the-badge&logo=Rocket&logoColor=green&label=LATEST&color=green)](https://github.com/LeoTN/PowerRules/releases/latest)
[![latest-beta-version](https://img.shields.io/github/v/release/LeoTN/PowerRules?&include_prereleases&filter=*.*.*b*&display_name=release&style=for-the-badge&logo=Textpattern&logoColor=orange&label=LATEST%20BETA&color=orange)](https://github.com/LeoTN/PowerRules/releases)
[![license](https://img.shields.io/github/license/LeoTN/PowerRules?&style=for-the-badge&logo=Google%20Docs&logoColor=blue&label=License&color=blue)](https://github.com/LeoTN/PowerRules/blob/main/LICENSE)

<details>
  <summary><b>Table of Contents</b></summary>
  <a href="#about">About</a><br>
  <a href="#getting-started">Getting Started</a><br>
  <a href="#features">Features</a><br>
  <a href="#supported-platforms">Supported Platforms</a><br>
  <a href="#credits--license">Credits & License</a>
</details>

</div>

## About

PowerRules is a rule-based command automation tool: when the conditions of a rule match, its commands run.

Originally built to shut down a computer at the right moment, it now runs any shell command.

Rules are evaluated from top to bottom and the first match runs its actions in order. Policies have to be [trusted](#trust) first, so modifications of a policy or its scripts do not go unnoticed.

## Getting Started

1\. Install via [pip](https://pypi.org/project/powerrules) (Python 3.11+) or download a [standalone binary](#supported-platforms):

```bash
pip install powerrules
```

2\. Create a policy file:

```yaml
# yaml-language-server: $schema=https://raw.githubusercontent.com/LeoTN/PowerRules/main/assets/schema/powerrules_policy.schema.json

rules:
  # Shut down when no backup process is running and the current time is between 23:00 and 01:30
  - name: "Shutdown after nightly backup"
    enabled: true
    conditions:
      and:
        - process:
            name: "backup.exe"
            exists: false
        - datetime:
            between:
              start: "23"
              end: "1:30"
    actions:
      # Windows command
      - name: "Shutdown"
        run: "shutdown /s /t 0"
```

3\. Validate the policy, [trust](#trust) it after reviewing it and evaluate it once. Use `--dry-run` to see what would happen:

```bash
pwru policy validate --policy my-policy.yaml
pwru trust add --policy my-policy.yaml
pwru policy run --once --policy my-policy.yaml --dry-run
```

> [!CAUTION]
> Without `--dry-run`, a matching rule ***runs*** its commands. The example above could shut down your computer.

> [!NOTE]
> `pwru` is installed by pip. When using a [standalone binary](#supported-platforms), call it by its file name instead.

## Features

### Process & Window Matching

Match rules based on processes and window titles.

```yaml
# Match if process "firefox.exe" is running
- process:
    name: "firefox.exe"
    exists: true

# Match if window with exact title "Firefox" exists
- window:
    title: "Firefox"
    exists: true
```

<details>
  <summary><b>Options reference</b></summary><br>

| Option | Applies to | Default | Description |
|--------|------------|---------|-------------|
| `name` | `process` | required | Name of the process to check. |
| `title` | `window` | required | Title of the window to check. |
| `exists` | both | required | Whether the process or window is expected to exist. |
| `match` | both | exact, case-sensitive | How the name or title is matched, see [Regex Matching](#regex-matching). |

</details>

### Regex Matching

Match process names and window titles using regular expressions with full-string matching.

```yaml
# Match if process name contains "fire"
- process:
    name: '.*fire.*'
    exists: true
    match:
      type: regex
      case_sensitive: true # This is the default behavior

# Match if window title starts with "firefox" (case insensitive)
- window:
    title: "Firefox.*"
    exists: true
    match:
      type: regex
      case_sensitive: false
```

<details>
  <summary><b>Options reference</b></summary><br>

Options of `match`:

| Option | Default | Description |
|--------|---------|-------------|
| `type` | `exact` | Matching mode: `exact` or `regex` (full-string match). |
| `case_sensitive` | `true` | Whether uppercase and lowercase letters should be treated differently. |

</details>

### Time-based Conditions

Match specific dates, time ranges, weekdays and months. If several of `between`, `weekday` and `month` are configured, all of them must match. The start of a range is inclusive, the end is exclusive.

```yaml
# Match every day from 23:00 until 1:30 the next morning
- datetime:
    between:
      start: "23"
      end: "1:30"

# Match on Mondays and Fridays
- datetime:
    weekday:
      - "Monday"
      - "Friday"

# Match in June, July and August
- datetime:
    month:
      - "June"
      - "July"
      - "August"

# Match on the whole days 2026-08-21 and 2026-08-22 (the end date 2026-08-23 is not included)
- datetime:
    between:
      start: "2026-08-21"
      end: "2026-08-23"

# Match from 2026-08-21 18:00 until 2026-08-23 6:00
- datetime:
    between:
      start: "2026-08-21 18:00"
      end: "2026-08-23 6:00"

# Match on Saturdays and Sundays from 2026-08-21 until the end of 2026
- datetime:
    between:
      start: "2026-08-21"
      end: "2027-01-01"
    weekday:
      - "Saturday"
      - "Sunday"

# Match on Saturday and Sunday nights in December from 22:00 until 6:00 the next morning
- datetime:
    between:
      start: "22"
      end: "6"
    weekday:
      - "Saturday"
      - "Sunday"
    month:
      - "December"
```

All values use the local time of the computer, timezones are not supported.

<details>
  <summary><b>Options reference</b></summary><br>

At least one of `between`, `weekday` and `month` is required.

| Option | Description |
|--------|-------------|
| `between` | Range during which the condition is satisfied. Both boundaries must be of the same kind (see below). |
| `weekday` | List of English weekday names (case-insensitive), e.g. `Monday`. |
| `month` | List of English month names (case-insensitive), e.g. `January`. |

`weekday` and `month`: If the time range crosses midnight, they refer to the day on which the range starts. For absolute ranges, they refer to the current day.

<br>

Kinds of `between`:

| Kind | Format | Example | Description |
|------|--------|---------|-------------|
| Time of day | `H`, `HH`, `H:MM`, `HH:MM`, `H:MM:SS` or `HH:MM:SS` | `"23"`, `"1:30"` | Repeats every day and may cross midnight. |
| Date | `YYYY-MM-DD` | `"2026-08-21"` | Covers whole days, from 0:00 of the start date until 0:00 of the end date. |
| Date with time | `YYYY-MM-DD` followed by a space or `T` and a time | `"2026-08-21 18:00"` | Absolute range without timezone. |

</details>

### Logical Conditions

Combine multiple conditions using `and`, `or`, and `not`.

```yaml
# Match if (condition_1 OR condition_2) AND NOT condition_3
- and:
    - or:
        - condition_1: ...
        - condition_2: ...
    - not:
        condition_3: ...
```

### Actions

When a rule matches, its actions run one after another as commands in a shell.

```yaml
actions:
  # A failing backup does not prevent the remaining actions
  - name: "Backup"
    run: "backup.sh"
    shell: bash
    working_directory: scripts # Relative to the policy file
    env:
      TARGET: "/mnt/backup"
    timeout: 600
    success_exit_codes: [0, 3]
    continue_on_error: true
    trusted_files:
      - scripts/backup.sh # Relative to the policy file (not to working_directory)

  # Start a command in the background without waiting for it
  - run: "notify.sh"
    shell: bash
    wait: false
    trusted_files:
      - scripts/notify.sh

  # Shutting down may require additional permissions on Linux
  - name: "Shutdown"
    run: "systemctl poweroff"
```

<details>
  <summary><b>Options reference</b></summary><br>

| Option | Default | Description |
|--------|---------|-------------|
| `run` | required | Command to run. A command with several lines stops at the first failing line. |
| `name` | `Action N of M` | Name of the action, used in log messages. |
| `shell` | `powershell` on Windows, `sh` on Linux and macOS | One of `sh`, `bash`, `cmd`, `powershell` or `pwsh`. The shell has to be installed. |
| `working_directory` | directory of the policy file | Relative paths are resolved against the directory of the policy file. |
| `env` | none | Additional environment variables. Numbers and booleans are converted to strings. Names starting with `POWERRULES_` are reserved. |
| `timeout` | `60` | Maximum run time in seconds. Afterwards, the command and its child processes are terminated. `null` disables the limit. |
| `success_exit_codes` | `[0]` | Exit codes which count as success. |
| `wait` | `true` | `false` starts the command in the background. Cannot be combined with `timeout` or `success_exit_codes`. |
| `continue_on_error` | `false` | Whether the remaining actions still run if this action fails. |
| `trusted_files` | none | Files (e.g. scripts) the command depends on. They are verified before every run, see [Trust](#trust). Relative paths are resolved against the directory of the policy file. |

</details>

Commands can read the name of their rule and action from the environment variables `POWERRULES_RULE_NAME` and `POWERRULES_ACTION_NAME`.

If an action fails (unexpected exit code, timeout or the command cannot be started), the remaining actions are skipped unless `continue_on_error` is set.

> [!WARNING]
> Commands run with the privileges of the user who started PowerRules, so treat policy files like scripts. Use [Trust](#trust) to detect modifications.

### Policy Evaluation

Evaluate a policy with `pwru policy run`. Without `--once`, the rules are evaluated every 10 seconds until PowerRules is stopped. A matching rule only runs its actions when it becomes the active match, not on every evaluation while it keeps matching.

```bash
# Evaluate continuously
pwru policy run

# Evaluate once and exit
pwru policy run --once

# Only report which rule would match without running any commands
pwru policy run --dry-run
```

The policy is loaded once at startup, so changes to the file require a restart. Unless `--dry-run` or `--no-require-trust` is used, the policy has to be [trusted](#trust). If a condition cannot be evaluated, an action fails or a trusted file was modified, the evaluation stops.

<details>
  <summary><b>Options reference</b></summary><br>

| Option | Applies to | Default | Description |
|--------|------------|---------|-------------|
| `--policy`, `-p` | all `policy` commands, `trust add`, `trust remove` | `powerrules.yaml` | Path to the PowerRules policy file. |
| `--once` | `policy run` | off | Evaluate the policy once and then exit. |
| `--stop-on-match` | `policy run` | off | Keep evaluating until the first rule matches, then stop. |
| `--dry-run` | `policy run` | off | Evaluate the policy without executing any matching action. |
| `--no-require-trust` | `policy run` | off | Run the policy even if it is not trusted or was modified since it was trusted, see [Trust](#trust). |

</details>

### Trust

PowerRules remembers which policies you trust. A policy is identified by its path and trusted by its content (SHA-256 hash). Every modification makes it untrusted until you review it and trust it again. Files which actions depend on (e.g. scripts or executables) are not detected automatically. To cover them as well, you can list them in the action's `trusted_files` configuration.

```bash
# Trust the current content of a policy and its trusted files
pwru trust add --policy my-policy.yaml

# Show all trusted policies and their current status
pwru trust list

# Remove the trust of a policy
pwru trust remove --policy my-policy.yaml

# Remove trusted policies whose policy file no longer exists
pwru trust prune
```

Relative paths are resolved against the directory of the policy file:

```yaml
actions:
  - run: "./backup.sh"
    working_directory: scripts
    trusted_files:
      - scripts/backup.sh # Relative to the policy file (not to working_directory)
```

`pwru policy run` refuses to run untrusted policies by default. `--no-require-trust` can be used to override this behaviour.

The policy and its trusted files are checked when the policy is loaded. Right before an action runs, its `trusted_files` are checked again. The trust store is saved in `~/.powerrules/trust.json`.

<details>
  <summary><b>Options reference</b></summary><br>

| Command | Options | Description |
|---------|---------|-------------|
| `trust add` | `--policy` | Trust the current content of the policy and its trusted files. An invalid policy or an unreadable file cannot be trusted. |
| `trust remove` | `--policy` | Remove the trust of the policy. |
| `trust list` | `--trusted`, `--modified`, `--missing` | List all trusted policies and their current status. Several filters can be combined, a policy is shown if it matches at least one of them. |
| `trust prune` | `--dry-run`, `--yes`, `-y` | Remove trusted policies whose policy file no longer exists. `--dry-run` only shows what would be removed, `--yes` skips the confirmation. |

<br>

Trust status of the policy and its files:

| Status | Description |
|--------|-------------|
| `trusted` | The content is unchanged since it was trusted. |
| `unknown` | The policy or file was never trusted. |
| `modified` | The content changed since it was trusted. |
| `missing` | The file cannot be read. |

A policy only counts as trusted if the policy and all of its trusted files are trusted.

</details>

> [!WARNING]
> Trust only covers the policy and its `trusted_files`. Programs started by commands are not checked and the trust store is only protected by the permissions of your user account.

### Logging

All `policy` and `trust` commands accept the following logging options:

```bash
pwru policy run --log-level DEBUG --log-file my-log.log
```

Messages are shown on the console and written to a rotating log file. Commands of actions and their output are only logged on `DEBUG` level. The error output of a failed action is always logged as an error.

<details>
  <summary><b>Options reference</b></summary><br>

| Option | Default | Description |
|--------|---------|-------------|
| `--log-level` | `INFO` | Minimum log level shown on the console and written to the log file. One of `DEBUG`, `INFO`, `WARNING` or `ERROR`. |
| `--log-file` | `powerrules.log` | Path to the rotating log file. |
| `--log-level-file` | value of `--log-level` | Minimum log level written to the log file. |

</details>

### Exit Codes

`pwru` reports the result of a command via its exit code.

<details>
  <summary><b>Exit code reference</b></summary><br>

| Exit code | Meaning | Examples |
|-----------|---------|----------|
| `0` | Success. | The policy is valid, was evaluated or no rule matched. |
| `1` | Runtime error. | A condition cannot be evaluated, an action fails, the trust store cannot be read or written, an unexpected error occurred or a confirmation was declined (`trust prune`). |
| `2` | Policy error or invalid usage. | The policy file was not found, contains invalid YAML or fails validation. Invalid command line options also use this code. |
| `3` | Trust error. | The policy or one of its `trusted_files` is not trusted, was modified or cannot be read, see [Trust](#trust). |

</details>

## Supported Platforms

| Platform | Standalone Binary | Notes |
|----------|-----------------------| ----- |
| Windows 10/11 | [powerrules-windows-x86_64.exe](https://github.com/LeoTN/PowerRules/releases/latest/download/powerrules-windows-x86_64.exe) | Window conditions only work when PowerRules runs in the logged-in user's session. |
| Linux | [powerrules-linux-x86_64](https://github.com/LeoTN/PowerRules/releases/latest/download/powerrules-linux-x86_64)<br>[powerrules-linux-arm64](https://github.com/LeoTN/PowerRules/releases/latest/download/powerrules-linux-arm64) | Window conditions require an X11 session (Wayland is currently not supported). |
| macOS | [powerrules-macos-arm64](https://github.com/LeoTN/PowerRules/releases/latest/download/powerrules-macos-arm64) | Window conditions require the screen recording permission. |

**Platform independent via pip:**
```bash
pip install powerrules
```

## Credits & License

* [Inkscape](https://inkscape.org) → program used to design the logo
* [Nuitka](https://github.com/nuitka/nuitka) → standalone binaries
* [psutil](https://github.com/giampaolo/psutil) → process information
* [Pydantic](https://github.com/pydantic/pydantic) → configuration validation
* [PyYAML](https://github.com/yaml/pyyaml) → YAML policy parsing
* [rich](https://github.com/textualize/rich) → console output formatting
* [Typer](https://github.com/fastapi/typer) → command-line interface

*This repository is licensed under the [MIT License](https://github.com/LeoTN/PowerRules/blob/main/LICENSE).*

