github-mcp-agent 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.
- github_mcp_agent-0.1.0/.claude/settings.json +5 -0
- github_mcp_agent-0.1.0/.claude/settings.local.json +13 -0
- github_mcp_agent-0.1.0/.env.example +3 -0
- github_mcp_agent-0.1.0/.gitignore +4 -0
- github_mcp_agent-0.1.0/PKG-INFO +14 -0
- github_mcp_agent-0.1.0/README.md +108 -0
- github_mcp_agent-0.1.0/github_mcp_agent/__init__.py +0 -0
- github_mcp_agent-0.1.0/github_mcp_agent/agent.py +103 -0
- github_mcp_agent-0.1.0/github_mcp_agent/cli.py +184 -0
- github_mcp_agent-0.1.0/github_mcp_agent/providers/__init__.py +32 -0
- github_mcp_agent-0.1.0/github_mcp_agent/providers/anthropic.py +28 -0
- github_mcp_agent-0.1.0/github_mcp_agent/providers/bedrock.py +112 -0
- github_mcp_agent-0.1.0/github_mcp_agent/providers/copilot.py +32 -0
- github_mcp_agent-0.1.0/github_mcp_agent/providers/gemini.py +28 -0
- github_mcp_agent-0.1.0/github_mcp_agent/providers/ollama.py +81 -0
- github_mcp_agent-0.1.0/github_mcp_agent/providers/openai.py +28 -0
- github_mcp_agent-0.1.0/github_mcp_agent/setup_wizard.py +134 -0
- github_mcp_agent-0.1.0/github_mcp_agent/system_prompt.txt +57 -0
- github_mcp_agent-0.1.0/github_mcp_agent/tools.py +214 -0
- github_mcp_agent-0.1.0/pyproject.toml +29 -0
- github_mcp_agent-0.1.0/system_prompt.txt +57 -0
- github_mcp_agent-0.1.0/uv.lock +2693 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Skill(update-config)",
|
|
5
|
+
"Skill(update-config:*)",
|
|
6
|
+
"WebFetch(domain:github.com)",
|
|
7
|
+
"WebFetch(domain:raw.githubusercontent.com)",
|
|
8
|
+
"WebFetch(domain:docs.github.com)",
|
|
9
|
+
"WebFetch(domain:api.github.com)",
|
|
10
|
+
"WebSearch"
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: github-mcp-agent
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A GitHub AI agent powered by AWS Bedrock and MCP
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: anthropic
|
|
7
|
+
Requires-Dist: boto3
|
|
8
|
+
Requires-Dist: click
|
|
9
|
+
Requires-Dist: litellm
|
|
10
|
+
Requires-Dist: mcp
|
|
11
|
+
Requires-Dist: python-dotenv
|
|
12
|
+
Requires-Dist: questionary
|
|
13
|
+
Requires-Dist: rich
|
|
14
|
+
Requires-Dist: strands-agents
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# GitHub MCP Agent
|
|
2
|
+
|
|
3
|
+
Talk to your GitHub repos, issues, and project boards in plain English from your terminal.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
You > list open issues in my raytracer repo
|
|
7
|
+
You > set priority of issue #42 to urgent
|
|
8
|
+
You > what's in progress on the project board?
|
|
9
|
+
You > which issues are assigned to maryam?
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
git clone https://github.com/OmarCodes022/GitHub-MCP-Agent
|
|
18
|
+
cd GitHub-MCP-Agent
|
|
19
|
+
uv tool install . # or: pip install .
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Setup
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
github-agent setup
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Interactive wizard:
|
|
31
|
+
|
|
32
|
+
1. GitHub token (validated immediately)
|
|
33
|
+
2. AI provider — choose one:
|
|
34
|
+
- **AWS Bedrock** — existing profile, access keys, or SSO
|
|
35
|
+
- **Anthropic API** — API key from console.anthropic.com
|
|
36
|
+
- **OpenAI** — API key from platform.openai.com
|
|
37
|
+
- **Google Gemini** — API key from aistudio.google.com
|
|
38
|
+
- **GitHub Copilot** — uses your GitHub token, requires a Copilot subscription
|
|
39
|
+
- **Local (Ollama)** — picks from your installed models, no API key needed
|
|
40
|
+
3. Model selection (scrollable menu)
|
|
41
|
+
4. Pulls the GitHub MCP Docker image
|
|
42
|
+
|
|
43
|
+
Config saved to `~/.config/github-mcp-agent/.env`.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
github-agent # start the agent
|
|
51
|
+
github-agent setup # full setup wizard
|
|
52
|
+
github-agent provider # switch AI provider and model
|
|
53
|
+
github-agent model # switch model within current provider
|
|
54
|
+
github-agent token # update GitHub token
|
|
55
|
+
github-agent config # edit config file in $EDITOR
|
|
56
|
+
github-agent prompt # customize the system prompt in $EDITOR
|
|
57
|
+
github-agent -v # start with verbose tool output
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Requirements
|
|
63
|
+
|
|
64
|
+
| Requirement | Notes |
|
|
65
|
+
|---|---|
|
|
66
|
+
| Python 3.10+ | |
|
|
67
|
+
| Docker | Must be running — used for the GitHub MCP server |
|
|
68
|
+
| GitHub token | Scopes: `repo`, `read:org`, `project` — [github.com/settings/tokens](https://github.com/settings/tokens) |
|
|
69
|
+
| Provider credentials | See setup wizard |
|
|
70
|
+
|
|
71
|
+
**For AWS Bedrock:** enable Claude model access in the [Bedrock console](https://console.aws.amazon.com/bedrock) under Model access.
|
|
72
|
+
|
|
73
|
+
**For Ollama:** install from [ollama.com](https://ollama.com), run `ollama serve`. Models with good tool-calling support: `qwen2.5`, `llama3.1`, `mistral`.
|
|
74
|
+
|
|
75
|
+
**For GitHub Copilot:** requires an active Copilot subscription (student pack, individual, or business).
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## How it works
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
You (terminal)
|
|
83
|
+
│
|
|
84
|
+
▼
|
|
85
|
+
github-agent (CLI)
|
|
86
|
+
│
|
|
87
|
+
├── AI model (Bedrock / Anthropic / OpenAI / Gemini / Copilot / Ollama)
|
|
88
|
+
│
|
|
89
|
+
└── GitHub MCP Server (Docker)
|
|
90
|
+
│
|
|
91
|
+
└── GitHub API
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Uses the [Strands Agents SDK](https://github.com/strands-agents/sdk-python) with the [GitHub MCP server](https://github.com/github/github-mcp-server).
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Troubleshooting
|
|
99
|
+
|
|
100
|
+
**`GITHUB_TOKEN` not set** — run `github-agent setup`
|
|
101
|
+
|
|
102
|
+
**Docker not running** — start Docker Desktop or `sudo systemctl start docker`
|
|
103
|
+
|
|
104
|
+
**Bedrock access denied** — check IAM permissions and model access in your region
|
|
105
|
+
|
|
106
|
+
**Ollama model not found** — run `ollama list` to confirm the model name, re-run `github-agent model`
|
|
107
|
+
|
|
108
|
+
**`No module named github_mcp_agent`** — reinstall with `uv tool install .` from the project directory
|
|
File without changes
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from contextlib import contextmanager
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from dotenv import load_dotenv
|
|
6
|
+
from strands import Agent
|
|
7
|
+
from strands.tools.mcp.mcp_client import MCPClient
|
|
8
|
+
from mcp import StdioServerParameters
|
|
9
|
+
from mcp.client.stdio import stdio_client
|
|
10
|
+
|
|
11
|
+
from github_mcp_agent.tools import detect_current_repo, local_tools
|
|
12
|
+
from github_mcp_agent import providers
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
_config_dir = Path.home() / ".config" / "github-mcp-agent"
|
|
16
|
+
load_dotenv(_config_dir / ".env")
|
|
17
|
+
load_dotenv()
|
|
18
|
+
|
|
19
|
+
MODEL_ID = os.getenv("MODEL_ID", "us.anthropic.claude-haiku-4-5-20251001-v1:0")
|
|
20
|
+
PROVIDER = os.getenv("PROVIDER", "bedrock")
|
|
21
|
+
VERBOSE = os.getenv("VERBOSE", "").lower() in ("1", "true", "yes")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _load_system_prompt() -> tuple[str, str | None]:
|
|
25
|
+
custom = _config_dir / "system_prompt.txt"
|
|
26
|
+
if custom.exists():
|
|
27
|
+
prompt = custom.read_text()
|
|
28
|
+
else:
|
|
29
|
+
from importlib.resources import files
|
|
30
|
+
prompt = files("github_mcp_agent").joinpath("system_prompt.txt").read_text()
|
|
31
|
+
|
|
32
|
+
repo = detect_current_repo()
|
|
33
|
+
if repo != "No GitHub remote detected":
|
|
34
|
+
prompt += f"\n\nThe user is currently working in the GitHub repository: {repo}. Default to this repository for all actions unless the user explicitly specifies another."
|
|
35
|
+
return prompt, repo
|
|
36
|
+
return prompt, None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _make_verbose_callback(provider: str):
|
|
40
|
+
import json as _json
|
|
41
|
+
is_ollama = provider == "ollama"
|
|
42
|
+
|
|
43
|
+
def callback(**kwargs):
|
|
44
|
+
if "current_tool_use" in kwargs:
|
|
45
|
+
tool = kwargs["current_tool_use"]
|
|
46
|
+
if tool.get("name"):
|
|
47
|
+
print(f"\n \033[2;36m> {tool['name']}\033[0m", flush=True)
|
|
48
|
+
if "data" in kwargs:
|
|
49
|
+
text = kwargs["data"]
|
|
50
|
+
if is_ollama:
|
|
51
|
+
try:
|
|
52
|
+
parsed = _json.loads(text)
|
|
53
|
+
if isinstance(parsed, dict) and "text" in parsed:
|
|
54
|
+
text = parsed["text"]
|
|
55
|
+
except Exception:
|
|
56
|
+
pass
|
|
57
|
+
print(text, end="", flush=True)
|
|
58
|
+
|
|
59
|
+
return callback
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@contextmanager
|
|
63
|
+
def create_agent(provider=None, model_id=None, verbose=False):
|
|
64
|
+
_provider = provider or PROVIDER
|
|
65
|
+
_model_id = model_id or MODEL_ID
|
|
66
|
+
|
|
67
|
+
token = os.environ.get("GITHUB_TOKEN")
|
|
68
|
+
if not token:
|
|
69
|
+
raise RuntimeError(
|
|
70
|
+
"GITHUB_TOKEN is not set. Run 'github-agent setup' to configure."
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
mcp_client = MCPClient(
|
|
74
|
+
lambda: stdio_client(
|
|
75
|
+
StdioServerParameters(
|
|
76
|
+
command="docker",
|
|
77
|
+
args=[
|
|
78
|
+
"run", "-i", "--rm",
|
|
79
|
+
"-e", "GITHUB_PERSONAL_ACCESS_TOKEN",
|
|
80
|
+
"ghcr.io/github/github-mcp-server",
|
|
81
|
+
"stdio",
|
|
82
|
+
"--toolsets", "all",
|
|
83
|
+
"--log-file", "/dev/null",
|
|
84
|
+
],
|
|
85
|
+
env={"GITHUB_PERSONAL_ACCESS_TOKEN": token},
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
model = providers.build_model(_provider, _model_id)
|
|
91
|
+
system_prompt, current_repo = _load_system_prompt()
|
|
92
|
+
|
|
93
|
+
with mcp_client:
|
|
94
|
+
mcp_tools = mcp_client.list_tools_sync()
|
|
95
|
+
agent_kwargs = dict(model=model, tools=mcp_tools + local_tools, system_prompt=system_prompt)
|
|
96
|
+
if verbose or VERBOSE:
|
|
97
|
+
agent_kwargs["callback_handler"] = _make_verbose_callback(_provider)
|
|
98
|
+
else:
|
|
99
|
+
cb = providers.make_callback(_provider)
|
|
100
|
+
if cb:
|
|
101
|
+
agent_kwargs["callback_handler"] = cb
|
|
102
|
+
agent = Agent(**agent_kwargs)
|
|
103
|
+
yield agent, current_repo, len(mcp_tools) + len(local_tools)
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import readline
|
|
3
|
+
import select
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
import questionary
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.rule import Rule
|
|
12
|
+
from rich.text import Text
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
CONFIG_DIR = Path.home() / ".config" / "github-mcp-agent"
|
|
17
|
+
|
|
18
|
+
_PROVIDER_CHOICES = [
|
|
19
|
+
"AWS Bedrock", "Anthropic API", "OpenAI", "Google Gemini", "GitHub Copilot", "Local (Ollama)"
|
|
20
|
+
]
|
|
21
|
+
_PROVIDER_KEY = {
|
|
22
|
+
"AWS Bedrock": "bedrock",
|
|
23
|
+
"Anthropic API": "anthropic",
|
|
24
|
+
"OpenAI": "openai",
|
|
25
|
+
"Google Gemini": "gemini",
|
|
26
|
+
"GitHub Copilot": "copilot",
|
|
27
|
+
"Local (Ollama)": "ollama",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _pick_model_for_provider(provider: str, _ask) -> tuple[str, str]:
|
|
32
|
+
import github_mcp_agent.providers as pkg
|
|
33
|
+
mod = getattr(pkg, provider)
|
|
34
|
+
console.print(f" [dim]Provider: {provider} (to switch, run: github-agent provider)[/dim]")
|
|
35
|
+
if provider == "ollama":
|
|
36
|
+
model_id = mod.pick_model(_ask)
|
|
37
|
+
else:
|
|
38
|
+
model_choices = [f"{name} ({desc})" for _, name, desc in mod.MODELS]
|
|
39
|
+
model_display = _ask(questionary.select, "Model:", choices=model_choices)
|
|
40
|
+
model_id = mod.MODELS[model_choices.index(model_display)][0]
|
|
41
|
+
return provider, model_id
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _run_agent(verbose: bool = False):
|
|
45
|
+
from github_mcp_agent.agent import MODEL_ID, PROVIDER, create_agent
|
|
46
|
+
try:
|
|
47
|
+
with create_agent(verbose=verbose) as (agent, current_repo, total_tools):
|
|
48
|
+
console.print()
|
|
49
|
+
console.print(Rule("[bold green]GitHub MCP Agent[/bold green]"))
|
|
50
|
+
repo_label = f"[bold]{current_repo}[/bold]" if current_repo else "[dim]none detected[/dim]"
|
|
51
|
+
console.print(f" [dim]Loaded [bold]{total_tools}[/bold] tools | Repo: {repo_label} | Provider: [bold]{PROVIDER}[/bold] | Model: [bold]{MODEL_ID}[/bold] | Type 'exit' to quit[/dim]")
|
|
52
|
+
console.print(Rule())
|
|
53
|
+
console.print()
|
|
54
|
+
|
|
55
|
+
while True:
|
|
56
|
+
try:
|
|
57
|
+
user_input = input("\033[1;36m You > \033[0m")
|
|
58
|
+
while select.select([sys.stdin], [], [], 0.05)[0]:
|
|
59
|
+
user_input += "\n" + sys.stdin.readline().rstrip("\n")
|
|
60
|
+
except (KeyboardInterrupt, EOFError):
|
|
61
|
+
break
|
|
62
|
+
|
|
63
|
+
if not user_input.strip():
|
|
64
|
+
continue
|
|
65
|
+
if user_input.strip().lower() in ["exit", "quit"]:
|
|
66
|
+
break
|
|
67
|
+
|
|
68
|
+
console.print()
|
|
69
|
+
console.print(Text(" Agent", style="bold magenta"))
|
|
70
|
+
console.print()
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
agent(user_input)
|
|
74
|
+
except Exception as e:
|
|
75
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
76
|
+
|
|
77
|
+
console.print()
|
|
78
|
+
|
|
79
|
+
console.print()
|
|
80
|
+
console.print(Rule("[dim]Session ended[/dim]"))
|
|
81
|
+
console.print()
|
|
82
|
+
|
|
83
|
+
except RuntimeError as e:
|
|
84
|
+
console.print(f"\n[bold red]{e}[/bold red]\n")
|
|
85
|
+
sys.exit(1)
|
|
86
|
+
except Exception as e:
|
|
87
|
+
console.print(f"\n[bold red]Failed to start:[/bold red] {e}\n")
|
|
88
|
+
sys.exit(1)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _open_config():
|
|
92
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
93
|
+
config_file = CONFIG_DIR / ".env"
|
|
94
|
+
if not config_file.exists():
|
|
95
|
+
config_file.write_text(
|
|
96
|
+
"GITHUB_TOKEN=\nAWS_PROFILE=default\nAWS_REGION=us-east-1\nMODEL_ID=us.anthropic.claude-haiku-4-5-20251001-v1:0\n"
|
|
97
|
+
)
|
|
98
|
+
editor = os.environ.get("EDITOR", "nano")
|
|
99
|
+
subprocess.run([editor, str(config_file)])
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _open_prompt():
|
|
103
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
104
|
+
prompt_file = CONFIG_DIR / "system_prompt.txt"
|
|
105
|
+
if not prompt_file.exists():
|
|
106
|
+
from importlib.resources import files
|
|
107
|
+
default = files("github_mcp_agent").joinpath("system_prompt.txt").read_text()
|
|
108
|
+
prompt_file.write_text(default)
|
|
109
|
+
console.print(f" [dim]Created {prompt_file} with default prompt - edit to customize.[/dim]\n")
|
|
110
|
+
editor = os.environ.get("EDITOR", "nano")
|
|
111
|
+
subprocess.run([editor, str(prompt_file)])
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@click.group(invoke_without_command=True, context_settings={"help_option_names": ["-h", "--help"]})
|
|
115
|
+
@click.option("--verbose", "-v", is_flag=True, default=False, help="Print tool calls as they happen")
|
|
116
|
+
@click.pass_context
|
|
117
|
+
def cli(ctx, verbose):
|
|
118
|
+
"""GitHub MCP Agent - talk to your repos in plain English."""
|
|
119
|
+
if ctx.invoked_subcommand is None:
|
|
120
|
+
_run_agent(verbose=verbose)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@cli.command()
|
|
124
|
+
def setup():
|
|
125
|
+
"""Run the interactive setup wizard."""
|
|
126
|
+
from github_mcp_agent.setup_wizard import run
|
|
127
|
+
run()
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@cli.command(name="provider")
|
|
131
|
+
def switch_provider():
|
|
132
|
+
"""Switch AI provider - pick credentials + model and save to config."""
|
|
133
|
+
from github_mcp_agent.setup_wizard import _ask, _write_config
|
|
134
|
+
from github_mcp_agent import providers as _providers
|
|
135
|
+
provider_display = _ask(questionary.select, "Provider:", choices=_PROVIDER_CHOICES)
|
|
136
|
+
provider_key = _PROVIDER_KEY[provider_display]
|
|
137
|
+
provider_values = _providers.setup(provider_key, _ask)
|
|
138
|
+
_write_config({"PROVIDER": provider_key, **provider_values})
|
|
139
|
+
console.print(" [green]Saved.[/green]")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@cli.command(name="model")
|
|
143
|
+
def switch_model():
|
|
144
|
+
"""Pick a model for the current provider and save to config."""
|
|
145
|
+
from github_mcp_agent.agent import PROVIDER
|
|
146
|
+
from github_mcp_agent.setup_wizard import _ask, _write_config
|
|
147
|
+
_, effective_model = _pick_model_for_provider(PROVIDER, _ask)
|
|
148
|
+
_write_config({"MODEL_ID": effective_model})
|
|
149
|
+
console.print(f" [green]Saved:[/green] model={effective_model}")
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@cli.command()
|
|
153
|
+
def token():
|
|
154
|
+
"""Update your GitHub Personal Access Token."""
|
|
155
|
+
from github_mcp_agent.setup_wizard import _ask, _validate_github_token, _write_config
|
|
156
|
+
new_token = _ask(questionary.password, "GitHub Personal Access Token:")
|
|
157
|
+
console.print(" Validating...", end=" ")
|
|
158
|
+
if _validate_github_token(new_token):
|
|
159
|
+
console.print("[green]valid[/green]")
|
|
160
|
+
_write_config({"GITHUB_TOKEN": new_token})
|
|
161
|
+
console.print(" [green]Saved.[/green]")
|
|
162
|
+
else:
|
|
163
|
+
console.print("[red]invalid - check the token and scopes[/red]")
|
|
164
|
+
sys.exit(1)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@cli.command()
|
|
168
|
+
def config():
|
|
169
|
+
"""Open the config file in $EDITOR."""
|
|
170
|
+
_open_config()
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@cli.command()
|
|
174
|
+
def prompt():
|
|
175
|
+
"""Open the system prompt file in $EDITOR."""
|
|
176
|
+
_open_prompt()
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def main():
|
|
180
|
+
cli()
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
if __name__ == "__main__":
|
|
184
|
+
main()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from . import anthropic, bedrock, copilot, gemini, ollama, openai
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
_REGISTRY = {
|
|
5
|
+
"bedrock": bedrock,
|
|
6
|
+
"anthropic": anthropic,
|
|
7
|
+
"openai": openai,
|
|
8
|
+
"gemini": gemini,
|
|
9
|
+
"copilot": copilot,
|
|
10
|
+
"ollama": ollama,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def build_model(provider: str, model_id: str):
|
|
15
|
+
mod = _REGISTRY.get(provider)
|
|
16
|
+
if mod is None:
|
|
17
|
+
raise RuntimeError(f"Unknown provider: {provider}")
|
|
18
|
+
return mod.build_model(model_id)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def make_callback(provider: str):
|
|
22
|
+
if provider == "ollama":
|
|
23
|
+
return ollama.make_callback()
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def setup(provider: str, _ask) -> dict:
|
|
28
|
+
mod = _REGISTRY.get(provider)
|
|
29
|
+
if mod is None:
|
|
30
|
+
raise RuntimeError(f"Unknown provider: {provider}")
|
|
31
|
+
return mod.setup(_ask)
|
|
32
|
+
pypi-AgEIcHlwaS5vcmcCJGU0ZDI1YWQ5LTEyOGMtNDUyZi1iNmZkLTEzOWU0MmRkNDIzOAACKlszLCJjZTAzNzUyYy00ZjNhLTQ3MTYtYjM2ZS02YmY0ZGNhZjkyMDciXQAABiD04xa6bIcHRFlnmMHzKPunpg81Z6OhNO_P102kWxtY9g
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import questionary
|
|
4
|
+
|
|
5
|
+
MODELS = [
|
|
6
|
+
("claude-haiku-4-5-20251001", "claude-haiku-4-5", "fastest"),
|
|
7
|
+
("claude-sonnet-4-6", "claude-sonnet-4-6", "balanced"),
|
|
8
|
+
("claude-opus-4-7", "claude-opus-4-7", "most capable"),
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def build_model(model_id: str):
|
|
13
|
+
from strands.models.anthropic import AnthropicModel
|
|
14
|
+
if not os.environ.get("ANTHROPIC_API_KEY"):
|
|
15
|
+
raise RuntimeError("ANTHROPIC_API_KEY is not set. Run 'github-agent setup' to configure.")
|
|
16
|
+
return AnthropicModel(model_id=model_id, max_tokens=8096)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def setup(_ask) -> dict:
|
|
20
|
+
from rich.console import Console
|
|
21
|
+
Console().print(" [dim]Get your key at: console.anthropic.com/settings/keys[/dim]")
|
|
22
|
+
key = _ask(questionary.password, "Anthropic API key (sk-ant-...):")
|
|
23
|
+
model_choices = [f"{name} ({desc})" for _, name, desc in MODELS]
|
|
24
|
+
model_display = _ask(questionary.select, "Model:", choices=model_choices)
|
|
25
|
+
return {
|
|
26
|
+
"ANTHROPIC_API_KEY": key,
|
|
27
|
+
"MODEL_ID": MODELS[model_choices.index(model_display)][0],
|
|
28
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import questionary
|
|
6
|
+
|
|
7
|
+
REGIONS = [
|
|
8
|
+
("us-east-1", "N. Virginia - recommended"),
|
|
9
|
+
("us-west-2", "Oregon"),
|
|
10
|
+
("eu-west-1", "Ireland"),
|
|
11
|
+
("eu-central-1", "Frankfurt"),
|
|
12
|
+
("eu-west-3", "Paris"),
|
|
13
|
+
("ap-northeast-1", "Tokyo"),
|
|
14
|
+
("ap-southeast-1", "Singapore"),
|
|
15
|
+
("ap-southeast-2", "Sydney"),
|
|
16
|
+
("ca-central-1", "Canada"),
|
|
17
|
+
("sa-east-1", "Sao Paulo"),
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
MODELS = [
|
|
21
|
+
("us.anthropic.claude-haiku-4-5-20251001-v1:0", "claude-haiku-4-5", "fastest, cheapest"),
|
|
22
|
+
("us.anthropic.claude-sonnet-4-6", "claude-sonnet-4-6", "balanced"),
|
|
23
|
+
("us.anthropic.claude-opus-4-7", "claude-opus-4-7", "most capable"),
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def build_model(model_id: str):
|
|
28
|
+
from strands.models import BedrockModel
|
|
29
|
+
return BedrockModel(model_id=model_id, region_name=os.getenv("AWS_REGION", "us-east-1"))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _list_aws_profiles() -> list[str]:
|
|
33
|
+
profiles = []
|
|
34
|
+
for f in [Path.home() / ".aws" / "credentials", Path.home() / ".aws" / "config"]:
|
|
35
|
+
if f.exists():
|
|
36
|
+
for line in f.read_text().splitlines():
|
|
37
|
+
if line.startswith("[") and line.endswith("]"):
|
|
38
|
+
name = line[1:-1].replace("profile ", "")
|
|
39
|
+
if name not in profiles:
|
|
40
|
+
profiles.append(name)
|
|
41
|
+
return profiles
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _validate_aws_profile(profile: str) -> bool:
|
|
45
|
+
env = os.environ.copy()
|
|
46
|
+
env["AWS_PROFILE"] = profile
|
|
47
|
+
return subprocess.run(["aws", "sts", "get-caller-identity"], capture_output=True, env=env).returncode == 0
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def setup(_ask) -> dict:
|
|
51
|
+
values = {}
|
|
52
|
+
|
|
53
|
+
while True:
|
|
54
|
+
cred_method = _ask(
|
|
55
|
+
questionary.select,
|
|
56
|
+
"AWS credentials:",
|
|
57
|
+
choices=["Use existing profile", "Enter access keys directly", "AWS SSO / browser login"],
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
if cred_method == "Use existing profile":
|
|
61
|
+
profiles = _list_aws_profiles()
|
|
62
|
+
choices = profiles + ["other (type manually)"] if profiles else ["other (type manually)"]
|
|
63
|
+
choice = _ask(questionary.select, "AWS profile:", choices=choices)
|
|
64
|
+
if choice == "other (type manually)":
|
|
65
|
+
profile = _ask(questionary.text, "Profile name:", default="default")
|
|
66
|
+
else:
|
|
67
|
+
profile = choice
|
|
68
|
+
from rich.console import Console
|
|
69
|
+
Console().print(" Validating...", end=" ")
|
|
70
|
+
if _validate_aws_profile(profile):
|
|
71
|
+
Console().print("[green]valid[/green]")
|
|
72
|
+
values["AWS_PROFILE"] = profile
|
|
73
|
+
break
|
|
74
|
+
else:
|
|
75
|
+
Console().print("[red]invalid - check your AWS credentials[/red]")
|
|
76
|
+
|
|
77
|
+
elif cred_method == "Enter access keys directly":
|
|
78
|
+
values["AWS_ACCESS_KEY_ID"] = _ask(questionary.text, "AWS Access Key ID:")
|
|
79
|
+
values["AWS_SECRET_ACCESS_KEY"] = _ask(questionary.password, "AWS Secret Access Key:")
|
|
80
|
+
session_token = _ask(questionary.text, "AWS Session Token (leave blank if none):")
|
|
81
|
+
if session_token:
|
|
82
|
+
values["AWS_SESSION_TOKEN"] = session_token
|
|
83
|
+
break
|
|
84
|
+
|
|
85
|
+
elif cred_method == "AWS SSO / browser login":
|
|
86
|
+
profiles = _list_aws_profiles()
|
|
87
|
+
choices = profiles + ["other (type manually)"] if profiles else ["other (type manually)"]
|
|
88
|
+
choice = _ask(questionary.select, "SSO profile to use:", choices=choices)
|
|
89
|
+
if choice == "other (type manually)":
|
|
90
|
+
profile = _ask(questionary.text, "Profile name:")
|
|
91
|
+
else:
|
|
92
|
+
profile = choice
|
|
93
|
+
from rich.console import Console
|
|
94
|
+
console = Console()
|
|
95
|
+
console.print(f"\n Running [bold]aws sso login --profile {profile}[/bold]")
|
|
96
|
+
result = subprocess.run(["aws", "sso", "login", "--profile", profile])
|
|
97
|
+
if result.returncode == 0:
|
|
98
|
+
values["AWS_PROFILE"] = profile
|
|
99
|
+
break
|
|
100
|
+
console.print("[red]SSO login failed - profile may not be configured for SSO.[/red]")
|
|
101
|
+
console.print("[dim] Run: aws configure sso --profile <name>[/dim]")
|
|
102
|
+
console.print("[dim] Or pick a different credential method below.[/dim]\n")
|
|
103
|
+
|
|
104
|
+
region_choices = [f"{r} ({label})" for r, label in REGIONS]
|
|
105
|
+
region_display = _ask(questionary.select, "AWS Region:", choices=region_choices)
|
|
106
|
+
values["AWS_REGION"] = region_display.split()[0]
|
|
107
|
+
|
|
108
|
+
model_choices = [f"{name} ({desc}) -> {mid}" for mid, name, desc in MODELS]
|
|
109
|
+
model_display = _ask(questionary.select, "Model:", choices=model_choices)
|
|
110
|
+
values["MODEL_ID"] = MODELS[model_choices.index(model_display)][0]
|
|
111
|
+
|
|
112
|
+
return values
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import questionary
|
|
4
|
+
|
|
5
|
+
MODELS = [
|
|
6
|
+
("gpt-4o", "gpt-4o", "flagship"),
|
|
7
|
+
("gpt-4o-mini", "gpt-4o-mini", "fast, cheap"),
|
|
8
|
+
("claude-sonnet-4-5", "claude-sonnet-4-5", "Anthropic via Copilot"),
|
|
9
|
+
("o3-mini", "o3-mini", "reasoning"),
|
|
10
|
+
("gemini-1.5-pro", "gemini-1.5-pro", "Google via Copilot"),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def build_model(model_id: str):
|
|
15
|
+
from strands.models.litellm import LiteLLMModel
|
|
16
|
+
token = os.environ.get("GITHUB_TOKEN")
|
|
17
|
+
if not token:
|
|
18
|
+
raise RuntimeError("GITHUB_TOKEN is not set. Run 'github-agent setup' to configure.")
|
|
19
|
+
return LiteLLMModel(
|
|
20
|
+
model_id=f"openai/{model_id}",
|
|
21
|
+
params={"api_base": "https://api.githubcopilot.com", "api_key": token},
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def setup(_ask) -> dict:
|
|
26
|
+
from rich.console import Console
|
|
27
|
+
console = Console()
|
|
28
|
+
console.print(" [dim]Uses your GitHub token - no extra API key needed.[/dim]")
|
|
29
|
+
console.print(" [dim]Requires an active Copilot subscription (student pack, individual, or business).[/dim]")
|
|
30
|
+
model_choices = [f"{name} ({desc})" for _, name, desc in MODELS]
|
|
31
|
+
model_display = _ask(questionary.select, "Model:", choices=model_choices)
|
|
32
|
+
return {"MODEL_ID": MODELS[model_choices.index(model_display)][0]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import questionary
|
|
4
|
+
|
|
5
|
+
MODELS = [
|
|
6
|
+
("gemini-2.0-flash", "gemini-2.0-flash", "fast, recommended"),
|
|
7
|
+
("gemini-1.5-pro", "gemini-1.5-pro", "most capable"),
|
|
8
|
+
("gemini-1.5-flash", "gemini-1.5-flash", "fast"),
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def build_model(model_id: str):
|
|
13
|
+
from strands.models.litellm import LiteLLMModel
|
|
14
|
+
if not os.environ.get("GEMINI_API_KEY"):
|
|
15
|
+
raise RuntimeError("GEMINI_API_KEY is not set. Run 'github-agent setup' to configure.")
|
|
16
|
+
return LiteLLMModel(model_id=f"gemini/{model_id}")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def setup(_ask) -> dict:
|
|
20
|
+
from rich.console import Console
|
|
21
|
+
Console().print(" [dim]Get your key at: aistudio.google.com/apikey[/dim]")
|
|
22
|
+
key = _ask(questionary.password, "Google AI Studio API key:")
|
|
23
|
+
model_choices = [f"{name} ({desc})" for _, name, desc in MODELS]
|
|
24
|
+
model_display = _ask(questionary.select, "Model:", choices=model_choices)
|
|
25
|
+
return {
|
|
26
|
+
"GEMINI_API_KEY": key,
|
|
27
|
+
"MODEL_ID": MODELS[model_choices.index(model_display)][0],
|
|
28
|
+
}
|