LogWise
LogWise is a lightweight Python CLI that runs shell commands, saves failures as JSON logs, detects common failure patterns with built-in rules, and optionally asks an AI provider to explain the error and suggest fixes.
Why LogWise?
When a command fails, the usual workflow is: rerun it, squint at the output, guess the root cause. LogWise shortens that loop by:
- running commands through a simple CLI,
- capturing stdout, stderr, exit codes, and timestamps,
- persisting every failure as a JSON log for later inspection,
- matching failures against built-in rules (missing files, permissions, …),
- suggesting practical fixes immediately,
- optionally consulting an AI provider — Gemini, OpenAI, DeepSeek, Groq, OpenRouter, or a local Ollama — through a single SDK-free HTTP layer.
Features
run— execute a command; print stdout on success, log + analyze on failureanalyze— re-analyze any saved log filelist— list saved logs as a table (file, command, exit code)prune— delete old logs by count (--keep) and/or age (--older-than), asks first unless--yes- Interactive retry (with command editing) / AI-offer / rerun prompts on terminals (off when piped;
--no-prompt/LOGWISE_NO_PROMPT) - Rule engine with extensible
ERROR_RULES - Multi-provider AI analysis over OpenAI-compatible APIs (stdlib
urllibonly, zero vendor SDKs), with spinner + per-provider accent colors - Rich terminal output with byte-identical plain fallback for pipes (
--no-color,NO_COLOR,LOGWISE_NO_COLOR) uv-managed project: locked deps, reproducible builds- stdlib
unittestsuite (tests/), Ruff lint + format, CI workflow
Requirements
- Python 3.10+ (
.python-versionpins 3.12) uvfor install/build (recommended)- A terminal environment
- Optional: an API key for your AI provider (see AI analysis);
ollamaneeds no key
Installation
git clone https://github.com/dhanush-ballanki/logwise.git
cd logwise
uv sync # create .venv, install locked deps + editable logwise
Run via uv run, or activate the venv (.venv\Scripts\activate on Windows, source .venv/bin/activate on Unix) and use logwise directly:
uv run logwise run "python script.py"
Build distributables (wheel + sdist into dist/):
uv build
Prefer plain pip? pyproject.toml is standards-based, so this works too:
python -m venv .venv
pip install -e .
Usage
Run a command
logwise run "python script.py"
Success prints stdout only. Failure saves logs/log_<timestamp>.json and prints the analysis.
logwise run "python script.py" --ai --provider openai --model gpt-4o-mini
Analyze an existing log
logwise analyze log_2025-04-06T10-30-00.json
logwise analyze log_2025-04-06T10-30-00.json --ai --provider groq
List captured logs
logwise list
Prune old logs
logwise prune --keep 50 # keep newest 50, preview + confirm
logwise prune --keep 20 --older-than 30 --yes # ...older than 30 days, no prompt
Example
logwise run "ls /missing/path"
Error occurred (exit code: 2)
Stderr captured:
ls: cannot access '/missing/path': No such file or directory
Analysis:
Found 2 rule-based issue(s).
- Reason: Command failed with non-zero exit code. (Possible reasons: Invalid arguments, missing dependencies, or runtime errors. Check stderr for details.)
Steps to fix: Verify command syntax, install missing packages, or debug the script.
- Reason: File or directory not found. (Path issue.)
Steps to fix: Check if the file exists (ls), correct the path, or create the missing item.
Output & colors
On a real terminal, failures render with Rich: a red ❌ Error header, stderr in a red panel, yellow ⚠ issue rows with green → fixes, and AI advice as a Markdown panel captioned with provider / model in that provider's accent color (blue Gemini, green OpenAI, violet DeepSeek, orange Groq, cyan OpenRouter, grey Ollama). list renders a table (file, command, exit code).
Plain text is automatic when output is piped or redirected, and can be forced with --no-color, NO_COLOR=1, or LOGWISE_NO_COLOR=1. Successful command stdout is never styled — it stays byte-identical so pipes and scripts keep working.
On terminals, run offers to retry a failed command — with a chance to edit it first (typos welcome), looping until it succeeds or you decline — and then offers AI analysis if you didn't pass --ai. analyze offers to re-run (and edit) the logged command. Prompts never appear when piped; --no-prompt / LOGWISE_NO_PROMPT=1 disables them.
Architecture
System overview
flowchart LR
subgraph CLI["CLI (main.py)"]
RUN["run"]
ANALYZE["analyze"]
LIST["list"]
end
subgraph CORE["Core"]
CAP["capture.py\ncapture_and_run()"]
ANZ["analyze.py\nanalyze_log_in_memory()"]
RULES["rules.py\napply_rules()"]
end
subgraph AILAYER["AI layer (SDK-free)"]
AI["ai.py\nai_analyze_err()"]
PROV["providers.py\nPROVIDERS table"]
end
STORE[("logs/\nlog_<iso>.json")]
RUN --> CAP
ANALYZE --> ANZ
LIST --> STORE
CAP --> STORE
CAP --> ANZ
ANZ --> RULES
ANZ --> AI
AI --> PROV
AI --> EXT[("AI APIs\nOpenAI-compatible\n/chat/completions")]
Module responsibilities:
| Module | Role |
|---|---|
main.py |
Typer app with run / analyze / list commands; owns --ai, --provider, --model flags |
capture.py |
Runs the command via subprocess.run(shell=True); writes the JSON log on failure; prints the analysis |
analyze.py |
analyze_log_in_memory() — the analysis orchestrator; analyze_log() loads a file then delegates; list_logs() lists logs/ |
rules.py |
ERROR_RULES list + apply_rules(); new rules are plain dicts |
ai.py |
One POST {base_url}/chat/completions call via stdlib urllib; tolerant reason/fixes parsing |
providers.py |
PROVIDERS table (base_url, key_env, default_model) + resolve_*() helpers; adding a vendor is data-only |
src/logwise/ has no __init__.py (namespace package).
logwise run lifecycle
sequenceDiagram
participant U as User
participant CLI as main.py run
participant CAP as capture_and_run
participant FS as logs/
participant ANZ as analyze_log_in_memory
U->>CLI: logwise run "cmd" [--ai]
CLI->>CAP: capture_and_run(cmd, use_ai, provider, model)
CAP->>CAP: subprocess.run(shell=True)
alt exit 0 and empty stderr
CAP->>U: print(stdout)
else failure
CAP->>FS: write log_<iso>.json
CAP->>ANZ: analyze_log_in_memory(entry, ...)
ANZ->>U: via CAP: summary + issues + ai_analysis
end
Quirk:
returncode == 0with non-empty stderr counts as failure and writes a log.
Analysis decision flow
flowchart TD
START(["analyze_log_in_memory(log, use_ai)"]) --> ISAI{use_ai?}
ISAI -- yes --> AIONLY["ai_analyze_err()\n(rules skipped)"]
AIONLY --> S1["summary = 'AI-based analysis requested.'\nor 'AI analysis failed: ...'"]
ISAI -- no --> RULES["apply_rules()"]
RULES --> HIT{issues?}
HIT -- yes --> S2["summary = 'Found N rule-based issue(s).'"]
HIT -- no --> FB["ai_analyze_err() fallback"]
FB --> S3["summary = 'No rule-based issues. AI fallback used.'\nor '... AI fallback failed: ...'"]
--ai is AI-only (rules skipped). Default mode runs rules first and calls AI only when zero rules match. AI errors are surfaced in summary — never silent, never a traceback.
AI layer: one HTTP path, N providers
flowchart LR
AI["ai_analyze_err()\nresolves provider/model/key"] --> HTTP["_post_chat_completions()\nurllib POST\n{base_url}/chat/completions"]
HTTP --> GEM["Gemini\n.../v1beta/openai"]
HTTP --> OAI["OpenAI"]
HTTP --> DS["DeepSeek"]
HTTP --> GR["Groq"]
HTTP --> OR["OpenRouter"]
HTTP --> OL["Ollama\nlocalhost:11434"]
Request shape: {model, messages: [{role: user, content: prompt}], temperature: 0.7, max_tokens: 700}, with Authorization: Bearer <key> when the provider needs one. The response text is split into reason / fixes on the step-by-step marker (case-insensitive, tolerant of missing headers).
Resolution precedence
flowchart LR
F["CLI flag\n--provider / --model"] --> E["Env\nLOGWISE_*"] --> D["Provider default"]
| Setting | Flag | Env | Default |
|---|---|---|---|
| Provider | --provider |
LOGWISE_PROVIDER |
gemini |
| Model | --model |
LOGWISE_MODEL |
provider's default_model |
| Base URL | — | LOGWISE_BASE_URL |
provider's base_url |
| API key | — | LOGWISE_API_KEY then provider key env |
error unless keyless/localhost |
Log format
Each failure is stored as logs/log_<ISO-timestamp>.json (LOG_DIR is src/logwise/../../logs, i.e. repo-root logs/, not configurable):
{
"command": "ls /missing/path",
"start_time": "2026-09-20T06:49:37.422836",
"end_time": "2026-09-20T06:49:37.435101",
"stdout": "",
"stderr": "ls: cannot access '/missing/path': No such file or directory",
"exit_code": 2
}
And analyze_log_in_memory() returns:
{
"log": {...}, # the entry above
"issues": [...], # rule hits (default mode only)
"summary": "Found 2 rule-based issue(s).",
"ai_analysis": { # only on AI success
"reason": "...", "fixes": "...",
"provider": "groq", "model": "llama-3.3-70b-versatile",
},
}
Rule-based detection
src/logwise/rules.py currently ships three rules:
non_zero_exit— exit code ≠ 0permission_denied—permission deniedin stderrfile_not_found—no such file or directoryin stderr
Add a rule as a dict — no framework, no registration calls:
{
'id': 'module_not_found',
'condition': lambda log: 'modulenotfounderror' in log['stderr'].lower().replace(' ', ''),
'description': 'Python module not found.',
'root_cause': 'Missing dependency.',
'fixes': 'Install it with pip (pip install <package>).'
}
AI analysis
When --ai is used, LogWise sends the command, exit code, and stderr to the selected provider and returns a concise reason plus fix steps.
Available providers (--provider, or LOGWISE_PROVIDER env, default gemini):
| Provider | Key env | Default model |
|---|---|---|
gemini |
GEMINI_API_KEY |
gemini-2.0-flash |
openai |
OPENAI_API_KEY |
gpt-4o-mini |
deepseek |
DEEPSEEK_API_KEY |
deepseek-chat |
groq |
GROQ_API_KEY |
llama-3.3-70b-versatile |
openrouter |
OPENROUTER_API_KEY |
openai/gpt-4o-mini |
ollama |
none (local) | llama3.1 |
export GEMINI_API_KEY="your_key_here" # or OPENAI_API_KEY / GROQ_API_KEY / ...
logwise run "python script.py" --ai
logwise run "python script.py" --ai --provider openai --model gpt-4o-mini
logwise analyze log_....json --ai --provider groq
ollama serve & logwise run "python script.py" --ai --provider ollama
Generic overrides for any OpenAI-compatible server: LOGWISE_API_KEY,
LOGWISE_MODEL, LOGWISE_BASE_URL. All of these — plus the provider keys —
can live in .env instead of exports (see Installation).
Configuration reference
| Env var | Purpose | Default |
|---|---|---|
LOGWISE_PROVIDER |
AI provider name | gemini |
LOGWISE_MODEL |
Model override | provider default |
LOGWISE_BASE_URL |
Custom OpenAI-compatible endpoint | provider default |
LOGWISE_API_KEY |
Generic key (beats provider-specific env) | — |
GEMINI_API_KEY / OPENAI_API_KEY / DEEPSEEK_API_KEY / GROQ_API_KEY / OPENROUTER_API_KEY |
Provider keys | — |
LOGWISE_ENV_FILE |
Explicit .env path (skips upward search) |
nearest .env from cwd upward |
LOGWISE_LOG_DIR |
Where failure logs are stored | logs/ under cwd |
LOGWISE_NO_COLOR / NO_COLOR |
Force plain-text output | color when attached to a terminal |
Flags beat env vars: --provider > LOGWISE_PROVIDER, --model > LOGWISE_MODEL.
File beats nothing: exported variables always win over .env values.
Project structure
logwise/
├── .github/workflows/ # CI: ruff + unittest + build
├── AGENTS.md
├── LICENSE
├── README.md
├── pyproject.toml # deps + build config (setuptools, src layout)
├── uv.lock # locked deps (committed)
├── .python-version # pins 3.12
├── src/
│ └── logwise/
│ ├── ai.py # SDK-free chat-completions client
│ ├── analyze.py # analysis orchestrator + prune_logs()
│ ├── capture.py # subprocess runner + log writer
│ ├── display.py # Rich terminal rendering (only color-aware module)
│ ├── env.py # stdlib .env loader (no extra dependency)
│ ├── main.py # Typer CLI (run | analyze | list | prune)
│ ├── paths.py # runtime data dirs (cwd-based, install-safe)
│ ├── providers.py # provider registry
│ └── rules.py # ERROR_RULES
├── tests/ # stdlib unittest suite (71 tests)
├── logs/ # auto-created at startup (cwd-based; override with LOGWISE_LOG_DIR)
├── dist/ # uv build output (git-ignored)
└── .venv/ # uv venv (git-ignored)
Development
uv sync --group dev # install dev tools (ruff)
uv run python -m unittest discover # 71 tests, stdlib only
uv run --group dev ruff check src tests
uv run --group dev ruff format --check src tests
uv run logwise run "ls /missing/path" # rules path
uv run logwise run "echo hi" # success path
uv run logwise list
uv run logwise prune --keep 50 --yes # rotate old logs
uv build # wheel + sdist into dist/
CI (.github/workflows/ci.yml) runs ruff, the test suite, and uv build on every push to main and every PR. To exercise the AI layer without spending API calls, point LOGWISE_BASE_URL at any stub that answers POST /chat/completions with {"choices": [{"message": {"content": "..."}}]}.
Publishing to PyPI
The package is publish-ready: SPDX license, readme, classifiers, project URLs, src layout, and two runtime dependencies (typer, rich).
uv build # wheel + sdist into dist/
uv publish # needs a PyPI token (or: twine upload dist/*)
Checklist before the first upload:
uv buildsucceeds with no warnings.- Bump
versioninpyproject.tomlfor every subsequent release. - Never commit
.env(git-ignored) ordist/(git-ignored). pip install logwisein a fresh venv, thenlogwise run "ls /missing"from an unrelated directory — logs must land in that directory'slogs/, never insite-packages.
Contributing
Contributions are welcome:
- Fork the repository
- Create a feature branch
- Make your changes (new rules go in
rules.py, new vendors inproviders.py) - Sanity-check with
uv run logwise runon a failing + passing command - Submit a pull request
License
This project is licensed under the MIT License. See the LICENSE file for details.
Maintainer
Dhanush Ballanki
Release files for logwise-cli 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| logwise_cli-0.2.0.tar.gz | 33.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| logwise_cli-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 56.6 kB
Release files / logwise_cli-0.2.0.tar.gz
| Download URL | logwise_cli-0.2.0.tar.gz |
|---|---|
| Size | 33.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4aef46a2a374703a0df460c3283f7e3511b002fd7e8614e4c96863a7441a5592
|
|
BLAKE2b-256 checksum How to use checksums |
39be3e5b10e378d034a51c4d9e5e028d17c523b65064124348d4f99c869fd809
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.16 {"installer":{"name":"uv","version":"0.9.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / logwise_cli-0.2.0-py3-none-any.whl
| Download URL | logwise_cli-0.2.0-py3-none-any.whl |
|---|---|
| Size | 23.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0d56705592271437d6c3cc3f88e72833abae76daa0708aca83a2c7408e5f14c7
|
|
BLAKE2b-256 checksum How to use checksums |
88807e13dde700b21ac909c9e785a8fc882950e3d903dd7332adf9267833b1d2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.16 {"installer":{"name":"uv","version":"0.9.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|