stackfix 0.2.0
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.
- package/README.md +111 -0
- package/bin/stackfix +56 -0
- package/package.json +39 -0
- package/pyproject.toml +23 -0
- package/scripts/parse_selftest.py +13 -0
- package/scripts/postinstall.js +130 -0
- package/stackfix/__init__.py +1 -0
- package/stackfix/__main__.py +4 -0
- package/stackfix/__pycache__/__init__.cpython-314.pyc +0 -0
- package/stackfix/__pycache__/__main__.cpython-314.pyc +0 -0
- package/stackfix/__pycache__/agent.cpython-314.pyc +0 -0
- package/stackfix/__pycache__/agents.cpython-314.pyc +0 -0
- package/stackfix/__pycache__/cli.cpython-314.pyc +0 -0
- package/stackfix/__pycache__/context.cpython-314.pyc +0 -0
- package/stackfix/__pycache__/history.cpython-314.pyc +0 -0
- package/stackfix/__pycache__/patching.cpython-314.pyc +0 -0
- package/stackfix/__pycache__/safety.cpython-314.pyc +0 -0
- package/stackfix/__pycache__/session.cpython-314.pyc +0 -0
- package/stackfix/__pycache__/tui.cpython-314.pyc +0 -0
- package/stackfix/__pycache__/util.cpython-314.pyc +0 -0
- package/stackfix/agent.py +298 -0
- package/stackfix/agents.py +29 -0
- package/stackfix/cli.py +169 -0
- package/stackfix/context.py +73 -0
- package/stackfix/history.py +32 -0
- package/stackfix/patching.py +138 -0
- package/stackfix/safety.py +60 -0
- package/stackfix/session.py +40 -0
- package/stackfix/tui.py +591 -0
- package/stackfix/util.py +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# StackFix
|
|
2
|
+
|
|
3
|
+
> Terminal-first agentic CLI that fixes failing commands using AI
|
|
4
|
+
|
|
5
|
+
StackFix is a developer productivity tool that brings AI-powered assistance directly to your terminal. Describe what you want in natural language, wrap failing commands to get automated fixes, or use the interactive TUI for complex workflows.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- 🤖 **Natural language prompts** — Ask questions, get code suggestions
|
|
10
|
+
- 🔧 **Command wrapping** — Automatically diagnose and fix failing commands
|
|
11
|
+
- 🎨 **Syntax-highlighted diffs** — Clear visualization of proposed changes
|
|
12
|
+
- 💬 **Interactive TUI** — Rich terminal interface with slash commands
|
|
13
|
+
- 🔌 **Provider-agnostic** — Works with OpenAI, Nebius, or any compatible API
|
|
14
|
+
- 📝 **Session persistence** — Resume previous conversations
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
### npm (recommended)
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install -g stackfix
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### pip
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install stackfix
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### From source
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
git clone https://github.com/stackfix/stackfix.git
|
|
34
|
+
cd stackfix
|
|
35
|
+
pip install -e .
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Launch interactive TUI
|
|
42
|
+
stackfix
|
|
43
|
+
|
|
44
|
+
# Single prompt (non-interactive)
|
|
45
|
+
stackfix --prompt "explain this error: ImportError: No module named 'foo'"
|
|
46
|
+
|
|
47
|
+
# Wrap a failing command
|
|
48
|
+
stackfix -- pytest -q
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Configuration
|
|
52
|
+
|
|
53
|
+
Set environment variables for your LLM provider:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# OpenAI
|
|
57
|
+
export MODEL_BASE_URL="https://api.openai.com/v1"
|
|
58
|
+
export MODEL_API_KEY="sk-..."
|
|
59
|
+
export MODEL_NAME="gpt-4"
|
|
60
|
+
|
|
61
|
+
# Nebius
|
|
62
|
+
export MODEL_BASE_URL="https://api.tokenfactory.nebius.com/v1"
|
|
63
|
+
export MODEL_API_KEY="..."
|
|
64
|
+
export MODEL_NAME="openai/gpt-oss-120b"
|
|
65
|
+
|
|
66
|
+
# Local (Ollama)
|
|
67
|
+
export MODEL_BASE_URL="http://localhost:11434/v1"
|
|
68
|
+
export MODEL_NAME="codellama:13b"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## TUI Commands
|
|
72
|
+
|
|
73
|
+
| Command | Description |
|
|
74
|
+
|---------|-------------|
|
|
75
|
+
| `/help` | Show all available commands |
|
|
76
|
+
| `/exit` | Exit the TUI |
|
|
77
|
+
| `/model <name>` | View or switch the model |
|
|
78
|
+
| `/approvals <mode>` | Set approval mode (suggest, auto-edit, full-auto) |
|
|
79
|
+
| `/status` | Show current configuration |
|
|
80
|
+
| `/diff` | Show pending git changes |
|
|
81
|
+
| `/history` | Show last run summary |
|
|
82
|
+
| `/new` | Start a new session |
|
|
83
|
+
| `/resume <id>` | Resume a saved session |
|
|
84
|
+
|
|
85
|
+
Use `!command` to run shell commands directly.
|
|
86
|
+
|
|
87
|
+
## Project Context
|
|
88
|
+
|
|
89
|
+
Create an `AGENTS.md` file in your project root to provide persistent context:
|
|
90
|
+
|
|
91
|
+
```markdown
|
|
92
|
+
# Project Guidelines
|
|
93
|
+
|
|
94
|
+
- Use pytest for testing
|
|
95
|
+
- Follow PEP 8 style
|
|
96
|
+
- Prefer type hints
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Development
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# Install with dev dependencies
|
|
103
|
+
pip install -e ".[dev]"
|
|
104
|
+
|
|
105
|
+
# Run tests
|
|
106
|
+
pytest tests/ -v
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
MIT
|
package/bin/stackfix
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
|
|
7
|
+
// Get the package root directory
|
|
8
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
9
|
+
const venvPath = path.join(packageRoot, '.venv');
|
|
10
|
+
|
|
11
|
+
// Determine Python executable path
|
|
12
|
+
function getPythonPath() {
|
|
13
|
+
const isWindows = process.platform === 'win32';
|
|
14
|
+
|
|
15
|
+
// Check for venv first
|
|
16
|
+
if (fs.existsSync(venvPath)) {
|
|
17
|
+
const venvPython = isWindows
|
|
18
|
+
? path.join(venvPath, 'Scripts', 'python.exe')
|
|
19
|
+
: path.join(venvPath, 'bin', 'python');
|
|
20
|
+
if (fs.existsSync(venvPython)) {
|
|
21
|
+
return venvPython;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Fall back to system Python
|
|
26
|
+
return isWindows ? 'python' : 'python3';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Run stackfix
|
|
30
|
+
const pythonPath = getPythonPath();
|
|
31
|
+
const args = ['-m', 'stackfix', ...process.argv.slice(2)];
|
|
32
|
+
|
|
33
|
+
const child = spawn(pythonPath, args, {
|
|
34
|
+
stdio: 'inherit',
|
|
35
|
+
cwd: process.cwd(),
|
|
36
|
+
env: {
|
|
37
|
+
...process.env,
|
|
38
|
+
PYTHONPATH: packageRoot
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
child.on('error', (err) => {
|
|
43
|
+
if (err.code === 'ENOENT') {
|
|
44
|
+
console.error('Error: Python not found. Please install Python 3.9+');
|
|
45
|
+
console.error(' Windows: https://www.python.org/downloads/');
|
|
46
|
+
console.error(' macOS: brew install python3');
|
|
47
|
+
console.error(' Linux: apt install python3 python3-pip');
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
console.error('Error:', err.message);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
child.on('exit', (code) => {
|
|
55
|
+
process.exit(code || 0);
|
|
56
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "stackfix",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Terminal-first agentic CLI that fixes failing commands using AI",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cli",
|
|
7
|
+
"ai",
|
|
8
|
+
"developer-tools",
|
|
9
|
+
"agent",
|
|
10
|
+
"llm",
|
|
11
|
+
"terminal",
|
|
12
|
+
"debugging"
|
|
13
|
+
],
|
|
14
|
+
"author": "StackFix Contributors",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/dyglo/stackfix.git"
|
|
19
|
+
},
|
|
20
|
+
"bin": "bin/stackfix",
|
|
21
|
+
"scripts": {
|
|
22
|
+
"postinstall": "node scripts/postinstall.js"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=16.0.0"
|
|
26
|
+
},
|
|
27
|
+
"os": [
|
|
28
|
+
"darwin",
|
|
29
|
+
"linux",
|
|
30
|
+
"win32"
|
|
31
|
+
],
|
|
32
|
+
"files": [
|
|
33
|
+
"bin/",
|
|
34
|
+
"scripts/",
|
|
35
|
+
"stackfix/",
|
|
36
|
+
"pyproject.toml",
|
|
37
|
+
"README.md"
|
|
38
|
+
]
|
|
39
|
+
}
|
package/pyproject.toml
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "stackfix"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Terminal-first agentic CLI that fixes failing commands using AI"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"requests>=2.31",
|
|
13
|
+
"textual>=0.55",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.optional-dependencies]
|
|
17
|
+
dev = ["pytest>=7.0", "pytest-asyncio>=0.21"]
|
|
18
|
+
|
|
19
|
+
[project.scripts]
|
|
20
|
+
stackfix = "stackfix.cli:main"
|
|
21
|
+
|
|
22
|
+
[tool.setuptools.packages.find]
|
|
23
|
+
include = ["stackfix*"]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from stackfix.agent import _parse_agent_response
|
|
3
|
+
|
|
4
|
+
cases = [
|
|
5
|
+
'{"summary":"ok","confidence":0.5,"patch_unified_diff":"","rerun_command":["pytest","-q"]}',
|
|
6
|
+
'{"summary":"ok","confidence":"0.8","patch_unified_diff":null,"rerun_command":"pytest -q"}',
|
|
7
|
+
'{"summary":"ok"}',
|
|
8
|
+
'plain text response',
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
for i, content in enumerate(cases, 1):
|
|
12
|
+
result = _parse_agent_response(content)
|
|
13
|
+
print(f"case {i}: summary={result.get('summary')!r} rerun={result.get('rerun_command')} warning={result.get('_warning')}")
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* StackFix postinstall script
|
|
5
|
+
* Sets up Python virtual environment and installs dependencies
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { execSync, spawn } = require('child_process');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
|
|
12
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
13
|
+
const venvPath = path.join(packageRoot, '.venv');
|
|
14
|
+
const isWindows = process.platform === 'win32';
|
|
15
|
+
|
|
16
|
+
// ANSI colors
|
|
17
|
+
const GREEN = '\x1b[32m';
|
|
18
|
+
const YELLOW = '\x1b[33m';
|
|
19
|
+
const RED = '\x1b[31m';
|
|
20
|
+
const RESET = '\x1b[0m';
|
|
21
|
+
const BOLD = '\x1b[1m';
|
|
22
|
+
|
|
23
|
+
function log(msg) {
|
|
24
|
+
console.log(`${BOLD}[stackfix]${RESET} ${msg}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function success(msg) {
|
|
28
|
+
console.log(`${GREEN}✓${RESET} ${msg}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function warn(msg) {
|
|
32
|
+
console.log(`${YELLOW}⚠${RESET} ${msg}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function error(msg) {
|
|
36
|
+
console.error(`${RED}✗${RESET} ${msg}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Find Python 3.9+
|
|
40
|
+
function findPython() {
|
|
41
|
+
const candidates = isWindows
|
|
42
|
+
? ['python', 'python3', 'py -3']
|
|
43
|
+
: ['python3', 'python'];
|
|
44
|
+
|
|
45
|
+
for (const cmd of candidates) {
|
|
46
|
+
try {
|
|
47
|
+
const version = execSync(`${cmd} --version 2>&1`, { encoding: 'utf8' }).trim();
|
|
48
|
+
const match = version.match(/Python (\d+)\.(\d+)/);
|
|
49
|
+
if (match) {
|
|
50
|
+
const major = parseInt(match[1], 10);
|
|
51
|
+
const minor = parseInt(match[2], 10);
|
|
52
|
+
if (major === 3 && minor >= 9) {
|
|
53
|
+
return { cmd, version };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
// Try next candidate
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Main installation
|
|
64
|
+
async function main() {
|
|
65
|
+
log('Setting up StackFix...');
|
|
66
|
+
|
|
67
|
+
// Find Python
|
|
68
|
+
const python = findPython();
|
|
69
|
+
if (!python) {
|
|
70
|
+
error('Python 3.9+ is required but not found.');
|
|
71
|
+
console.log('');
|
|
72
|
+
console.log('Please install Python 3.9 or later:');
|
|
73
|
+
console.log(' Windows: https://www.python.org/downloads/');
|
|
74
|
+
console.log(' macOS: brew install python3');
|
|
75
|
+
console.log(' Linux: apt install python3 python3-pip');
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
success(`Found ${python.version} (${python.cmd})`);
|
|
80
|
+
|
|
81
|
+
// Create venv if it doesn't exist
|
|
82
|
+
if (!fs.existsSync(venvPath)) {
|
|
83
|
+
log('Creating virtual environment...');
|
|
84
|
+
try {
|
|
85
|
+
execSync(`${python.cmd} -m venv "${venvPath}"`, {
|
|
86
|
+
cwd: packageRoot,
|
|
87
|
+
stdio: 'inherit'
|
|
88
|
+
});
|
|
89
|
+
success('Virtual environment created');
|
|
90
|
+
} catch (err) {
|
|
91
|
+
error('Failed to create virtual environment');
|
|
92
|
+
console.log('Try running: pip install --user stackfix');
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
success('Virtual environment exists');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Determine pip path
|
|
100
|
+
const pipPath = isWindows
|
|
101
|
+
? path.join(venvPath, 'Scripts', 'pip.exe')
|
|
102
|
+
: path.join(venvPath, 'bin', 'pip');
|
|
103
|
+
|
|
104
|
+
// Install package in editable mode
|
|
105
|
+
log('Installing StackFix...');
|
|
106
|
+
try {
|
|
107
|
+
execSync(`"${pipPath}" install -e "${packageRoot}" --quiet`, {
|
|
108
|
+
cwd: packageRoot,
|
|
109
|
+
stdio: 'inherit'
|
|
110
|
+
});
|
|
111
|
+
success('StackFix installed successfully');
|
|
112
|
+
} catch (err) {
|
|
113
|
+
error('Failed to install StackFix');
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
console.log('');
|
|
118
|
+
log('Setup complete! Run "stackfix" to start.');
|
|
119
|
+
console.log('');
|
|
120
|
+
console.log('Quick start:');
|
|
121
|
+
console.log(' stackfix # Launch interactive TUI');
|
|
122
|
+
console.log(' stackfix --prompt "..." # Single prompt mode');
|
|
123
|
+
console.log(' stackfix -- pytest -v # Wrap and fix a command');
|
|
124
|
+
console.log('');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
main().catch((err) => {
|
|
128
|
+
error(err.message);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__all__ = ["cli"]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|