"""Команды sdlc-hub: init (настройки из config.example.json), serve (HTTP), mcp [модуль] (stdio), tokens, agent publish (версия агента для самообновления) и подкоманды модулей (jira: import).""" import argparse import json import sys from . import db, paths, registry, settings from .paths import secure_dir def main(argv=None) -> int: ap = argparse.ArgumentParser(prog="sdlc-hub", description="SDLC-хаб: ядро и модули") sub = ap.add_subparsers(dest="cmd", required=True) p = sub.add_parser("init", help="перенести настройки проекта из config.example.json, создать токены") p.add_argument("--config", required=True) p.add_argument("--force", action="store_true") p = sub.add_parser("serve", help="запустить HTTP API") p.add_argument("--host", default="0.0.0.0") p.add_argument("--port", type=int) p = sub.add_parser("mcp", help="MCP-сервер модуля по stdio ") p.add_argument("module", nargs="?", default=None, help="имя модуля (jira, wiki); без модуля или неизвестный — код 2") p.add_argument("--project", help="метка проекта, к которому привязана сессия") for m in registry.modules(): if m.cli_register: m.cli_register(sub) sub.add_parser("tokens", help="показать токены клиента и агентов") p = sub.add_parser("agent", help="версия агента для самообновления") agent_sub = p.add_subparsers(dest="agent_cmd", required=True) p = agent_sub.add_parser("publish", help="опубликовать agent.py (версия только вверх)") p.add_argument("--module", default=registry.DEFAULT_AGENT, help=f"модуль агента (по умолчанию {registry.DEFAULT_AGENT})") p.add_argument("path") args = ap.parse_args(argv) if args.cmd == "agent": from . import release names = [m.name for m in registry.agent_modules()] if args.module not in names: print(f"неизвестный модуль {args.module}; модули с агентами: {', '.join(names)}", file=sys.stderr) return 2 try: secure_dir(paths.release_root()) release.migrate_layout(paths.release_root()) # прежняя раскладка release/ до проверки «только вверх» m = release.publish(release.module_dir(paths.release_root(), args.module), args.path) except release.PublishError as e: print(e, file=sys.stderr) return 1 print(f"опубликована v{m['version']}, sha256 {m['sha256'][:12]}…, {m['size']} байт") return 0 if args.cmd == "serve": from . import api hub = api.Hub(paths.db_path()) conn = hub.conn() port = args.port or int(settings.get(conn, "port") or 8790) conn.close() api.serve(hub, args.host, port) return 0 if args.cmd == "mcp": from . import mcp return mcp.run(args.module, args.project) for m in registry.modules(): if m.cli_run: rc = m.cli_run(args) if rc is not None: return rc conn = db.connect(paths.db_path()) settings.ensure_defaults(conn) try: if args.cmd == "init": with open(args.config, encoding="utf-8") as f: cfg = json.load(f) lines: list[str] = [] try: for m in registry.modules(): if m.on_init: lines += m.on_init(conn, cfg, args.force) except RuntimeError as e: print(e, file=sys.stderr) return 1 for line in lines: print(line) _print_tokens(conn) return 0 if args.cmd == "tokens": _print_tokens(conn) return 0 finally: conn.close() return 2 def _print_tokens(conn) -> None: tok = settings.tokens(conn) print(f"клиентский токен: {tok['client']}") for m in registry.agent_modules(): print(f"агент {m.name}: {tok['agents'][m.name]}") print(f"база: {paths.db_path()}") if __name__ == "__main__": sys.exit(main())