locally796 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.
- locally796-0.1.0/LICENSE +21 -0
- locally796-0.1.0/PKG-INFO +103 -0
- locally796-0.1.0/README.md +89 -0
- locally796-0.1.0/app/__init__.py +0 -0
- locally796-0.1.0/app/cli/__init__.py +0 -0
- locally796-0.1.0/app/cli/app.py +59 -0
- locally796-0.1.0/app/config/__init__.py +0 -0
- locally796-0.1.0/app/config/env.py +10 -0
- locally796-0.1.0/app/config/settings.py +14 -0
- locally796-0.1.0/app/core/__init__.py +0 -0
- locally796-0.1.0/app/core/agent.py +58 -0
- locally796-0.1.0/app/core/llm.py +12 -0
- locally796-0.1.0/app/core/tools.py +49 -0
- locally796-0.1.0/app/main.py +5 -0
- locally796-0.1.0/app/services/__init__.py +0 -0
- locally796-0.1.0/app/services/detector.py +24 -0
- locally796-0.1.0/app/services/executor.py +58 -0
- locally796-0.1.0/app/services/repo.py +21 -0
- locally796-0.1.0/app/utils/__init__.py +0 -0
- locally796-0.1.0/app/utils/constants.py +9 -0
- locally796-0.1.0/app/utils/logger.py +16 -0
- locally796-0.1.0/locally796.egg-info/PKG-INFO +103 -0
- locally796-0.1.0/locally796.egg-info/SOURCES.txt +28 -0
- locally796-0.1.0/locally796.egg-info/dependency_links.txt +1 -0
- locally796-0.1.0/locally796.egg-info/entry_points.txt +2 -0
- locally796-0.1.0/locally796.egg-info/requires.txt +4 -0
- locally796-0.1.0/locally796.egg-info/top_level.txt +1 -0
- locally796-0.1.0/pyproject.toml +27 -0
- locally796-0.1.0/setup.cfg +4 -0
- locally796-0.1.0/tests/test.py +5 -0
locally796-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Prathmesh Ravindra Salunkhe
|
|
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 OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: locally796
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AI-powered CLI tool to setup GitHub repositories locally
|
|
5
|
+
Author-email: Prathmesh Salunkhe <salunkheprathmesh0@gmail.com>
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: typer
|
|
10
|
+
Requires-Dist: rich
|
|
11
|
+
Requires-Dist: gitpython
|
|
12
|
+
Requires-Dist: langchain
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# Locally796
|
|
16
|
+
|
|
17
|
+
**Locally796** is an AI-powered CLI tool that automatically clones any GitHub repository and sets it up on your local machine — no manual configuration needed. Powered by a LangChain agent, it detects the project stack, installs dependencies, configures environment variables, and runs the project for you.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- 🤖 **AI-Driven Setup** — A LangChain agent acts as a senior DevOps engineer, analysing the repository, running the right install commands, and resolving errors automatically.
|
|
24
|
+
- 🔍 **Stack Detection** — Automatically identifies the project stack (Node.js, Python, Go, etc.) and applies the appropriate setup steps.
|
|
25
|
+
- ⚙️ **Dependency Installation** — Runs `npm install`, `pip install -r requirements.txt`, `go mod tidy`, and similar commands as needed.
|
|
26
|
+
- 🌿 **Environment Configuration** — Detects `.env.example` files and sets up environment variables for you.
|
|
27
|
+
- 🚀 **Project Launch** — Starts the project after setup and confirms it is running.
|
|
28
|
+
- 📦 **pip Package** — Distributed as a Python package, so you can install and use it anywhere with a single command.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Requirements
|
|
33
|
+
|
|
34
|
+
- Python 3.11 or higher
|
|
35
|
+
- A [Groq](https://console.groq.com/) API key to power the AI agent.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
You can install `locally796` globally using `pip`:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install locally796
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
*(Alternatively, to install from source: clone this repo and run `pip install -e .` inside the folder.)*
|
|
48
|
+
|
|
49
|
+
### Setup Your API Key
|
|
50
|
+
|
|
51
|
+
Before using `locally796` for the first time, you need to configure your Groq API key. The agent defaults to `llama-3.3-70b-versatile`, which is available for any standard Groq account.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
locally796 set-key <YOUR_GROQ_API_KEY>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
This securely saves your key in a global config file (`~/.locally796/.env`), meaning you can run the tool from anywhere on your system without having to set up `.env` files manually.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Usage
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
locally796 [GITHUB_REPO_URL] --path [CLONING_PATH]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Arguments
|
|
68
|
+
|
|
69
|
+
| Argument | Description |
|
|
70
|
+
|---|---|
|
|
71
|
+
| `GITHUB_REPO_URL` | The full URL of the GitHub repository to clone and set up |
|
|
72
|
+
| `--path` / `-p` | *(Optional)* Local directory path where the repo will be cloned. Defaults to a folder named after the repo in the current directory |
|
|
73
|
+
|
|
74
|
+
### Example
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
locally796 https://github.com/prathmesh796/pubsubs --path ./test-clone
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
This single command will autonomously:
|
|
81
|
+
1. Clone the repository into `./test-clone`
|
|
82
|
+
2. Detect the technology stack (e.g. Node.js, Python)
|
|
83
|
+
3. Launch the LangChain Agent to investigate the codebase.
|
|
84
|
+
4. Install all required dependencies (`npm install`, `pip install`, etc.)
|
|
85
|
+
5. Configure environment variables (if `.env.example` is present)
|
|
86
|
+
6. Start the local development server (e.g., `npm run dev`)
|
|
87
|
+
7. Automatically detect and fix any deployment errors if the server fails to start!
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## How It Works
|
|
92
|
+
|
|
93
|
+
1. **Clone** — The repo is cloned to the specified (or auto-generated) local path using GitPython.
|
|
94
|
+
2. **Detect** — The stack detector inspects the repository files to identify languages and frameworks.
|
|
95
|
+
3. **Agent Setup** — A LangChain agent equipped with shell, file-read, and file-write tools executes the full setup autonomously. If a command fails or crashes, the agent reads the error output, figures out the fix, and retries until success!
|
|
96
|
+
4. **Done** — The agent reports `SETUP_COMPLETE` and leaves the server running for you.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
MIT
|
|
103
|
+
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Locally796
|
|
2
|
+
|
|
3
|
+
**Locally796** is an AI-powered CLI tool that automatically clones any GitHub repository and sets it up on your local machine — no manual configuration needed. Powered by a LangChain agent, it detects the project stack, installs dependencies, configures environment variables, and runs the project for you.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- 🤖 **AI-Driven Setup** — A LangChain agent acts as a senior DevOps engineer, analysing the repository, running the right install commands, and resolving errors automatically.
|
|
10
|
+
- 🔍 **Stack Detection** — Automatically identifies the project stack (Node.js, Python, Go, etc.) and applies the appropriate setup steps.
|
|
11
|
+
- ⚙️ **Dependency Installation** — Runs `npm install`, `pip install -r requirements.txt`, `go mod tidy`, and similar commands as needed.
|
|
12
|
+
- 🌿 **Environment Configuration** — Detects `.env.example` files and sets up environment variables for you.
|
|
13
|
+
- 🚀 **Project Launch** — Starts the project after setup and confirms it is running.
|
|
14
|
+
- 📦 **pip Package** — Distributed as a Python package, so you can install and use it anywhere with a single command.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Requirements
|
|
19
|
+
|
|
20
|
+
- Python 3.11 or higher
|
|
21
|
+
- A [Groq](https://console.groq.com/) API key to power the AI agent.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
You can install `locally796` globally using `pip`:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install locally796
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
*(Alternatively, to install from source: clone this repo and run `pip install -e .` inside the folder.)*
|
|
34
|
+
|
|
35
|
+
### Setup Your API Key
|
|
36
|
+
|
|
37
|
+
Before using `locally796` for the first time, you need to configure your Groq API key. The agent defaults to `llama-3.3-70b-versatile`, which is available for any standard Groq account.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
locally796 set-key <YOUR_GROQ_API_KEY>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
This securely saves your key in a global config file (`~/.locally796/.env`), meaning you can run the tool from anywhere on your system without having to set up `.env` files manually.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
locally796 [GITHUB_REPO_URL] --path [CLONING_PATH]
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Arguments
|
|
54
|
+
|
|
55
|
+
| Argument | Description |
|
|
56
|
+
|---|---|
|
|
57
|
+
| `GITHUB_REPO_URL` | The full URL of the GitHub repository to clone and set up |
|
|
58
|
+
| `--path` / `-p` | *(Optional)* Local directory path where the repo will be cloned. Defaults to a folder named after the repo in the current directory |
|
|
59
|
+
|
|
60
|
+
### Example
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
locally796 https://github.com/prathmesh796/pubsubs --path ./test-clone
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
This single command will autonomously:
|
|
67
|
+
1. Clone the repository into `./test-clone`
|
|
68
|
+
2. Detect the technology stack (e.g. Node.js, Python)
|
|
69
|
+
3. Launch the LangChain Agent to investigate the codebase.
|
|
70
|
+
4. Install all required dependencies (`npm install`, `pip install`, etc.)
|
|
71
|
+
5. Configure environment variables (if `.env.example` is present)
|
|
72
|
+
6. Start the local development server (e.g., `npm run dev`)
|
|
73
|
+
7. Automatically detect and fix any deployment errors if the server fails to start!
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## How It Works
|
|
78
|
+
|
|
79
|
+
1. **Clone** — The repo is cloned to the specified (or auto-generated) local path using GitPython.
|
|
80
|
+
2. **Detect** — The stack detector inspects the repository files to identify languages and frameworks.
|
|
81
|
+
3. **Agent Setup** — A LangChain agent equipped with shell, file-read, and file-write tools executes the full setup autonomously. If a command fails or crashes, the agent reads the error output, figures out the fix, and retries until success!
|
|
82
|
+
4. **Done** — The agent reports `SETUP_COMPLETE` and leaves the server running for you.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT
|
|
89
|
+
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
import os
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
|
|
6
|
+
from app.services.repo import clone_repo
|
|
7
|
+
from app.services.detector import detect_stack
|
|
8
|
+
from app.core.agent import run_setup_and_start
|
|
9
|
+
from app.utils.logger import logger
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(help="Locally796 - AI powered CLI tool to clone and run any repo.", add_completion=False)
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
@app.command()
|
|
15
|
+
def set_key(api_key: str = typer.Argument(..., help="Your Groq API Key")):
|
|
16
|
+
"""Sets the global Groq API key for Locally796."""
|
|
17
|
+
home_dir = os.path.expanduser("~/.locally796")
|
|
18
|
+
os.makedirs(home_dir, exist_ok=True)
|
|
19
|
+
env_file = os.path.join(home_dir, ".env")
|
|
20
|
+
|
|
21
|
+
with open(env_file, "w") as f:
|
|
22
|
+
f.write(f"GROQ_API_KEY={api_key}\n")
|
|
23
|
+
|
|
24
|
+
console.print("[bold green]✅ API Key saved successfully![/bold green] You can now run `locally796` from anywhere.")
|
|
25
|
+
|
|
26
|
+
@app.command()
|
|
27
|
+
def clone_and_run(
|
|
28
|
+
repo_url: str = typer.Argument(..., help="The GitHub repository URL to process"),
|
|
29
|
+
path: Optional[str] = typer.Option(None, "--path", "-p", help="Destination path for cloning"),
|
|
30
|
+
):
|
|
31
|
+
"""
|
|
32
|
+
Clone a repository, detect stack, install dependencies, and run it.
|
|
33
|
+
"""
|
|
34
|
+
console.print(f"[bold blue]Starting Locally796 for:[/bold blue] {repo_url}")
|
|
35
|
+
if path:
|
|
36
|
+
target_path = path
|
|
37
|
+
else:
|
|
38
|
+
# Predict the repo name from the url
|
|
39
|
+
base_name = repo_url.rstrip('/').split('/')[-1]
|
|
40
|
+
if base_name.endswith('.git'):
|
|
41
|
+
base_name = base_name[:-4]
|
|
42
|
+
target_path = os.path.join(os.getcwd(), base_name)
|
|
43
|
+
|
|
44
|
+
console.print(f"[bold blue]Target path:[/bold blue] {target_path}")
|
|
45
|
+
|
|
46
|
+
# Step 1: Clone Repo
|
|
47
|
+
success = clone_repo(repo_url, target_path)
|
|
48
|
+
if not success:
|
|
49
|
+
console.print("[bold red]Aborting due to clone failure.[/bold red]")
|
|
50
|
+
raise typer.Exit(code=1)
|
|
51
|
+
|
|
52
|
+
# Step 2: Detect Stack
|
|
53
|
+
detected_stacks = detect_stack(target_path)
|
|
54
|
+
|
|
55
|
+
# Step 3: Run Setup Agent
|
|
56
|
+
run_setup_and_start(target_path, detected_stacks)
|
|
57
|
+
|
|
58
|
+
if __name__ == "__main__":
|
|
59
|
+
app()
|
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from dotenv import load_dotenv
|
|
3
|
+
|
|
4
|
+
home_env = os.path.expanduser("~/.locally796/.env")
|
|
5
|
+
if os.path.exists(home_env):
|
|
6
|
+
load_dotenv(home_env)
|
|
7
|
+
|
|
8
|
+
load_dotenv()
|
|
9
|
+
|
|
10
|
+
class Settings:
|
|
11
|
+
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
|
|
12
|
+
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
|
|
13
|
+
|
|
14
|
+
settings = Settings()
|
|
File without changes
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from langchain_classic.agents import AgentExecutor, create_tool_calling_agent
|
|
2
|
+
from langchain_core.tools import tool
|
|
3
|
+
from langchain_core.prompts import ChatPromptTemplate
|
|
4
|
+
from app.core.llm import get_llm
|
|
5
|
+
from app.core.tools import run_shell_command, read_file, list_dir, write_file
|
|
6
|
+
from app.utils.logger import logger, console
|
|
7
|
+
|
|
8
|
+
AGENT_SYSTEM_PROMPT = """You are a senior DevOps engineer and setup assistant.
|
|
9
|
+
Your task is to properly setup and run a software project.
|
|
10
|
+
|
|
11
|
+
You have the following capabilities:
|
|
12
|
+
1. Examine project files and directories to understand the structure.
|
|
13
|
+
2. Formulate necessary setup commands (e.g. `npm install`, `pip install -r requirements.txt`, `go mod tidy`).
|
|
14
|
+
3. Set up environment variables if a `.env.example` exists.
|
|
15
|
+
4. Execute run commands (e.g. `npm run dev`, `python app.py`) to verify it works.
|
|
16
|
+
|
|
17
|
+
If a command fails, read its output, figure out the issue, fix it, and retry.
|
|
18
|
+
NEVER execute obviously destructive commands (like `rm -rf /`).
|
|
19
|
+
Always execute commands in the correct `cwd` which the user provides as context.
|
|
20
|
+
Important: When running a dev server that blocks (like `npm run dev`), if it prints 'compiled successfully' or starts listening on a port, consider the step SUCCESSFUL. Our executor incorporates a 60-second timeout. If it prints a timeout error but indicates a server has started, consider the operation successful.
|
|
21
|
+
|
|
22
|
+
When you have successfully set everything up and run the project, respond with "SETUP_COMPLETE" and explain what you did.
|
|
23
|
+
|
|
24
|
+
CRITICAL TOOL USAGE INSTRUCTION:
|
|
25
|
+
When calling a tool, ensure the tool name strictly matches the function name (e.g., 'list_dir'). DO NOT put JSON arguments or string blocks inside the tool name field itself! Provide arguments normally via the valid arguments object.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def run_setup_and_start(target_path: str, detected_stacks: list):
|
|
29
|
+
try:
|
|
30
|
+
llm = get_llm()
|
|
31
|
+
except Exception as e:
|
|
32
|
+
console.print(f"[bold red]LLM Initialization Error:[/bold red] {e}")
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
tools = [run_shell_command, read_file, list_dir, write_file]
|
|
36
|
+
|
|
37
|
+
prompt = ChatPromptTemplate.from_messages([
|
|
38
|
+
("system", AGENT_SYSTEM_PROMPT),
|
|
39
|
+
("human", "Set up the project in the `{target_path}` directory.\nDetected stacks: {detected_stacks}.\nPlease execute the installation steps and then run the project. Use your tools sequentially to accomplish this."),
|
|
40
|
+
("placeholder", "{agent_scratchpad}"),
|
|
41
|
+
])
|
|
42
|
+
|
|
43
|
+
agent = create_tool_calling_agent(llm, tools, prompt)
|
|
44
|
+
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True)
|
|
45
|
+
|
|
46
|
+
console.print(f"[bold blue]Initializing Setup Agent for:[/bold blue] {target_path}")
|
|
47
|
+
logger.info("Starting agent executor.")
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
response = agent_executor.invoke({
|
|
51
|
+
"target_path": target_path,
|
|
52
|
+
"detected_stacks": ", ".join(detected_stacks) if detected_stacks else "Unknown"
|
|
53
|
+
})
|
|
54
|
+
console.print("[bold green]Agent completed its process.[/bold green]")
|
|
55
|
+
logger.info(f"Agent response: {response.get('output', '')}")
|
|
56
|
+
except Exception as e:
|
|
57
|
+
console.print(f"[bold red]Agent encountered a fatal error:[/bold red] {e}")
|
|
58
|
+
logger.error(f"Agent execution failed: {e}")
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from langchain_groq import ChatGroq
|
|
2
|
+
from app.config.settings import settings
|
|
3
|
+
|
|
4
|
+
def get_llm():
|
|
5
|
+
if not settings.GROQ_API_KEY:
|
|
6
|
+
raise ValueError("GROQ_API_KEY is not set. Please run 'locally796 set-key <YOUR_API_KEY>' to configure it globally.")
|
|
7
|
+
return ChatGroq(
|
|
8
|
+
model="llama-3.3-70b-versatile",
|
|
9
|
+
api_key=settings.GROQ_API_KEY,
|
|
10
|
+
temperature=0.2,
|
|
11
|
+
max_retries=2
|
|
12
|
+
)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from langchain.tools import tool
|
|
3
|
+
from app.services.executor import execute_command
|
|
4
|
+
|
|
5
|
+
@tool
|
|
6
|
+
def run_shell_command(command: str, cwd: str) -> str:
|
|
7
|
+
"""
|
|
8
|
+
Executes a shell command in the specified directory (cwd) and returns its output.
|
|
9
|
+
Useful for installing dependencies, running the app, or fixing environments.
|
|
10
|
+
"""
|
|
11
|
+
res = execute_command(command, cwd)
|
|
12
|
+
output = f"Status: {res['status']}\nExit code: {res['return_code']}\n"
|
|
13
|
+
if res['stdout']:
|
|
14
|
+
output += f"STDOUT:\n{res['stdout']}\n"
|
|
15
|
+
if res['stderr']:
|
|
16
|
+
output += f"STDERR:\n{res['stderr']}\n"
|
|
17
|
+
return output
|
|
18
|
+
|
|
19
|
+
@tool
|
|
20
|
+
def read_file(file_path: str) -> str:
|
|
21
|
+
"""Reads the contents of a file."""
|
|
22
|
+
if not os.path.exists(file_path):
|
|
23
|
+
return f"Error: File '{file_path}' does not exist."
|
|
24
|
+
try:
|
|
25
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
26
|
+
return f.read()
|
|
27
|
+
except Exception as e:
|
|
28
|
+
return f"Error reading file '{file_path}': {str(e)}"
|
|
29
|
+
|
|
30
|
+
@tool
|
|
31
|
+
def list_dir(directory: str) -> str:
|
|
32
|
+
"""Lists files and directories in the specified directory."""
|
|
33
|
+
if not os.path.exists(directory):
|
|
34
|
+
return f"Error: Directory '{directory}' does not exist."
|
|
35
|
+
try:
|
|
36
|
+
items = os.listdir(directory)
|
|
37
|
+
return "\n".join(items) if items else "Directory is empty."
|
|
38
|
+
except Exception as e:
|
|
39
|
+
return f"Error listing directory '{directory}': {str(e)}"
|
|
40
|
+
|
|
41
|
+
@tool
|
|
42
|
+
def write_file(file_path: str, content: str) -> str:
|
|
43
|
+
"""Writes content to a file. Useful for creating .env files or fixing configs."""
|
|
44
|
+
try:
|
|
45
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
46
|
+
f.write(content)
|
|
47
|
+
return f"File '{file_path}' written successfully."
|
|
48
|
+
except Exception as e:
|
|
49
|
+
return f"Error writing file '{file_path}': {str(e)}"
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import List
|
|
3
|
+
from app.utils.constants import STACK_FILES
|
|
4
|
+
from app.utils.logger import logger, console
|
|
5
|
+
|
|
6
|
+
def detect_stack(target_path: str) -> List[str]:
|
|
7
|
+
detected = []
|
|
8
|
+
logger.info(f"Detecting stack in {target_path}...")
|
|
9
|
+
for filename, stack_name in STACK_FILES.items():
|
|
10
|
+
file_path = os.path.join(target_path, filename)
|
|
11
|
+
if os.path.exists(file_path):
|
|
12
|
+
detected.append(stack_name)
|
|
13
|
+
|
|
14
|
+
# Deduplicate while preserving order if needed
|
|
15
|
+
detected = list(set(detected))
|
|
16
|
+
|
|
17
|
+
if detected:
|
|
18
|
+
console.print(f"[bold green]✓ Detected Stack:[/bold green] {', '.join(detected)}")
|
|
19
|
+
logger.info(f"Detected stacks: {detected}")
|
|
20
|
+
else:
|
|
21
|
+
console.print("[yellow]⚠ Could not automatically detect a standard stack.[/yellow]")
|
|
22
|
+
logger.warning("No stack detected.")
|
|
23
|
+
|
|
24
|
+
return detected
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import sys
|
|
3
|
+
from typing import Dict, Any
|
|
4
|
+
from app.utils.logger import logger, console
|
|
5
|
+
|
|
6
|
+
def execute_command(command: str, cwd: str, timeout: int = 60) -> Dict[str, Any]:
|
|
7
|
+
"""Execute a shell command and return its output. Includes a timeout for long-running processes."""
|
|
8
|
+
logger.info(f"Executing: `{command}` in {cwd} with timeout {timeout}s")
|
|
9
|
+
console.print(f"[dim]Running:[/dim] {command}")
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
process = subprocess.Popen(
|
|
13
|
+
command,
|
|
14
|
+
cwd=cwd,
|
|
15
|
+
shell=True,
|
|
16
|
+
text=True,
|
|
17
|
+
stdout=subprocess.PIPE,
|
|
18
|
+
stderr=subprocess.PIPE
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
stdout, stderr = process.communicate(timeout=timeout)
|
|
23
|
+
return {
|
|
24
|
+
"command": command,
|
|
25
|
+
"return_code": process.returncode,
|
|
26
|
+
"stdout": stdout.strip() if stdout else "",
|
|
27
|
+
"stderr": stderr.strip() if stderr else "",
|
|
28
|
+
"status": "completed"
|
|
29
|
+
}
|
|
30
|
+
except subprocess.TimeoutExpired:
|
|
31
|
+
logger.warning(f"Execution timed out after {timeout} seconds.")
|
|
32
|
+
|
|
33
|
+
if sys.platform == "win32":
|
|
34
|
+
subprocess.run(f"taskkill /F /T /PID {process.pid}", shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
35
|
+
else:
|
|
36
|
+
process.kill()
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
stdout, stderr = process.communicate(timeout=2)
|
|
40
|
+
except subprocess.TimeoutExpired:
|
|
41
|
+
stdout, stderr = "Process is still running (Timeout reached). Likely a server started successfully.", ""
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
"command": command,
|
|
45
|
+
"return_code": 0,
|
|
46
|
+
"stdout": stdout.strip() if stdout else "Process is still running (Timeout reached). Likely a server started successfully.",
|
|
47
|
+
"stderr": stderr.strip() if stderr else "",
|
|
48
|
+
"status": "timeout"
|
|
49
|
+
}
|
|
50
|
+
except Exception as e:
|
|
51
|
+
logger.error(f"Execution failed: {e}")
|
|
52
|
+
return {
|
|
53
|
+
"command": command,
|
|
54
|
+
"return_code": -1,
|
|
55
|
+
"stdout": "",
|
|
56
|
+
"stderr": str(e),
|
|
57
|
+
"status": "error"
|
|
58
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import git
|
|
3
|
+
from app.utils.logger import logger, console
|
|
4
|
+
|
|
5
|
+
def clone_repo(repo_url: str, target_path: str) -> bool:
|
|
6
|
+
try:
|
|
7
|
+
if os.path.exists(target_path) and os.path.isdir(target_path):
|
|
8
|
+
if os.listdir(target_path):
|
|
9
|
+
logger.info(f"Target directory {target_path} already exists and is not empty.")
|
|
10
|
+
console.print(f"[yellow]Skipping clone, directory not empty: {target_path}[/yellow]")
|
|
11
|
+
return True
|
|
12
|
+
logger.info(f"Cloning {repo_url} into {target_path}...")
|
|
13
|
+
with console.status(f"[bold green]Cloning {repo_url}...[/bold green]"):
|
|
14
|
+
git.Repo.clone_from(repo_url, target_path)
|
|
15
|
+
logger.info("Clone completed successfully.")
|
|
16
|
+
console.print("[bold green]✓ Repository cloned successfully.[/bold green]")
|
|
17
|
+
return True
|
|
18
|
+
except Exception as e:
|
|
19
|
+
logger.error(f"Failed to clone repository: {str(e)}")
|
|
20
|
+
console.print(f"[bold red]✗ Failed to clone repository: {str(e)}[/bold red]")
|
|
21
|
+
return False
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
from rich.logging import RichHandler
|
|
4
|
+
|
|
5
|
+
console = Console()
|
|
6
|
+
|
|
7
|
+
def get_logger(name: str):
|
|
8
|
+
logging.basicConfig(
|
|
9
|
+
level="INFO",
|
|
10
|
+
format="%(message)s",
|
|
11
|
+
datefmt="[%X]",
|
|
12
|
+
handlers=[RichHandler(rich_tracebacks=True)]
|
|
13
|
+
)
|
|
14
|
+
return logging.getLogger(name)
|
|
15
|
+
|
|
16
|
+
logger = get_logger("locally796")
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: locally796
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AI-powered CLI tool to setup GitHub repositories locally
|
|
5
|
+
Author-email: Prathmesh Salunkhe <salunkheprathmesh0@gmail.com>
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: typer
|
|
10
|
+
Requires-Dist: rich
|
|
11
|
+
Requires-Dist: gitpython
|
|
12
|
+
Requires-Dist: langchain
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# Locally796
|
|
16
|
+
|
|
17
|
+
**Locally796** is an AI-powered CLI tool that automatically clones any GitHub repository and sets it up on your local machine — no manual configuration needed. Powered by a LangChain agent, it detects the project stack, installs dependencies, configures environment variables, and runs the project for you.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- 🤖 **AI-Driven Setup** — A LangChain agent acts as a senior DevOps engineer, analysing the repository, running the right install commands, and resolving errors automatically.
|
|
24
|
+
- 🔍 **Stack Detection** — Automatically identifies the project stack (Node.js, Python, Go, etc.) and applies the appropriate setup steps.
|
|
25
|
+
- ⚙️ **Dependency Installation** — Runs `npm install`, `pip install -r requirements.txt`, `go mod tidy`, and similar commands as needed.
|
|
26
|
+
- 🌿 **Environment Configuration** — Detects `.env.example` files and sets up environment variables for you.
|
|
27
|
+
- 🚀 **Project Launch** — Starts the project after setup and confirms it is running.
|
|
28
|
+
- 📦 **pip Package** — Distributed as a Python package, so you can install and use it anywhere with a single command.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Requirements
|
|
33
|
+
|
|
34
|
+
- Python 3.11 or higher
|
|
35
|
+
- A [Groq](https://console.groq.com/) API key to power the AI agent.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
You can install `locally796` globally using `pip`:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install locally796
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
*(Alternatively, to install from source: clone this repo and run `pip install -e .` inside the folder.)*
|
|
48
|
+
|
|
49
|
+
### Setup Your API Key
|
|
50
|
+
|
|
51
|
+
Before using `locally796` for the first time, you need to configure your Groq API key. The agent defaults to `llama-3.3-70b-versatile`, which is available for any standard Groq account.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
locally796 set-key <YOUR_GROQ_API_KEY>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
This securely saves your key in a global config file (`~/.locally796/.env`), meaning you can run the tool from anywhere on your system without having to set up `.env` files manually.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Usage
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
locally796 [GITHUB_REPO_URL] --path [CLONING_PATH]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Arguments
|
|
68
|
+
|
|
69
|
+
| Argument | Description |
|
|
70
|
+
|---|---|
|
|
71
|
+
| `GITHUB_REPO_URL` | The full URL of the GitHub repository to clone and set up |
|
|
72
|
+
| `--path` / `-p` | *(Optional)* Local directory path where the repo will be cloned. Defaults to a folder named after the repo in the current directory |
|
|
73
|
+
|
|
74
|
+
### Example
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
locally796 https://github.com/prathmesh796/pubsubs --path ./test-clone
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
This single command will autonomously:
|
|
81
|
+
1. Clone the repository into `./test-clone`
|
|
82
|
+
2. Detect the technology stack (e.g. Node.js, Python)
|
|
83
|
+
3. Launch the LangChain Agent to investigate the codebase.
|
|
84
|
+
4. Install all required dependencies (`npm install`, `pip install`, etc.)
|
|
85
|
+
5. Configure environment variables (if `.env.example` is present)
|
|
86
|
+
6. Start the local development server (e.g., `npm run dev`)
|
|
87
|
+
7. Automatically detect and fix any deployment errors if the server fails to start!
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## How It Works
|
|
92
|
+
|
|
93
|
+
1. **Clone** — The repo is cloned to the specified (or auto-generated) local path using GitPython.
|
|
94
|
+
2. **Detect** — The stack detector inspects the repository files to identify languages and frameworks.
|
|
95
|
+
3. **Agent Setup** — A LangChain agent equipped with shell, file-read, and file-write tools executes the full setup autonomously. If a command fails or crashes, the agent reads the error output, figures out the fix, and retries until success!
|
|
96
|
+
4. **Done** — The agent reports `SETUP_COMPLETE` and leaves the server running for you.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
MIT
|
|
103
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
app/__init__.py
|
|
5
|
+
app/main.py
|
|
6
|
+
app/cli/__init__.py
|
|
7
|
+
app/cli/app.py
|
|
8
|
+
app/config/__init__.py
|
|
9
|
+
app/config/env.py
|
|
10
|
+
app/config/settings.py
|
|
11
|
+
app/core/__init__.py
|
|
12
|
+
app/core/agent.py
|
|
13
|
+
app/core/llm.py
|
|
14
|
+
app/core/tools.py
|
|
15
|
+
app/services/__init__.py
|
|
16
|
+
app/services/detector.py
|
|
17
|
+
app/services/executor.py
|
|
18
|
+
app/services/repo.py
|
|
19
|
+
app/utils/__init__.py
|
|
20
|
+
app/utils/constants.py
|
|
21
|
+
app/utils/logger.py
|
|
22
|
+
locally796.egg-info/PKG-INFO
|
|
23
|
+
locally796.egg-info/SOURCES.txt
|
|
24
|
+
locally796.egg-info/dependency_links.txt
|
|
25
|
+
locally796.egg-info/entry_points.txt
|
|
26
|
+
locally796.egg-info/requires.txt
|
|
27
|
+
locally796.egg-info/top_level.txt
|
|
28
|
+
tests/test.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
app
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "locally796"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "AI-powered CLI tool to setup GitHub repositories locally"
|
|
5
|
+
authors = [
|
|
6
|
+
{ name="Prathmesh Salunkhe", email="salunkheprathmesh0@gmail.com" }
|
|
7
|
+
]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
|
|
11
|
+
dependencies = [
|
|
12
|
+
"typer",
|
|
13
|
+
"rich",
|
|
14
|
+
"gitpython",
|
|
15
|
+
"langchain",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.scripts]
|
|
19
|
+
locally796 = "app.main:app"
|
|
20
|
+
|
|
21
|
+
[build-system]
|
|
22
|
+
requires = ["setuptools>=61.0.0", "wheel"]
|
|
23
|
+
build-backend = "setuptools.build_meta"
|
|
24
|
+
|
|
25
|
+
[tool.setuptools.packages.find]
|
|
26
|
+
where = ["."]
|
|
27
|
+
include = ["app*"]
|