runbook-exec 0.1.0__tar.gz

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.
Files changed (37) hide show
  1. runbook_exec-0.1.0/LICENSE +21 -0
  2. runbook_exec-0.1.0/PKG-INFO +282 -0
  3. runbook_exec-0.1.0/README.md +249 -0
  4. runbook_exec-0.1.0/pyproject.toml +109 -0
  5. runbook_exec-0.1.0/runbook_exec/__init__.py +3 -0
  6. runbook_exec-0.1.0/runbook_exec/_json_utils.py +38 -0
  7. runbook_exec-0.1.0/runbook_exec/approval.py +527 -0
  8. runbook_exec-0.1.0/runbook_exec/audit.py +305 -0
  9. runbook_exec-0.1.0/runbook_exec/classifier.py +105 -0
  10. runbook_exec-0.1.0/runbook_exec/cli.py +130 -0
  11. runbook_exec-0.1.0/runbook_exec/config.py +72 -0
  12. runbook_exec-0.1.0/runbook_exec/display.py +248 -0
  13. runbook_exec-0.1.0/runbook_exec/exceptions.py +57 -0
  14. runbook_exec-0.1.0/runbook_exec/executor.py +455 -0
  15. runbook_exec-0.1.0/runbook_exec/interrupt.py +27 -0
  16. runbook_exec-0.1.0/runbook_exec/llm.py +55 -0
  17. runbook_exec-0.1.0/runbook_exec/models.py +136 -0
  18. runbook_exec-0.1.0/runbook_exec/parser.py +235 -0
  19. runbook_exec-0.1.0/runbook_exec/shell.py +119 -0
  20. runbook_exec-0.1.0/runbook_exec.egg-info/PKG-INFO +282 -0
  21. runbook_exec-0.1.0/runbook_exec.egg-info/SOURCES.txt +35 -0
  22. runbook_exec-0.1.0/runbook_exec.egg-info/dependency_links.txt +1 -0
  23. runbook_exec-0.1.0/runbook_exec.egg-info/entry_points.txt +2 -0
  24. runbook_exec-0.1.0/runbook_exec.egg-info/requires.txt +14 -0
  25. runbook_exec-0.1.0/runbook_exec.egg-info/top_level.txt +1 -0
  26. runbook_exec-0.1.0/setup.cfg +4 -0
  27. runbook_exec-0.1.0/tests/test_approval.py +799 -0
  28. runbook_exec-0.1.0/tests/test_audit.py +855 -0
  29. runbook_exec-0.1.0/tests/test_classifier.py +295 -0
  30. runbook_exec-0.1.0/tests/test_cli.py +383 -0
  31. runbook_exec-0.1.0/tests/test_config.py +236 -0
  32. runbook_exec-0.1.0/tests/test_display.py +483 -0
  33. runbook_exec-0.1.0/tests/test_executor.py +1169 -0
  34. runbook_exec-0.1.0/tests/test_llm.py +113 -0
  35. runbook_exec-0.1.0/tests/test_models.py +279 -0
  36. runbook_exec-0.1.0/tests/test_parser.py +624 -0
  37. runbook_exec-0.1.0/tests/test_shell.py +249 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 <Your Name>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OF OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,282 @@
