polycoding 0.1.0__py3-none-any.whl
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.
- cli/__init__.py +53 -0
- cli/db.py +67 -0
- cli/flow.py +187 -0
- cli/main.py +44 -0
- cli/project.py +166 -0
- cli/server.py +127 -0
- cli/utils.py +70 -0
- cli/worker.py +124 -0
- github_app/__init__.py +13 -0
- github_app/app.py +224 -0
- github_app/auth.py +137 -0
- github_app/config.py +38 -0
- github_app/installation_manager.py +194 -0
- github_app/label_mapper.py +112 -0
- github_app/models.py +112 -0
- github_app/webhook_handler.py +217 -0
- persistence/__init__.py +5 -0
- persistence/config.py +12 -0
- persistence/postgres.py +346 -0
- persistence/registry.py +111 -0
- persistence/tasks.py +178 -0
- polycoding-0.1.0.dist-info/METADATA +225 -0
- polycoding-0.1.0.dist-info/RECORD +41 -0
- polycoding-0.1.0.dist-info/WHEEL +4 -0
- polycoding-0.1.0.dist-info/entry_points.txt +3 -0
- polycoding-0.1.0.dist-info/licenses/LICENSE +20 -0
- project_manager/README.md +668 -0
- project_manager/__init__.py +29 -0
- project_manager/base.py +202 -0
- project_manager/config.py +36 -0
- project_manager/conversation/__init__.py +19 -0
- project_manager/conversation/flow.py +233 -0
- project_manager/conversation/types.py +64 -0
- project_manager/flow_runner.py +160 -0
- project_manager/git_utils.py +30 -0
- project_manager/github.py +367 -0
- project_manager/github_conversation.py +144 -0
- project_manager/github_projects_client.py +329 -0
- project_manager/hooks.py +377 -0
- project_manager/module.py +66 -0
- project_manager/types.py +79 -0
cli/__init__.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Central CLI tool for Polycode.
|
|
2
|
+
|
|
3
|
+
Entry point: `polycode` command.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
polycode server webhook # Start webhook server
|
|
7
|
+
polycode server socketio # Start Socket.IO server
|
|
8
|
+
polycode server flower # Start Flower monitoring
|
|
9
|
+
polycode worker start # Start Celery worker
|
|
10
|
+
polycode flow run ralph # Run a flow manually
|
|
11
|
+
polycode project sync # Sync issues to project
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from rich.panel import Panel
|
|
16
|
+
from rich.text import Text
|
|
17
|
+
|
|
18
|
+
console = Console()
|
|
19
|
+
|
|
20
|
+
__version__ = "0.1.0"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def print_banner():
|
|
24
|
+
"""Print pretty CLI banner."""
|
|
25
|
+
banner = Text.assemble(
|
|
26
|
+
("đ ", "bold cyan"),
|
|
27
|
+
("polycode", "bold white"),
|
|
28
|
+
(f" v{__version__}", "dim"),
|
|
29
|
+
)
|
|
30
|
+
console.print(Panel(banner, border_style="cyan"))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def print_error(message: str) -> None:
|
|
34
|
+
"""Print error message in red."""
|
|
35
|
+
console.print(f"[bold red]â {message}[/]")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def print_success(message: str) -> None:
|
|
39
|
+
"""Print success message in green."""
|
|
40
|
+
console.print(f"[bold green]â {message}[/]")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def print_warning(message: str) -> None:
|
|
44
|
+
"""Print warning message in yellow."""
|
|
45
|
+
console.print(f"[bold yellow]â ī¸ {message}[/]")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def print_info(message: str) -> None:
|
|
49
|
+
"""Print info message in blue."""
|
|
50
|
+
console.print(f"[bold blue]âšī¸ {message}[/]")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
__all__ = ["print_error", "print_success", "print_warning", "print_info", "console"]
|
cli/db.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Database management commands for Polycode CLI."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from sqlalchemy import create_engine
|
|
7
|
+
|
|
8
|
+
from cli import print_error, print_info, print_success, print_warning
|
|
9
|
+
from cli.utils import get_logger
|
|
10
|
+
from persistence.postgres import Base
|
|
11
|
+
|
|
12
|
+
log = get_logger(__name__)
|
|
13
|
+
db_app = typer.Typer(help="Database management commands")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@db_app.command("init")
|
|
17
|
+
def db_init(
|
|
18
|
+
drop: bool = typer.Option(False, "--drop", help="Drop existing tables first"),
|
|
19
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose logging"),
|
|
20
|
+
) -> None:
|
|
21
|
+
"""Initialize database tables.
|
|
22
|
+
|
|
23
|
+
Creates all tables defined in the persistence layer.
|
|
24
|
+
"""
|
|
25
|
+
if verbose:
|
|
26
|
+
import cli.utils
|
|
27
|
+
|
|
28
|
+
cli.utils.setup_logging("DEBUG")
|
|
29
|
+
|
|
30
|
+
print_info("đ§ Initializing database...")
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
from project_manager.config import settings
|
|
34
|
+
|
|
35
|
+
connection_string = settings.DATABASE_URL
|
|
36
|
+
engine = create_engine(connection_string)
|
|
37
|
+
|
|
38
|
+
if drop:
|
|
39
|
+
print_info("đ Dropping existing tables...")
|
|
40
|
+
Base.metadata.drop_all(engine)
|
|
41
|
+
print_success("Existing tables dropped")
|
|
42
|
+
|
|
43
|
+
print_info("đ Creating tables...")
|
|
44
|
+
Base.metadata.create_all(engine)
|
|
45
|
+
print_success("Database initialized successfully")
|
|
46
|
+
|
|
47
|
+
except Exception as e:
|
|
48
|
+
print_error(f"Database initialization failed: {e}")
|
|
49
|
+
log.exception(f"Database init failed: {e}")
|
|
50
|
+
sys.exit(1)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@db_app.command("migrate")
|
|
54
|
+
def db_migrate(
|
|
55
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose logging"),
|
|
56
|
+
) -> None:
|
|
57
|
+
"""Run database migrations.
|
|
58
|
+
|
|
59
|
+
Note: Migration support not yet implemented.
|
|
60
|
+
"""
|
|
61
|
+
if verbose:
|
|
62
|
+
import cli.utils
|
|
63
|
+
|
|
64
|
+
cli.utils.setup_logging("DEBUG")
|
|
65
|
+
|
|
66
|
+
print_warning("â ī¸ Migration support not yet implemented")
|
|
67
|
+
print_info("Use 'polycode db init --drop' to recreate tables")
|
cli/flow.py
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""Flow execution commands for Polycode CLI."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from alive_progress import alive_bar
|
|
9
|
+
from uuid import NAMESPACE_DNS, uuid5
|
|
10
|
+
from rich import box
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.table import Table
|
|
13
|
+
|
|
14
|
+
from bootstrap import init_plugins
|
|
15
|
+
from cli import print_error, print_info, print_success
|
|
16
|
+
from cli.utils import get_logger
|
|
17
|
+
from flows.base import KickoffIssue, KickoffRepo
|
|
18
|
+
from project_manager.github import GitHubProjectManager
|
|
19
|
+
from project_manager.types import ProjectConfig, StatusMapping
|
|
20
|
+
|
|
21
|
+
log = get_logger(__name__)
|
|
22
|
+
console = Console()
|
|
23
|
+
flow_app = typer.Typer(help="Flow execution commands")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@flow_app.command("list")
|
|
27
|
+
def flow_list(
|
|
28
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose logging"),
|
|
29
|
+
) -> None:
|
|
30
|
+
"""List all available flows."""
|
|
31
|
+
if verbose:
|
|
32
|
+
import cli.utils
|
|
33
|
+
|
|
34
|
+
cli.utils.setup_logging("DEBUG")
|
|
35
|
+
|
|
36
|
+
print_info("đ Available flows:")
|
|
37
|
+
|
|
38
|
+
flows: dict[str, str] = {
|
|
39
|
+
"ralph": "Ralph - Feature development orchestrator with per-story commits",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
table = Table(title="Available Flows", box=box.ROUNDED)
|
|
43
|
+
table.add_column("Name", style="cyan", header_style="bold")
|
|
44
|
+
table.add_column("Description", style="white")
|
|
45
|
+
|
|
46
|
+
for name, description in flows.items():
|
|
47
|
+
table.add_row(name, description)
|
|
48
|
+
|
|
49
|
+
console.print(table)
|
|
50
|
+
print_success(f"Found {len(flows)} flow(s)")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@flow_app.command("run")
|
|
54
|
+
def flow_run(
|
|
55
|
+
flow_name: str = typer.Argument(..., help="Flow name to execute"),
|
|
56
|
+
repo_owner: str = typer.Option(None, "--repo-owner", "-o", help="Repository owner (required)"),
|
|
57
|
+
repo_name: str = typer.Option(None, "--repo-name", "-r", help="Repository name (required)"),
|
|
58
|
+
issue_number: int = typer.Option(None, "--issue-number", "-i", help="Issue number to process (required)"),
|
|
59
|
+
project_id: str | None = typer.Option(None, "--project-id", "-p", help="GitHub project ID/number"),
|
|
60
|
+
provider: str = typer.Option("github", "--provider", help="Provider type (default: github)"),
|
|
61
|
+
token: str | None = typer.Option(None, "--token", "-t", help="GitHub token (or use GITHUB_TOKEN env var)"),
|
|
62
|
+
status_mapping: str | None = typer.Option(None, "--status-mapping", help="Custom status mapping as JSON"),
|
|
63
|
+
plugins: list[str] = typer.Option([], "--plugin", help="Load additional plugins (repeatable)"),
|
|
64
|
+
extra: str | None = typer.Option(None, "--extra", help="Extra configuration as JSON (for communications, etc.)"),
|
|
65
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose logging"),
|
|
66
|
+
) -> None:
|
|
67
|
+
"""Run a flow manually against a repository.
|
|
68
|
+
|
|
69
|
+
Requires full project configuration including provider, repository details,
|
|
70
|
+
and optional extras for custom behavior.
|
|
71
|
+
"""
|
|
72
|
+
if verbose:
|
|
73
|
+
import cli.utils
|
|
74
|
+
|
|
75
|
+
cli.utils.setup_logging("DEBUG")
|
|
76
|
+
|
|
77
|
+
if not repo_owner:
|
|
78
|
+
print_error("--repo-owner is required when specifying repo configuration")
|
|
79
|
+
sys.exit(1)
|
|
80
|
+
|
|
81
|
+
if not repo_name:
|
|
82
|
+
print_error("--repo-name is required when specifying repo configuration")
|
|
83
|
+
sys.exit(1)
|
|
84
|
+
|
|
85
|
+
if not issue_number:
|
|
86
|
+
print_error("--issue-number is required")
|
|
87
|
+
sys.exit(1)
|
|
88
|
+
|
|
89
|
+
print_info(f"đ Running flow '{flow_name}'")
|
|
90
|
+
print_info(f" Repository: {repo_owner}/{repo_name}")
|
|
91
|
+
print_info(f" Issue: #{issue_number}")
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
init_plugins()
|
|
95
|
+
|
|
96
|
+
if status_mapping:
|
|
97
|
+
try:
|
|
98
|
+
mapping_data = json.loads(status_mapping)
|
|
99
|
+
status_map = StatusMapping.from_dict(mapping_data)
|
|
100
|
+
except json.JSONDecodeError as e:
|
|
101
|
+
print_error(f"Invalid status mapping JSON: {e}")
|
|
102
|
+
sys.exit(1)
|
|
103
|
+
else:
|
|
104
|
+
status_map = StatusMapping()
|
|
105
|
+
|
|
106
|
+
extra_data: dict[str, Any] = {}
|
|
107
|
+
if extra:
|
|
108
|
+
try:
|
|
109
|
+
extra_data = json.loads(extra)
|
|
110
|
+
except json.JSONDecodeError as e:
|
|
111
|
+
print_error(f"Invalid extra JSON: {e}")
|
|
112
|
+
sys.exit(1)
|
|
113
|
+
|
|
114
|
+
if plugins:
|
|
115
|
+
print_info(f" Plugins: {', '.join(plugins)}")
|
|
116
|
+
|
|
117
|
+
config = ProjectConfig(
|
|
118
|
+
provider=provider,
|
|
119
|
+
repo_owner=repo_owner,
|
|
120
|
+
repo_name=repo_name,
|
|
121
|
+
project_identifier=project_id,
|
|
122
|
+
token=token,
|
|
123
|
+
status_mapping=status_map,
|
|
124
|
+
extra=extra_data,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
manager = GitHubProjectManager(config)
|
|
128
|
+
issue = manager.get_issue(issue_number)
|
|
129
|
+
|
|
130
|
+
if not issue:
|
|
131
|
+
print_error(f"Issue #{issue_number} not found")
|
|
132
|
+
sys.exit(1)
|
|
133
|
+
|
|
134
|
+
print_info(f" Title: {issue.title}")
|
|
135
|
+
|
|
136
|
+
from bootstrap import bootstrap
|
|
137
|
+
|
|
138
|
+
bootstrap(config={"modules": {p: {} for p in plugins}})
|
|
139
|
+
|
|
140
|
+
flow_identifier = f"{manager.config.repo_owner}/{manager.config.repo_name}/{issue_number}"
|
|
141
|
+
|
|
142
|
+
comments = manager.get_comments(issue_number)
|
|
143
|
+
|
|
144
|
+
kickoff_issue = KickoffIssue(
|
|
145
|
+
id=issue_number,
|
|
146
|
+
flow_id=uuid5(NAMESPACE_DNS, flow_identifier),
|
|
147
|
+
title=issue.title,
|
|
148
|
+
body=issue.body or "",
|
|
149
|
+
comments=[
|
|
150
|
+
{
|
|
151
|
+
"id": c.id,
|
|
152
|
+
"user": c.user.login if c.user else None,
|
|
153
|
+
"body": c.body,
|
|
154
|
+
"created_at": c.created_at.isoformat() if c.created_at else None,
|
|
155
|
+
}
|
|
156
|
+
for c in comments
|
|
157
|
+
],
|
|
158
|
+
memory_prefix=f"{manager.config.repo_owner}/{manager.config.repo_name}",
|
|
159
|
+
repository=KickoffRepo(
|
|
160
|
+
owner=manager.config.repo_owner,
|
|
161
|
+
repository=manager.config.repo_name,
|
|
162
|
+
),
|
|
163
|
+
project_config=config,
|
|
164
|
+
)
|
|
165
|
+
with alive_bar(
|
|
166
|
+
0,
|
|
167
|
+
title=f"Running {flow_name}",
|
|
168
|
+
spinner="squares",
|
|
169
|
+
force_tty=True,
|
|
170
|
+
enrich_print=False,
|
|
171
|
+
enrich_offset=0,
|
|
172
|
+
monitor=False,
|
|
173
|
+
) as bar:
|
|
174
|
+
bar.text = f"Processing issue #{issue_number}: {issue.title}"
|
|
175
|
+
|
|
176
|
+
from flows.ralph import ralph_kickoff
|
|
177
|
+
|
|
178
|
+
ralph_kickoff(kickoff_issue)
|
|
179
|
+
|
|
180
|
+
bar()
|
|
181
|
+
|
|
182
|
+
print_success(f"Flow '{flow_name}' completed for issue #{issue_number}")
|
|
183
|
+
|
|
184
|
+
except Exception as e:
|
|
185
|
+
print_error(f"Flow execution failed: {e}")
|
|
186
|
+
log.exception(f"Flow execution failed: {e}")
|
|
187
|
+
sys.exit(1)
|
cli/main.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Main CLI entry point using Typer."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from . import console, db, flow, print_banner, project, server, worker
|
|
6
|
+
from .utils import setup_logging
|
|
7
|
+
|
|
8
|
+
app = typer.Typer(
|
|
9
|
+
name="polycode",
|
|
10
|
+
help="đ Polycode: Multi-agent software development automation",
|
|
11
|
+
add_completion=False,
|
|
12
|
+
no_args_is_help=True,
|
|
13
|
+
pretty_exceptions_enable=False,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
app.add_typer(server.server_app, name="server", help="Server management commands")
|
|
17
|
+
app.add_typer(worker.worker_app, name="worker", help="Celery worker management commands")
|
|
18
|
+
app.add_typer(flow.flow_app, name="flow", help="Flow execution commands")
|
|
19
|
+
app.add_typer(project.project_app, name="project", help="GitHub project management commands")
|
|
20
|
+
app.add_typer(db.db_app, name="db", help="Database management commands")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@app.callback()
|
|
24
|
+
def main(
|
|
25
|
+
log_level: str = typer.Option(
|
|
26
|
+
"INFO",
|
|
27
|
+
"--log-level",
|
|
28
|
+
"-l",
|
|
29
|
+
help="Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)",
|
|
30
|
+
),
|
|
31
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose (DEBUG) logging"),
|
|
32
|
+
) -> None:
|
|
33
|
+
"""Polycode CLI - Multi-agent software development automation."""
|
|
34
|
+
level = "DEBUG" if verbose else log_level
|
|
35
|
+
setup_logging(level)
|
|
36
|
+
|
|
37
|
+
if verbose or level.upper() == "DEBUG":
|
|
38
|
+
console.print("[dim]đ Verbose mode enabled[/]")
|
|
39
|
+
|
|
40
|
+
print_banner()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
if __name__ == "__main__":
|
|
44
|
+
app()
|
cli/project.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Project management commands for Polycode CLI."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from rich import box
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
from cli import print_error, print_info, print_success
|
|
11
|
+
from cli.utils import get_logger
|
|
12
|
+
|
|
13
|
+
log = get_logger(__name__)
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
project_app = typer.Typer(help="GitHub project management commands")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def create_manager_from_env():
|
|
20
|
+
"""Create project manager from environment variables.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Configured project manager
|
|
24
|
+
"""
|
|
25
|
+
import os
|
|
26
|
+
|
|
27
|
+
from project_manager.config import settings
|
|
28
|
+
from project_manager.types import ProjectConfig, StatusMapping
|
|
29
|
+
|
|
30
|
+
provider = settings.PROJECT_PROVIDER or "github"
|
|
31
|
+
repo_owner = settings.REPO_OWNER or os.getenv("REPO_OWNER")
|
|
32
|
+
repo_name = settings.REPO_NAME or os.getenv("REPO_NAME")
|
|
33
|
+
project_identifier = settings.PROJECT_IDENTIFIER or os.getenv("PROJECT_IDENTIFIER")
|
|
34
|
+
|
|
35
|
+
status_mapping = StatusMapping(
|
|
36
|
+
todo=os.getenv("STATUS_TODO", "Todo"),
|
|
37
|
+
ready=os.getenv("STATUS_READY", "Ready"),
|
|
38
|
+
in_progress=os.getenv("STATUS_IN_PROGRESS", "In progress"),
|
|
39
|
+
reviewing=os.getenv("STATUS_REVIEWING", "In review"),
|
|
40
|
+
done=os.getenv("STATUS_DONE", "Done"),
|
|
41
|
+
blocked=os.getenv("STATUS_BLOCKED", "Blocked"),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
config = ProjectConfig(
|
|
45
|
+
provider=provider,
|
|
46
|
+
repo_owner=repo_owner or "",
|
|
47
|
+
repo_name=repo_name or "",
|
|
48
|
+
project_identifier=project_identifier,
|
|
49
|
+
status_mapping=status_mapping,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
if provider == "github":
|
|
53
|
+
from project_manager.github import GitHubProjectManager
|
|
54
|
+
|
|
55
|
+
return GitHubProjectManager(config)
|
|
56
|
+
else:
|
|
57
|
+
raise ValueError(f"Unsupported provider: {provider}")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@project_app.command("sync")
|
|
61
|
+
def project_sync(
|
|
62
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose logging"),
|
|
63
|
+
) -> None:
|
|
64
|
+
"""Sync all open issues to project."""
|
|
65
|
+
if verbose:
|
|
66
|
+
import cli.utils
|
|
67
|
+
|
|
68
|
+
cli.utils.setup_logging("DEBUG")
|
|
69
|
+
|
|
70
|
+
print_info("đ Syncing issues to project...")
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
manager = create_manager_from_env()
|
|
74
|
+
added = manager.sync_issues_to_project()
|
|
75
|
+
|
|
76
|
+
if added > 0:
|
|
77
|
+
print_success(f"Added {added} issues to project")
|
|
78
|
+
else:
|
|
79
|
+
print_info("All issues already in project")
|
|
80
|
+
|
|
81
|
+
except Exception as e:
|
|
82
|
+
print_error(f"Sync failed: {e}")
|
|
83
|
+
log.exception(f"Sync failed: {e}")
|
|
84
|
+
sys.exit(1)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@project_app.command("list")
|
|
88
|
+
def project_list(
|
|
89
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose logging"),
|
|
90
|
+
) -> None:
|
|
91
|
+
"""List all project items."""
|
|
92
|
+
if verbose:
|
|
93
|
+
import cli.utils
|
|
94
|
+
|
|
95
|
+
cli.utils.setup_logging("DEBUG")
|
|
96
|
+
|
|
97
|
+
print_info("đ Listing project items...")
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
manager = create_manager_from_env()
|
|
101
|
+
items = manager.get_project_items()
|
|
102
|
+
|
|
103
|
+
table = Table(title="Project Items", box=box.ROUNDED)
|
|
104
|
+
table.add_column("#", style="cyan", header_style="bold")
|
|
105
|
+
table.add_column("Status", style="green")
|
|
106
|
+
table.add_column("Title", style="white")
|
|
107
|
+
|
|
108
|
+
for item in items:
|
|
109
|
+
status = item.status or "No status"
|
|
110
|
+
table.add_row(f"#{item.issue_number}", f"[{status}]", item.title)
|
|
111
|
+
|
|
112
|
+
console.print(table)
|
|
113
|
+
print_success(f"Found {len(items)} item(s)")
|
|
114
|
+
|
|
115
|
+
except Exception as e:
|
|
116
|
+
print_error(f"List failed: {e}")
|
|
117
|
+
log.exception(f"List failed: {e}")
|
|
118
|
+
sys.exit(1)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@project_app.command("status")
|
|
122
|
+
def project_status(
|
|
123
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose logging"),
|
|
124
|
+
) -> None:
|
|
125
|
+
"""Show current flow status from GitHub Project."""
|
|
126
|
+
if verbose:
|
|
127
|
+
import cli.utils
|
|
128
|
+
|
|
129
|
+
cli.utils.setup_logging("DEBUG")
|
|
130
|
+
|
|
131
|
+
print_info("đ Checking flow status...")
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
manager = create_manager_from_env()
|
|
135
|
+
|
|
136
|
+
items = manager.get_project_items()
|
|
137
|
+
|
|
138
|
+
from project_manager.types import IssueStatus
|
|
139
|
+
|
|
140
|
+
ready_status = manager.config.status_mapping.to_provider_status(IssueStatus.READY)
|
|
141
|
+
in_progress_status = manager.config.status_mapping.to_provider_status(IssueStatus.IN_PROGRESS)
|
|
142
|
+
|
|
143
|
+
ready = [item for item in items if item.status == ready_status]
|
|
144
|
+
in_progress = [item for item in items if item.status == in_progress_status]
|
|
145
|
+
|
|
146
|
+
if in_progress:
|
|
147
|
+
item = in_progress[0]
|
|
148
|
+
console.print(f"[bold green]â Flow running:[/] Issue #{item.issue_number}")
|
|
149
|
+
console.print(f" Title: {item.title}")
|
|
150
|
+
console.print(f" Status: {item.status}")
|
|
151
|
+
else:
|
|
152
|
+
console.print("[yellow]â No flow currently running[/]")
|
|
153
|
+
|
|
154
|
+
console.print(f"\n[bold]Ready:[/] {len(ready)}, [bold]In progress:[/] {len(in_progress)}")
|
|
155
|
+
|
|
156
|
+
if ready:
|
|
157
|
+
console.print("\n[cyan]Next ready issues:[/]")
|
|
158
|
+
for item in ready[:5]:
|
|
159
|
+
console.print(f" #{item.issue_number}: {item.title}")
|
|
160
|
+
|
|
161
|
+
print_success("Status check complete")
|
|
162
|
+
|
|
163
|
+
except Exception as e:
|
|
164
|
+
print_error(f"Status check failed: {e}")
|
|
165
|
+
log.exception(f"Status check failed: {e}")
|
|
166
|
+
sys.exit(1)
|
cli/server.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Server commands for Polycode CLI."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
import uvicorn
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
|
|
9
|
+
from cli import print_error, print_info
|
|
10
|
+
from cli.utils import get_logger
|
|
11
|
+
from github_app.app import app as github_app
|
|
12
|
+
|
|
13
|
+
log = get_logger(__name__)
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
server_app = typer.Typer(help="Server management commands")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@server_app.command("webhook")
|
|
20
|
+
def webhook_server(
|
|
21
|
+
host: str = typer.Option("0.0.0.0", "--host", "-h", help="Webhook server host"),
|
|
22
|
+
port: int = typer.Option(8000, "--port", "-p", help="Webhook server port"),
|
|
23
|
+
reload: bool = typer.Option(False, "--reload", help="Enable auto-reload (development)"),
|
|
24
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose logging"),
|
|
25
|
+
) -> None:
|
|
26
|
+
"""Start webhook server for GitHub events.
|
|
27
|
+
|
|
28
|
+
Uses unified GitHub App webhook server.
|
|
29
|
+
For GitHub App webhooks (multi-repo), set GITHUB_APP_ID and GITHUB_APP_PRIVATE_KEY.
|
|
30
|
+
For legacy webhooks (single repo), set GITHUB_TOKEN and repo env vars.
|
|
31
|
+
"""
|
|
32
|
+
if verbose:
|
|
33
|
+
import cli.utils
|
|
34
|
+
|
|
35
|
+
cli.utils.setup_logging("DEBUG")
|
|
36
|
+
|
|
37
|
+
print_info(f"đ Starting webhook server on {host}:{port}")
|
|
38
|
+
|
|
39
|
+
if reload:
|
|
40
|
+
print_info("đ Auto-reload enabled")
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
uvicorn.run(
|
|
44
|
+
github_app,
|
|
45
|
+
host=host,
|
|
46
|
+
port=port,
|
|
47
|
+
reload=reload,
|
|
48
|
+
log_level="debug" if verbose else "warning",
|
|
49
|
+
)
|
|
50
|
+
except KeyboardInterrupt:
|
|
51
|
+
print_info("âš Server stopped by user")
|
|
52
|
+
sys.exit(0)
|
|
53
|
+
except Exception as e:
|
|
54
|
+
print_error(f"Failed to start server: {e}")
|
|
55
|
+
log.exception(f"Server startup failed: {e}")
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@server_app.command("socketio")
|
|
60
|
+
def socketio_server(
|
|
61
|
+
host: str = typer.Option("0.0.0.0", "--host", "-h", help="Socket.IO server host"),
|
|
62
|
+
port: int = typer.Option(8001, "--port", "-p", help="Socket.IO server port"),
|
|
63
|
+
reload: bool = typer.Option(False, "--reload", help="Enable auto-reload (development)"),
|
|
64
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose logging"),
|
|
65
|
+
) -> None:
|
|
66
|
+
"""Start Socket.IO streaming server.
|
|
67
|
+
|
|
68
|
+
Bridges Redis pub/sub to Socket.IO clients for real-time flow updates.
|
|
69
|
+
"""
|
|
70
|
+
if verbose:
|
|
71
|
+
import cli.utils
|
|
72
|
+
|
|
73
|
+
cli.utils.setup_logging("DEBUG")
|
|
74
|
+
|
|
75
|
+
print_info(f"đ Starting Socket.IO server on {host}:{port}")
|
|
76
|
+
|
|
77
|
+
if reload:
|
|
78
|
+
print_info("đ Auto-reload enabled")
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
uvicorn.run(
|
|
82
|
+
"channels.stream.server_app:asgi_app",
|
|
83
|
+
host=host,
|
|
84
|
+
port=port,
|
|
85
|
+
reload=reload,
|
|
86
|
+
log_level="debug" if verbose else "warning",
|
|
87
|
+
)
|
|
88
|
+
except KeyboardInterrupt:
|
|
89
|
+
print_info("âš Server stopped by user")
|
|
90
|
+
sys.exit(0)
|
|
91
|
+
except Exception as e:
|
|
92
|
+
print_error(f"Failed to start Socket.IO server: {e}")
|
|
93
|
+
log.exception(f"Socket.IO server startup failed: {e}")
|
|
94
|
+
sys.exit(1)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@server_app.command("flower")
|
|
98
|
+
def flower_server(
|
|
99
|
+
host: str = typer.Option("0.0.0.0", "--host", "-h", help="Flower server host"),
|
|
100
|
+
port: int = typer.Option(5555, "--port", "-p", help="Flower server port"),
|
|
101
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose logging"),
|
|
102
|
+
) -> None:
|
|
103
|
+
"""Start Flower monitoring UI for Celery workers.
|
|
104
|
+
|
|
105
|
+
Flower provides real-time monitoring of Celery tasks and workers.
|
|
106
|
+
"""
|
|
107
|
+
if verbose:
|
|
108
|
+
import cli.utils
|
|
109
|
+
|
|
110
|
+
cli.utils.setup_logging("DEBUG")
|
|
111
|
+
|
|
112
|
+
print_info(f"đ¸ Starting Flower monitoring on {host}:{port}")
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
uvicorn.run(
|
|
116
|
+
"flower:app",
|
|
117
|
+
host=host,
|
|
118
|
+
port=port,
|
|
119
|
+
log_level="debug" if verbose else "warning",
|
|
120
|
+
)
|
|
121
|
+
except KeyboardInterrupt:
|
|
122
|
+
print_info("âš Flower stopped by user")
|
|
123
|
+
sys.exit(0)
|
|
124
|
+
except Exception as e:
|
|
125
|
+
print_error(f"Failed to start Flower: {e}")
|
|
126
|
+
log.exception(f"Flower startup failed: {e}")
|
|
127
|
+
sys.exit(1)
|