1
+ Metadata-Version: 2.4
2
+ Name: runbook-exec
3
+ Version: 0.1.0
4
+ Summary: AI-driven CLI tool that transforms Markdown runbooks into executable automation with safety gates and audit trails
5
+ License: MIT
6
+ Keywords: runbook,automation,incident-response,cli,llm
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Intended Audience :: System Administrators
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Topic :: System :: Systems Administration
15
+ Classifier: Topic :: Utilities
16
+ Requires-Python: >=3.11
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: typer<1,>=0.15
20
+ Requires-Dist: anthropic<1,>=0.49
21
+ Requires-Dist: slack-sdk<4,>=3.35
22
+ Requires-Dist: rich<14,>=13.9
23
+ Requires-Dist: pydantic<3,>=2.13
24
+ Requires-Dist: markdown-it-py<4,>=3.0
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest==8.3.5; extra == "dev"
27
+ Requires-Dist: pytest-cov==6.0.0; extra == "dev"
28
+ Requires-Dist: pytest-asyncio==0.25.3; extra == "dev"
29
+ Requires-Dist: pytest-mock==3.14.0; extra == "dev"
30
+ Requires-Dist: hypothesis==6.130.5; extra == "dev"
31
+ Requires-Dist: ruff==0.9.10; extra == "dev"
32
+ Dynamic: license-file
33
+
34
+ # runbook-exec
35
+
36
+ > **Status**: v0.1.0 — initial release. Core functionality validated end-to-end. Slack approval workflow tested with mocks; production Slack testing pending.
37
+
38
+ AI-driven CLI tool that transforms passive Markdown runbooks into executable automation with safety gates and audit trails.
39
+
40
+ At incident time (e.g., 3 AM), instead of a human manually following a runbook step-by-step, `runbook-exec` reads the Markdown runbook, classifies each step by risk level using Claude, executes safe steps autonomously, requests human approval for risky operations via Slack, and produces a tamper-evident audit log.
41
+
42
+ ## Quickstart
43
+
44
+ ### 1. Install
45
+
46
+ Until v0.1.0 ships to PyPI, install from source:
47
+
48
+ ```bash
49
+ git clone https://github.com/giridharpandurangi/runbook-exec
50
+ cd runbook-exec
51
+ pip install -e .
52
+ ```
53
+
54
+ Once published, you'll be able to:
55
+
56
+ ```bash
57
+ pip install runbook-exec
58
+ ```
59
+
60
+ ### Free Anthropic credits
61
+
62
+ New Anthropic accounts get $5 in free credits at [console.anthropic.com](https://console.anthropic.com) — enough for hundreds of `runbook-exec validate` runs.
63
+
64
+ ### 2. Set environment variables
65
+
66
+ ```bash
67
+ export ANTHROPIC_API_KEY=sk-ant-... # Required
68
+ export SLACK_BOT_TOKEN=xoxb-... # Required for approval workflow
69
+ export SLACK_APP_TOKEN=xapp-... # Required for Socket Mode
70
+ ```
71
+
72
+ ### 3. Run an example runbook
73
+
74
+ ```bash
75
+ # Dry-run first to see what would happen
76
+ runbook-exec run examples/disk-full.md --dry-run
77
+
78
+ # Validate risk levels without executing
79
+ runbook-exec validate examples/disk-full.md
80
+
81
+ # Execute for real
82
+ runbook-exec run examples/disk-full.md
83
+ ```
84
+
85
+ ## Platform support
86
+
87
+ Tested on Linux, macOS, and Windows. The shell executor uses `subprocess` and runs whatever shell command you provide — runbook commands need to be valid for the host OS. Most realistic runbooks target Linux. On Windows, use `cmd.exe`-compatible commands or wrap PowerShell via `powershell -Command "..."`.
88
+
89
+ ## Runbook formatting
90
+
91
+ `runbook-exec` parses Markdown using the CommonMark spec. Two rules matter in practice:
92
+
93
+ **Numbered lists only.** Only numbered (`1.`, `2.`, ...) list items become steps. Bullet points (`-`, `*`) are ignored.
94
+
95
+ **Code blocks must be indented under their list item.** A fenced code block is only associated with a step if it is indented at least as far as the list item's content. This is standard CommonMark behaviour — a code block at the left margin is not part of the list.
96
+
97
+ ✅ Correct — code block indented under the list item:
98
+ ```markdown
99
+ 1. Check disk usage
100
+
101
+ ```bash
102
+ df -h
103
+ ```
104
+ ```
105
+
106
+ ❌ Wrong — code block at the left margin (will not be extracted as the step's command):
107
+ ```markdown
108
+ 1. Check disk usage
109
+
110
+ ```bash
111
+ df -h
112
+ ```
113
+ ```
114
+
115
+ When in doubt, run `runbook-exec validate <runbook.md>` — steps with `command=None` in the output mean the code block was not picked up.
116
+
117
+ ## Subcommands
118
+
119
+ ### `run`
120
+
121
+ Execute a runbook end-to-end with safety gates and audit logging.
122
+
123
+ ```bash
124
+ runbook-exec run <runbook.md> [OPTIONS]
125
+
126
+ Options:
127
+ --dry-run Simulate execution without running commands
128
+ --incident-id TEXT Identifier used in the audit log filename
129
+ --auto-approve TEXT Auto-approve steps at or below this risk level:
130
+ read_only | modifying | destructive
131
+ --no-llm-context Disable post-step LLM decision calls
132
+ ```
133
+
134
+ ### `validate`
135
+
136
+ Parse and classify a runbook without executing any commands. Useful for CI/CD.
137
+
138
+ ```bash
139
+ runbook-exec validate <runbook.md>
140
+ ```
141
+
142
+ ### `replay`
143
+
144
+ Display a previous runbook execution from an audit log.
145
+
146
+ ```bash
147
+ runbook-exec replay <audit-log.json>
148
+ ```
149
+
150
+ ## Configuration
151
+
152
+ Create a `.runbook-exec.toml` file in your project root to set defaults:
153
+
154
+ ```toml
155
+ llm_model = "claude-sonnet-4-5"
156
+ slack_channel = "#incidents"
157
+ timeout_seconds = 300
158
+ auto_approve_level = "read_only" # "read_only" | "modifying" | "destructive"
159
+ audit_log_dir = "./runbook-exec-logs"
160
+ ```
161
+
162
+ ### Environment variables
163
+
164
+ | Variable | Required | Description |
165
+ |---|---|---|
166
+ | `ANTHROPIC_API_KEY` | Yes | Anthropic API key for Claude |
167
+ | `SLACK_BOT_TOKEN` | For approvals | Slack bot token (`xoxb-...`) |
168
+ | `SLACK_APP_TOKEN` | For approvals | Slack app token for Socket Mode (`xapp-...`) |
169
+
170
+ ### Configuration precedence
171
+
172
+ CLI flags > `.runbook-exec.toml` > built-in defaults
173
+
174
+ ### Slack fallback
175
+
176
+ If Slack is not configured (missing `SLACK_BOT_TOKEN` or `SLACK_APP_TOKEN`), `runbook-exec` falls back to interactive terminal prompts for approvals and failure direction. A banner is shown at startup indicating which mode is active.
177
+
178
+ Slack is recommended for production use because terminal prompts require an active SSH or console session. Terminal mode is useful for local testing and development.
179
+
180
+ ## Risk levels
181
+
182
+ Each step is classified as one of three risk levels:
183
+
184
+ | Level | Description | Examples |
185
+ |---|---|---|
186
+ | `read_only` | Observes system state only | `df`, `ls`, `cat`, `kubectl get`, `ps` |
187
+ | `modifying` | Changes state but recoverable | `systemctl restart`, `logrotate`, file edits |
188
+ | `destructive` | Deletes data or requires sudo | `rm -rf`, `DROP TABLE`, `kubectl delete` |
189
+
190
+ By default, `read_only` steps run automatically. `modifying` and `destructive` steps require Slack approval.
191
+
192
+ ## Safety bias principle
193
+
194
+ When the classifier is uncertain between two risk levels, it always chooses the more cautious (higher risk) level. This is enforced in the LLM prompt, not in post-processing, so the reasoning reflects the actual decision.
195
+
196
+ ## LLM data disclosure
197
+
198
+ `runbook-exec` sends data to the Anthropic API in two scenarios:
199
+
200
+ 1. **Step classification** (always): The step text and extracted command are sent to Claude to determine the risk level. This happens for every step before any execution begins.
201
+
202
+ 2. **Post-step decisions** (unless `--no-llm-context` is set): After each step executes successfully, the step text, command, exit code, and stdout/stderr output are sent to Claude to decide whether to continue, skip subsequent steps, or abort.
203
+
204
+ **What is NOT sent**: Slack tokens, audit log contents, or any data from steps that have not yet executed.
205
+
206
+ ## `--no-llm-context` flag
207
+
208
+ Use `--no-llm-context` to prevent command output from being sent to the Anthropic API:
209
+
210
+ ```bash
211
+ runbook-exec run runbook.md --no-llm-context
212
+ ```
213
+
214
+ When this flag is set:
215
+ - Step classification still uses the LLM (only step text and command are sent)
216
+ - Post-step decision calls are disabled
217
+ - Execution continues to the next step after each command completes
218
+ - A warning is displayed at startup
219
+
220
+ Use this flag when running runbooks that produce sensitive output (credentials, PII, internal hostnames) that should not leave your network boundary.
221
+
222
+ ## Audit logs
223
+
224
+ Every execution produces a tamper-evident audit log in `./runbook-exec-logs/` (configurable via `audit_log_dir`).
225
+
226
+ Audit logs use NDJSON format with a SHA-256 hash chain. Each entry includes a hash of the previous entry, making tampering detectable.
227
+
228
+ To replay and verify an audit log:
229
+
230
+ ```bash
231
+ runbook-exec replay runbook-exec-logs/disk-full-20240115T030000Z-a3f1.json
232
+ ```
233
+
234
+ Audit log files are never committed to source control (`.gitignore` includes `runbook-exec-logs/`).
235
+
236
+ ## CI/CD integration
237
+
238
+ Add runbook validation to your PR checks using the included GitHub Action:
239
+
240
+ ```yaml
241
+ # .github/workflows/validate.yml
242
+ name: Validate Runbooks
243
+ on:
244
+ pull_request:
245
+ paths:
246
+ - "examples/**/*.md"
247
+
248
+ jobs:
249
+ validate:
250
+ runs-on: ubuntu-latest
251
+ steps:
252
+ - uses: actions/checkout@v4
253
+ - uses: actions/setup-python@v5
254
+ with:
255
+ python-version: "3.11"
256
+ - run: pip install runbook-exec
257
+ - run: runbook-exec validate examples/disk-full.md
258
+ env:
259
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
260
+ ```
261
+
262
+ The workflow fails if `runbook-exec validate` exits with a non-zero code.
263
+
264
+ ## Example runbook
265
+
266
+ See [`examples/disk-full.md`](examples/disk-full.md) for a realistic disk-full incident runbook with a mix of `read_only`, `modifying`, and `destructive` steps.
267
+
268
+ ## Development
269
+
270
+ ```bash
271
+ # Install in development mode
272
+ pip install -e ".[dev]"
273
+
274
+ # Run tests
275
+ pytest
276
+
277
+ # Run tests with coverage
278
+ pytest --cov=runbook_exec --cov-report=term-missing
279
+
280
+ # Lint
281
+ ruff check .
282
+ ```
@@ -0,0 +1,249 @@
1
+ # runbook-exec
2
+
3
+ > **Status**: v0.1.0 — initial release. Core functionality validated end-to-end. Slack approval workflow tested with mocks; production Slack testing pending.
4
+
5
+ AI-driven CLI tool that transforms passive Markdown runbooks into executable automation with safety gates and audit trails.
6
+
7
+ At incident time (e.g., 3 AM), instead of a human manually following a runbook step-by-step, `runbook-exec` reads the Markdown runbook, classifies each step by risk level using Claude, executes safe steps autonomously, requests human approval for risky operations via Slack, and produces a tamper-evident audit log.
8
+
9
+ ## Quickstart
10
+
11
+ ### 1. Install
12
+
13
+ Until v0.1.0 ships to PyPI, install from source:
14
+
15
+ ```bash
16
+ git clone https://github.com/giridharpandurangi/runbook-exec
17
+ cd runbook-exec
18
+ pip install -e .
19
+ ```
20
+
21
+ Once published, you'll be able to:
22
+
23
+ ```bash
24
+ pip install runbook-exec
25
+ ```
26
+
27
+ ### Free Anthropic credits
28
+
29
+ New Anthropic accounts get $5 in free credits at [console.anthropic.com](https://console.anthropic.com) — enough for hundreds of `runbook-exec validate` runs.
30
+
31
+ ### 2. Set environment variables
32
+
33
+ ```bash
34
+ export ANTHROPIC_API_KEY=sk-ant-... # Required
35
+ export SLACK_BOT_TOKEN=xoxb-... # Required for approval workflow
36
+ export SLACK_APP_TOKEN=xapp-... # Required for Socket Mode
37
+ ```
38
+
39
+ ### 3. Run an example runbook
40
+
41
+ ```bash
42
+ # Dry-run first to see what would happen
43
+ runbook-exec run examples/disk-full.md --dry-run
44
+
45
+ # Validate risk levels without executing
46
+ runbook-exec validate examples/disk-full.md
47
+
48
+ # Execute for real
49
+ runbook-exec run examples/disk-full.md
50
+ ```
51
+
52
+ ## Platform support
53
+
54
+ Tested on Linux, macOS, and Windows. The shell executor uses `subprocess` and runs whatever shell command you provide — runbook commands need to be valid for the host OS. Most realistic runbooks target Linux. On Windows, use `cmd.exe`-compatible commands or wrap PowerShell via `powershell -Command "..."`.
55
+
56
+ ## Runbook formatting
57
+
58
+ `runbook-exec` parses Markdown using the CommonMark spec. Two rules matter in practice:
59
+
60
+ **Numbered lists only.** Only numbered (`1.`, `2.`, ...) list items become steps. Bullet points (`-`, `*`) are ignored.
61
+
62
+ **Code blocks must be indented under their list item.** A fenced code block is only associated with a step if it is indented at least as far as the list item's content. This is standard CommonMark behaviour — a code block at the left margin is not part of the list.
63
+
64
+ ✅ Correct — code block indented under the list item:
65
+ ```markdown
66
+ 1. Check disk usage
67
+
68
+ ```bash
69
+ df -h
70
+ ```
71
+ ```
72
+
73
+ ❌ Wrong — code block at the left margin (will not be extracted as the step's command):
74
+ ```markdown
75
+ 1. Check disk usage
76
+
77
+ ```bash
78
+ df -h
79
+ ```
80
+ ```
81
+
82
+ When in doubt, run `runbook-exec validate <runbook.md>` — steps with `command=None` in the output mean the code block was not picked up.
83
+
84
+ ## Subcommands
85
+
86
+ ### `run`
87
+
88
+ Execute a runbook end-to-end with safety gates and audit logging.
89
+
90
+ ```bash
91
+ runbook-exec run <runbook.md> [OPTIONS]
92
+
93
+ Options:
94
+ --dry-run Simulate execution without running commands
95
+ --incident-id TEXT Identifier used in the audit log filename
96
+ --auto-approve TEXT Auto-approve steps at or below this risk level:
97
+ read_only | modifying | destructive
98
+ --no-llm-context Disable post-step LLM decision calls
99
+ ```
100
+
101
+ ### `validate`
102
+
103
+ Parse and classify a runbook without executing any commands. Useful for CI/CD.
104
+
105
+ ```bash
106
+ runbook-exec validate <runbook.md>
107
+ ```
108
+
109
+ ### `replay`
110
+
111
+ Display a previous runbook execution from an audit log.
112
+
113
+ ```bash
114
+ runbook-exec replay <audit-log.json>
115
+ ```
116
+
117
+ ## Configuration
118
+
119
+ Create a `.runbook-exec.toml` file in your project root to set defaults:
120
+
121
+ ```toml
122
+ llm_model = "claude-sonnet-4-5"
123
+ slack_channel = "#incidents"
124
+ timeout_seconds = 300
125
+ auto_approve_level = "read_only" # "read_only" | "modifying" | "destructive"
126
+ audit_log_dir = "./runbook-exec-logs"
127
+ ```
128
+
129
+ ### Environment variables
130
+
131
+ | Variable | Required | Description |
132
+ |---|---|---|
133
+ | `ANTHROPIC_API_KEY` | Yes | Anthropic API key for Claude |
134
+ | `SLACK_BOT_TOKEN` | For approvals | Slack bot token (`xoxb-...`) |
135
+ | `SLACK_APP_TOKEN` | For approvals | Slack app token for Socket Mode (`xapp-...`) |
136
+
137
+ ### Configuration precedence
138
+
139
+ CLI flags > `.runbook-exec.toml` > built-in defaults
140
+
141
+ ### Slack fallback
142
+
143
+ If Slack is not configured (missing `SLACK_BOT_TOKEN` or `SLACK_APP_TOKEN`), `runbook-exec` falls back to interactive terminal prompts for approvals and failure direction. A banner is shown at startup indicating which mode is active.
144
+
145
+ Slack is recommended for production use because terminal prompts require an active SSH or console session. Terminal mode is useful for local testing and development.
146
+
147
+ ## Risk levels
148
+
149
+ Each step is classified as one of three risk levels:
150
+
151
+ | Level | Description | Examples |
152
+ |---|---|---|
153
+ | `read_only` | Observes system state only | `df`, `ls`, `cat`, `kubectl get`, `ps` |
154
+ | `modifying` | Changes state but recoverable | `systemctl restart`, `logrotate`, file edits |
155
+ | `destructive` | Deletes data or requires sudo | `rm -rf`, `DROP TABLE`, `kubectl delete` |
156
+
157
+ By default, `read_only` steps run automatically. `modifying` and `destructive` steps require Slack approval.
158
+
159
+ ## Safety bias principle
160
+
161
+ When the classifier is uncertain between two risk levels, it always chooses the more cautious (higher risk) level. This is enforced in the LLM prompt, not in post-processing, so the reasoning reflects the actual decision.
162
+
163
+ ## LLM data disclosure
164
+
165
+ `runbook-exec` sends data to the Anthropic API in two scenarios:
166
+
167
+ 1. **Step classification** (always): The step text and extracted command are sent to Claude to determine the risk level. This happens for every step before any execution begins.
168
+
169
+ 2. **Post-step decisions** (unless `--no-llm-context` is set): After each step executes successfully, the step text, command, exit code, and stdout/stderr output are sent to Claude to decide whether to continue, skip subsequent steps, or abort.
170
+
171
+ **What is NOT sent**: Slack tokens, audit log contents, or any data from steps that have not yet executed.
172
+
173
+ ## `--no-llm-context` flag
174
+
175
+ Use `--no-llm-context` to prevent command output from being sent to the Anthropic API:
176
+
177
+ ```bash
178
+ runbook-exec run runbook.md --no-llm-context
179
+ ```
180
+
181
+ When this flag is set:
182
+ - Step classification still uses the LLM (only step text and command are sent)
183
+ - Post-step decision calls are disabled
184
+ - Execution continues to the next step after each command completes
185
+ - A warning is displayed at startup
186
+
187
+ Use this flag when running runbooks that produce sensitive output (credentials, PII, internal hostnames) that should not leave your network boundary.
188
+
189
+ ## Audit logs
190
+
191
+ Every execution produces a tamper-evident audit log in `./runbook-exec-logs/` (configurable via `audit_log_dir`).
192
+
193
+ Audit logs use NDJSON format with a SHA-256 hash chain. Each entry includes a hash of the previous entry, making tampering detectable.
194
+
195
+ To replay and verify an audit log:
196
+
197
+ ```bash
198
+ runbook-exec replay runbook-exec-logs/disk-full-20240115T030000Z-a3f1.json
199
+ ```
200
+
201
+ Audit log files are never committed to source control (`.gitignore` includes `runbook-exec-logs/`).
202
+
203
+ ## CI/CD integration
204
+
205
+ Add runbook validation to your PR checks using the included GitHub Action:
206
+
207
+ ```yaml
208
+ # .github/workflows/validate.yml
209
+ name: Validate Runbooks
210
+ on:
211
+ pull_request:
212
+ paths:
213
+ - "examples/**/*.md"
214
+
215
+ jobs:
216
+ validate:
217
+ runs-on: ubuntu-latest
218
+ steps:
219
+ - uses: actions/checkout@v4
220
+ - uses: actions/setup-python@v5
221
+ with:
222
+ python-version: "3.11"
223
+ - run: pip install runbook-exec
224
+ - run: runbook-exec validate examples/disk-full.md
225
+ env:
226
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
227
+ ```
228
+
229
+ The workflow fails if `runbook-exec validate` exits with a non-zero code.
230
+
231
+ ## Example runbook
232
+
233
+ See [`examples/disk-full.md`](examples/disk-full.md) for a realistic disk-full incident runbook with a mix of `read_only`, `modifying`, and `destructive` steps.
234
+
235
+ ## Development
236
+
237
+ ```bash
238
+ # Install in development mode
239
+ pip install -e ".[dev]"
240
+
241
+ # Run tests
242
+ pytest
243
+
244
+ # Run tests with coverage
245
+ pytest --cov=runbook_exec --cov-report=term-missing
246
+
247
+ # Lint
248
+ ruff check .
249
+ ```
@@ -0,0 +1,109 @@
1
+ [build-system]
2
+ requires = ["setuptools>=75.0.0", "wheel>=0.45.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "runbook-exec"
7
+ version = "0.1.0"
8
+ description = "AI-driven CLI tool that transforms Markdown runbooks into executable automation with safety gates and audit trails"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = { text = "MIT" }
12
+ keywords = ["runbook", "automation", "incident-response", "cli", "llm"]
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Intended Audience :: Developers",
16
+ "Intended Audience :: System Administrators",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Topic :: System :: Systems Administration",
22
+ "Topic :: Utilities",
23
+ ]
24
+ dependencies = [
25
+ "typer>=0.15,<1",
26
+ "anthropic>=0.49,<1",
27
+ "slack-sdk>=3.35,<4",
28
+ "rich>=13.9,<14",
29
+ "pydantic>=2.13,<3",
30
+ "markdown-it-py>=3.0,<4",
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ dev = [
35
+ "pytest==8.3.5",
36
+ "pytest-cov==6.0.0",
37
+ "pytest-asyncio==0.25.3",
38
+ "pytest-mock==3.14.0",
39
+ "hypothesis==6.130.5",
40
+ "ruff==0.9.10",
41
+ ]
42
+
43
+ [project.scripts]
44
+ runbook-exec = "runbook_exec.cli:app"
45
+
46
+ [tool.setuptools.packages.find]
47
+ where = ["."]
48
+ include = ["runbook_exec*"]
49
+
50
+ [tool.setuptools.package-data]
51
+ runbook_exec = ["py.typed"]
52
+ "*" = ["examples/*.md"]
53
+
54
+ [tool.ruff]
55
+ target-version = "py311"
56
+ line-length = 100
57
+
58
+ [tool.ruff.lint]
59
+ select = [
60
+ "E", # pycodestyle errors
61
+ "W", # pycodestyle warnings
62
+ "F", # pyflakes
63
+ "I", # isort
64
+ "B", # flake8-bugbear
65
+ "C4", # flake8-comprehensions
66
+ "UP", # pyupgrade
67
+ "N", # pep8-naming
68
+ "SIM", # flake8-simplify
69
+ "TCH", # flake8-type-checking
70
+ "RUF", # ruff-specific rules
71
+ ]
72
+ ignore = [
73
+ "E501", # line too long — handled by formatter
74
+ "B008", # do not perform function calls in default arguments (typer uses this pattern)
75
+ ]
76
+
77
+ [tool.ruff.lint.isort]
78
+ known-first-party = ["runbook_exec"]
79
+
80
+ [tool.ruff.format]
81
+ quote-style = "double"
82
+ indent-style = "space"
83
+ skip-magic-trailing-comma = false
84
+
85
+ [tool.pytest.ini_options]
86
+ testpaths = ["tests"]
87
+ markers = [
88
+ "property: property-based tests using Hypothesis",
89
+ ]
90
+ addopts = "-v"
91
+ filterwarnings = [
92
+ # pytest-asyncio 0.25.x emits a DeprecationWarning on Python 3.14 about
93
+ # asyncio.get_event_loop_policy. Library issue, will resolve upstream.
94
+ "ignore::DeprecationWarning:pytest_asyncio",
95
+ ]
96
+
97
+ [tool.coverage.run]
98
+ source = ["runbook_exec"]
99
+ omit = ["tests/*"]
100
+
101
+ [tool.coverage.report]
102
+ show_missing = true
103
+ fail_under = 80
104
+ exclude_lines = [
105
+ "pragma: no cover",
106
+ "def __repr__",
107
+ "if TYPE_CHECKING:",
108
+ "raise NotImplementedError",
109
+ ]
@@ -0,0 +1,3 @@
1
+ """runbook-exec: AI-driven runbook automation with safety gates and audit trails."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,38 @@
1
+ """Shared JSON parsing utilities for runbook-exec.
2
+
3
+ Centralises the markdown-fence stripping logic so every LLM response parser
4
+ goes through the same pre-processing step. This prevents the recurring bug
5
+ where the LLM wraps its JSON response in ```json ... ``` fences.
6
+ """
7
+
8
+ import re
9
+
10
+ # Matches opening fence: optional whitespace, ``` or ~~~, optional language tag
11
+ _FENCE_OPEN = re.compile(r"^\s*(?:```|~~~)\w*\s*\n?", re.MULTILINE)
12
+ # Matches closing fence: optional whitespace, ``` or ~~~
13
+ _FENCE_CLOSE = re.compile(r"\n?\s*(?:```|~~~)\s*$", re.MULTILINE)
14
+
15
+
16
+ def strip_markdown_fences(text: str) -> str:
17
+ """Strip markdown code fences from an LLM response.
18
+
19
+ Handles all common variants:
20
+ - ```json\\n{...}\\n```
21
+ - ```\\n{...}\\n```
22
+ - ~~~json\\n{...}\\n~~~
23
+ - Leading/trailing whitespace around fences
24
+
25
+ Args:
26
+ text: Raw LLM response text, possibly wrapped in code fences.
27
+
28
+ Returns:
29
+ The text with any enclosing code fence stripped, or the original
30
+ text unchanged if no fence is detected.
31
+ """
32
+ stripped = text.strip()
33
+ # Only strip if the text actually starts with a fence marker
34
+ if not (stripped.startswith("```") or stripped.startswith("~~~")):
35
+ return stripped
36
+ result = _FENCE_OPEN.sub("", stripped, count=1)
37
+ result = _FENCE_CLOSE.sub("", result, count=1)
38
+ return result.strip()