apigw-cli 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.
@@ -0,0 +1,49 @@
1
+ Metadata-Version: 2.4
2
+ Name: apigw-cli
3
+ Version: 0.1.0
4
+ Summary: CLI для API Gateway
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: typer[all]>=0.12
8
+ Requires-Dist: httpx>=0.27
9
+ Requires-Dist: rich>=13
10
+ Requires-Dist: anthropic>=0.25
11
+ Requires-Dist: questionary>=2
12
+
13
+ # apigw-cli
14
+
15
+ CLI для API Gateway — управляйте интеграциями из терминала.
16
+
17
+ ## Установка
18
+
19
+ ```bash
20
+ pip install apigw-cli
21
+ ```
22
+
23
+ ## Быстрый старт
24
+
25
+ ```bash
26
+ apigw register
27
+ apigw config set user-id my_user
28
+ apigw tools list
29
+ apigw tools search "отзывы wildberries"
30
+ apigw connect wildberries --api-key MY_TOKEN
31
+ apigw execute wildberries__get_orders \
32
+ --input '{"date_from":"2026-03-01","date_to":"2026-03-23"}'
33
+ apigw chat
34
+ ```
35
+
36
+ ## Все команды
37
+
38
+ ```bash
39
+ apigw --help
40
+ apigw register # регистрация
41
+ apigw config set # настройка
42
+ apigw config show # просмотр конфига
43
+ apigw tools list # список инструментов
44
+ apigw tools search # поиск инструментов
45
+ apigw execute # выполнить инструмент
46
+ apigw connect # подключить сервис
47
+ apigw build # создать новый инструмент
48
+ apigw chat # интерактивный режим
49
+ ```
@@ -0,0 +1,37 @@
1
+ # apigw-cli
2
+
3
+ CLI для API Gateway — управляйте интеграциями из терминала.
4
+
5
+ ## Установка
6
+
7
+ ```bash
8
+ pip install apigw-cli
9
+ ```
10
+
11
+ ## Быстрый старт
12
+
13
+ ```bash
14
+ apigw register
15
+ apigw config set user-id my_user
16
+ apigw tools list
17
+ apigw tools search "отзывы wildberries"
18
+ apigw connect wildberries --api-key MY_TOKEN
19
+ apigw execute wildberries__get_orders \
20
+ --input '{"date_from":"2026-03-01","date_to":"2026-03-23"}'
21
+ apigw chat
22
+ ```
23
+
24
+ ## Все команды
25
+
26
+ ```bash
27
+ apigw --help
28
+ apigw register # регистрация
29
+ apigw config set # настройка
30
+ apigw config show # просмотр конфига
31
+ apigw tools list # список инструментов
32
+ apigw tools search # поиск инструментов
33
+ apigw execute # выполнить инструмент
34
+ apigw connect # подключить сервис
35
+ apigw build # создать новый инструмент
36
+ apigw chat # интерактивный режим
37
+ ```
@@ -0,0 +1,3 @@
1
+ """apigw-cli — CLI для API Gateway."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,65 @@
1
+ import httpx
2
+
3
+ from apigw_cli.config import get_api_key, get_base_url, load_config
4
+
5
+
6
+ class APIGWClient:
7
+ def __init__(self):
8
+ self.base_url = get_base_url().rstrip("/")
9
+ self.api_key = get_api_key()
10
+ self.user_id = load_config().get("user_id")
11
+
12
+ def _headers(self) -> dict[str, str]:
13
+ return {
14
+ "Authorization": f"Bearer {self.api_key}",
15
+ "Content-Type": "application/json",
16
+ }
17
+
18
+ def get(self, path: str, params: dict | None = None,
19
+ extra_headers: dict | None = None) -> dict:
20
+ headers = self._headers()
21
+ if extra_headers:
22
+ headers.update(extra_headers)
23
+ r = httpx.get(
24
+ f"{self.base_url}{path}",
25
+ headers=headers,
26
+ params=params,
27
+ timeout=30,
28
+ )
29
+ return self._handle(r)
30
+
31
+ def post(self, path: str, json: dict | None = None,
32
+ *, allow_errors: bool = False) -> dict:
33
+ r = httpx.post(
34
+ f"{self.base_url}{path}",
35
+ headers=self._headers(),
36
+ json=json,
37
+ timeout=60,
38
+ )
39
+ return self._handle(r, allow_errors=allow_errors)
40
+
41
+ def delete(self, path: str) -> dict:
42
+ r = httpx.delete(
43
+ f"{self.base_url}{path}",
44
+ headers=self._headers(),
45
+ timeout=30,
46
+ )
47
+ return self._handle(r)
48
+
49
+ def _handle(self, r: httpx.Response, *, allow_errors: bool = False) -> dict:
50
+ if r.status_code == 401:
51
+ raise SystemExit("❌ Неверный API ключ. Запустите: apigw register")
52
+ if r.status_code == 429:
53
+ raise SystemExit("❌ Превышен лимит запросов. Подождите минуту.")
54
+ if allow_errors and r.status_code in (403, 503):
55
+ try:
56
+ return r.json()
57
+ except Exception:
58
+ pass
59
+ if r.status_code >= 400:
60
+ try:
61
+ err = r.json().get("message", r.text)
62
+ except Exception:
63
+ err = r.text
64
+ raise SystemExit(f"❌ Ошибка: {err}")
65
+ return r.json()
File without changes
@@ -0,0 +1,87 @@
1
+ import time
2
+
3
+ import typer
4
+ from rich.console import Console
5
+ from rich.progress import Progress, SpinnerColumn, TextColumn
6
+
7
+ from apigw_cli.client import APIGWClient
8
+
9
+
10
+ def build(
11
+ service: str = typer.Argument(..., help="slug сервиса"),
12
+ tool: str = typer.Argument(..., help="название инструмента"),
13
+ openapi_url: str = typer.Option(None, "--openapi-url"),
14
+ wait: bool = typer.Option(True, "--wait/--no-wait",
15
+ help="ждать завершения"),
16
+ ):
17
+ """Создать новый инструмент через LLM.
18
+
19
+ Примеры:
20
+ apigw build wildberries reply_to_review
21
+ apigw build my-crm get-leads \\
22
+ --openapi-url https://api.mycrm.com/openapi.json
23
+ """
24
+ console = Console()
25
+ client = APIGWClient()
26
+
27
+ payload = {
28
+ "service_slug": service.replace("-", "_"),
29
+ "tool_name": tool.replace("-", "_"),
30
+ }
31
+ if openapi_url:
32
+ payload["openapi_url"] = openapi_url
33
+
34
+ data = client.post("/v1/tools/build", json=payload)
35
+ build_id = data.get("build_id")
36
+
37
+ if data.get("status") == "exists":
38
+ console.print(f"[green]✅ Инструмент уже существует: "
39
+ f"{service}__{tool}[/green]")
40
+ return
41
+
42
+ console.print(f"[cyan]🔨 Сборка запущена[/cyan] "
43
+ f"[dim](id: {build_id[:8] if build_id else '-'}...)[/dim]")
44
+ console.print("[dim]Gateway ищет документацию и генерирует код...[/dim]\n")
45
+
46
+ if not wait:
47
+ if build_id:
48
+ console.print(f"Проверить: [cyan]apigw build status {build_id}[/cyan]")
49
+ return
50
+
51
+ STATUS_LABELS = {
52
+ "queued": "В очереди...",
53
+ "building": "Ищу документацию...",
54
+ "testing": "Тестирую в sandbox...",
55
+ "done": "Готово!",
56
+ "failed": "Ошибка",
57
+ }
58
+
59
+ with Progress(SpinnerColumn(), TextColumn("{task.description}"),
60
+ console=console) as progress:
61
+ task = progress.add_task("Запуск...", total=None)
62
+ for _ in range(60):
63
+ time.sleep(10)
64
+ s = client.get(f"/v1/tools/build/{build_id}")
65
+ current = s.get("status", "queued")
66
+ progress.update(task,
67
+ description=STATUS_LABELS.get(current, current))
68
+
69
+ if current == "done":
70
+ progress.stop()
71
+ console.print(
72
+ f"\n[green]✅ Готов: {service}__{tool}[/green]\n"
73
+ f"Использовать: [cyan]apigw execute "
74
+ f"{service}__{tool}[/cyan]"
75
+ )
76
+ return
77
+ if current == "failed":
78
+ progress.stop()
79
+ error = s.get("error_msg", "неизвестная ошибка")
80
+ console.print(f"\n[red]❌ Не удалось: {error}[/red]")
81
+ console.print(
82
+ "[dim]Попробуйте передать --openapi-url[/dim]"
83
+ )
84
+ return
85
+
86
+ console.print(f"[yellow]⏱️ Таймаут. Проверьте: "
87
+ f"apigw build status {build_id}[/yellow]")
@@ -0,0 +1,145 @@
1
+ import json
2
+
3
+ import typer
4
+ from rich.console import Console
5
+ from rich.panel import Panel
6
+
7
+ from apigw_cli.client import APIGWClient
8
+ from apigw_cli.config import load_config
9
+
10
+
11
+ def chat(
12
+ user: str = typer.Option(None, "--user", "-u"),
13
+ ):
14
+ """Интерактивный режим — пишите на русском.
15
+
16
+ CLI сам выбирает нужный инструмент и выполняет его.
17
+ Введите 'выход' для завершения.
18
+ """
19
+ import datetime
20
+
21
+ import anthropic
22
+
23
+ console = Console()
24
+ user_id = user or load_config().get("user_id")
25
+
26
+ if not user_id:
27
+ console.print(
28
+ "[red]❌ Укажите user_id: "
29
+ "apigw config set user-id <id>[/red]"
30
+ )
31
+ raise typer.Exit(1)
32
+
33
+ http = APIGWClient()
34
+
35
+ # Получить список инструментов для системного промпта
36
+ tools_data = http.get("/v1/tools", params={"limit": 100})
37
+ available = tools_data.get("tools", [])
38
+ tools_desc = "\n".join(
39
+ f"- {t['name']}: {t.get('description','')}"
40
+ for t in available
41
+ )
42
+
43
+ system = f"""Ты — ассистент для работы с API Gateway.
44
+ Доступные инструменты:
45
+ {tools_desc}
46
+
47
+ Когда пользователь просит что-то сделать — верни ТОЛЬКО JSON:
48
+ {{"tool": "tool_name", "input": {{"param": "value"}}}}
49
+
50
+ Если инструмента нет:
51
+ {{"tool": null, "message": "объяснение"}}
52
+
53
+ user_id: {user_id}
54
+ Сегодня: {datetime.date.today()}
55
+ """
56
+
57
+ console.print(Panel(
58
+ "[bold cyan]API Gateway Chat[/bold cyan]\n"
59
+ "[dim]Пишите на русском что нужно сделать.\n"
60
+ "Введите 'выход' для завершения.[/dim]",
61
+ border_style="cyan",
62
+ ))
63
+
64
+ ai = anthropic.Anthropic()
65
+ history = []
66
+
67
+ while True:
68
+ try:
69
+ user_input = input("\n> ").strip()
70
+ except (KeyboardInterrupt, EOFError):
71
+ console.print("\n[dim]До свидания![/dim]")
72
+ break
73
+
74
+ if user_input.lower() in ("выход", "exit", "quit", "q"):
75
+ console.print("[dim]До свидания![/dim]")
76
+ break
77
+ if not user_input:
78
+ continue
79
+
80
+ history.append({"role": "user", "content": user_input})
81
+
82
+ with console.status("[dim]Думаю...[/dim]"):
83
+ resp = ai.messages.create(
84
+ model="claude-haiku-4-5-20251001",
85
+ max_tokens=500,
86
+ system=system,
87
+ messages=history,
88
+ )
89
+ ai_text = resp.content[0].text.strip()
90
+ history.append({"role": "assistant", "content": ai_text})
91
+
92
+ try:
93
+ clean = (ai_text.replace("```json", "")
94
+ .replace("```", "").strip())
95
+ action = json.loads(clean)
96
+ except json.JSONDecodeError:
97
+ console.print(f"[dim]{ai_text}[/dim]")
98
+ continue
99
+
100
+ if not action.get("tool"):
101
+ console.print(
102
+ f"[yellow]{action.get('message', ai_text)}[/yellow]"
103
+ )
104
+ continue
105
+
106
+ tool_name = action["tool"]
107
+ tool_input = action.get("input", {})
108
+ console.print(f"[dim]→ {tool_name}[/dim]")
109
+
110
+ result = http.post("/v1/tools/execute", json={
111
+ "tool_name": tool_name,
112
+ "user_id": user_id,
113
+ "input": tool_input,
114
+ })
115
+
116
+ if result.get("success"):
117
+ data = result.get("data", {})
118
+ summary_resp = ai.messages.create(
119
+ model="claude-haiku-4-5-20251001",
120
+ max_tokens=200,
121
+ messages=[{"role": "user", "content":
122
+ f"Пользователь просил: {user_input}\n"
123
+ f"Результат: "
124
+ f"{json.dumps(data, ensure_ascii=False)[:1000]}\n"
125
+ f"Кратко опиши на русском (1-2 предложения)."}],
126
+ )
127
+ console.print(
128
+ f"\n[green]{summary_resp.content[0].text}[/green]"
129
+ )
130
+ history.append({"role": "user",
131
+ "content": f"[Результат {tool_name}]: "
132
+ f"{json.dumps(data, ensure_ascii=False)[:300]}"
133
+ })
134
+ else:
135
+ error = result.get("error", "unknown")
136
+ if error == "reauth_required":
137
+ service = tool_name.split("__")[0]
138
+ console.print(
139
+ f"[yellow]⚠️ Подключите {service}: "
140
+ f"apigw connect {service}[/yellow]"
141
+ )
142
+ else:
143
+ console.print(
144
+ f"[red]❌ {result.get('message', error)}[/red]"
145
+ )
@@ -0,0 +1,41 @@
1
+ import typer
2
+ import json
3
+ from rich.console import Console
4
+ from apigw_cli.config import load_config, save_config
5
+
6
+ app = typer.Typer(help="Управление конфигурацией")
7
+
8
+
9
+ @app.command("set")
10
+ def config_set(key: str, value: str):
11
+ """Установить параметр.
12
+
13
+ Примеры:
14
+ apigw config set user-id shop_123
15
+ apigw config set base-url http://localhost:8080
16
+ """
17
+ key_map = {"user-id": "user_id", "base-url": "base_url",
18
+ "api-key": "api_key"}
19
+ if key not in key_map:
20
+ typer.echo(f"Неизвестный параметр: {key}")
21
+ typer.echo(f"Доступные: {', '.join(key_map.keys())}")
22
+ raise typer.Exit(1)
23
+ cfg = load_config()
24
+ cfg[key_map[key]] = value
25
+ save_config(**{k: cfg.get(k) for k in
26
+ ["api_key", "base_url", "user_id"]})
27
+ typer.echo(f" {key} = {value}")
28
+
29
+
30
+ @app.command("show")
31
+ def config_show():
32
+ """Показать текущий конфиг."""
33
+ cfg = load_config()
34
+ if not cfg:
35
+ typer.echo("Не авторизован. Запустите: apigw register")
36
+ return
37
+ display = cfg.copy()
38
+ if display.get("api_key"):
39
+ k = display["api_key"]
40
+ display["api_key"] = k[:16] + "..." + k[-4:]
41
+ Console().print_json(json.dumps(display))
@@ -0,0 +1,66 @@
1
+ import webbrowser
2
+ import typer
3
+ import questionary
4
+ from rich.console import Console
5
+ from apigw_cli.client import APIGWClient
6
+ from apigw_cli.config import load_config
7
+
8
+
9
+ def connect(
10
+ service: str = typer.Argument(..., help="название сервиса"),
11
+ user: str = typer.Option(None, "--user", "-u"),
12
+ api_key_value: str = typer.Option(None, "--api-key",
13
+ help="API ключ сервиса"),
14
+ ):
15
+ """Подключить сервис для пользователя.
16
+
17
+ API ключ (WB, Telegram, Ozon, Kaspi):
18
+ apigw connect wildberries --api-key TOKEN
19
+
20
+ OAuth (Facebook, Google, Яндекс, Slack):
21
+ apigw connect facebook-ads
22
+ """
23
+ console = Console()
24
+ client = APIGWClient()
25
+ user_id = user or load_config().get("user_id")
26
+
27
+ if not user_id:
28
+ console.print("[red]❌ Укажите user_id: --user <id>[/red]")
29
+ raise typer.Exit(1)
30
+
31
+ slug = service.replace("-", "_")
32
+
33
+ if api_key_value:
34
+ client.post(
35
+ f"/auth/connect/{slug}?user_id={user_id}",
36
+ json={"api_key": api_key_value},
37
+ )
38
+ console.print(
39
+ f"[green]✅ {service} подключён "
40
+ f"для пользователя {user_id}[/green]"
41
+ )
42
+ else:
43
+ data = client.get(
44
+ f"/auth/connect/{slug}",
45
+ params={"user_id": user_id,
46
+ "redirect_back": f"{client.base_url}/done"},
47
+ )
48
+ auth_url = data.get("auth_url")
49
+ if not auth_url:
50
+ console.print("[red]❌ Не удалось получить URL[/red]")
51
+ raise typer.Exit(1)
52
+
53
+ console.print(f"\n[cyan]Открываю браузер для {service}...[/cyan]")
54
+ console.print(f"[dim]Если не открылся:[/dim] {auth_url}\n")
55
+ webbrowser.open(auth_url)
56
+
57
+ questionary.text(
58
+ "Нажмите Enter после авторизации в браузере..."
59
+ ).ask()
60
+
61
+ status = client.get(f"/auth/status/{slug}",
62
+ extra_headers={"X-User-ID": user_id})
63
+ if status.get("connected"):
64
+ console.print(f"[green]✅ {service} подключён![/green]")
65
+ else:
66
+ console.print("[yellow]⚠️ Проверьте позже.[/yellow]")
@@ -0,0 +1,65 @@
1
+ import json
2
+ import typer
3
+ from rich.console import Console
4
+ from rich.syntax import Syntax
5
+ from apigw_cli.client import APIGWClient
6
+ from apigw_cli.config import load_config
7
+
8
+
9
+ def execute(
10
+ tool_name: str = typer.Argument(..., help="имя инструмента"),
11
+ user: str = typer.Option(None, "--user", "-u",
12
+ help="user_id (дефолт из конфига)"),
13
+ input_json: str = typer.Option(None, "--input", "-i",
14
+ help='JSON: \'{"key":"val"}\''),
15
+ idempotency_key: str = typer.Option(None, "--idempotency-key"),
16
+ ):
17
+ """Выполнить инструмент.
18
+
19
+ Примеры:
20
+ apigw execute telegram__send_message \\
21
+ --input '{"chat_id":"123","text":"Привет"}'
22
+ apigw execute wildberries__get_orders \\
23
+ --input '{"date_from":"2026-03-01","date_to":"2026-03-23"}'
24
+ """
25
+ console = Console()
26
+ client = APIGWClient()
27
+
28
+ user_id = user or load_config().get("user_id")
29
+ if not user_id:
30
+ console.print("[red]❌ user_id не указан.[/red]")
31
+ console.print("Установите: [cyan]apigw config set user-id <id>[/cyan]")
32
+ raise typer.Exit(1)
33
+
34
+ input_data = {}
35
+ if input_json:
36
+ try:
37
+ input_data = json.loads(input_json)
38
+ except json.JSONDecodeError:
39
+ console.print("[red]❌ Невалидный JSON в --input[/red]")
40
+ raise typer.Exit(1)
41
+
42
+ with console.status(f"[dim]Выполняю {tool_name}...[/dim]"):
43
+ payload = {"tool_name": tool_name,
44
+ "user_id": user_id, "input": input_data}
45
+ if idempotency_key:
46
+ payload["idempotency_key"] = idempotency_key
47
+ result = client.post("/tools/execute", json=payload,
48
+ allow_errors=True)
49
+
50
+ if result.get("success"):
51
+ console.print(f"[green]✅ Успешно[/green] "
52
+ f"[dim]({result.get('duration_ms', 0)}мс)[/dim]")
53
+ console.print(Syntax(
54
+ json.dumps(result.get("data", {}),
55
+ ensure_ascii=False, indent=2),
56
+ "json", theme="monokai"
57
+ ))
58
+ else:
59
+ error = result.get("error", "unknown")
60
+ if error == "reauth_required":
61
+ service = tool_name.split("__")[0]
62
+ console.print("[yellow]⚠️ Требуется переподключение[/yellow]")
63
+ console.print(f"Запустите: [cyan]apigw connect {service}[/cyan]")
64
+ else:
65
+ console.print(f"[red]❌ {result.get('message', error)}[/red]")
@@ -0,0 +1,66 @@
1
+ import httpx
2
+ import typer
3
+ import questionary
4
+ from rich.console import Console
5
+ from apigw_cli.config import load_config, save_config, get_base_url
6
+
7
+
8
+ def register():
9
+ """Зарегистрироваться и получить API ключ."""
10
+ console = Console()
11
+ console.print("\n[bold cyan]API Gateway — Регистрация[/bold cyan]\n")
12
+
13
+ existing = load_config()
14
+ if existing.get("api_key"):
15
+ if not questionary.confirm(
16
+ "Вы уже зарегистрированы. Перерегистрироваться?"
17
+ ).ask():
18
+ console.print("[green]Используется существующий ключ.[/green]")
19
+ return
20
+
21
+ email = questionary.text(
22
+ "Email:",
23
+ validate=lambda x: True if "@" in x and "." in x
24
+ else "Введите корректный email"
25
+ ).ask()
26
+
27
+ name = questionary.text(
28
+ "Имя:",
29
+ validate=lambda x: True if len(x) > 1 else "Введите имя"
30
+ ).ask()
31
+
32
+ company = questionary.text("Компания (необязательно):").ask()
33
+
34
+ console.print("\n[dim]Регистрирую...[/dim]")
35
+ try:
36
+ r = httpx.post(
37
+ f"{get_base_url()}/v1/auth/register",
38
+ json={"email": email, "name": name,
39
+ "company": company or None},
40
+ timeout=30,
41
+ )
42
+ except httpx.ConnectError:
43
+ console.print(f"[red]Не удалось подключиться к {get_base_url()}[/red]")
44
+ raise typer.Exit(1)
45
+
46
+ if r.status_code == 409:
47
+ console.print("[red]Email уже зарегистрирован.[/red]")
48
+ raise typer.Exit(1)
49
+
50
+ if r.status_code not in (200, 201):
51
+ console.print(f"[red]Ошибка: {r.text}[/red]")
52
+ raise typer.Exit(1)
53
+
54
+ api_key = r.json()["api_key"]
55
+ save_config(api_key=api_key, base_url=get_base_url())
56
+
57
+ console.print(f"""
58
+ [bold green]Готово![/bold green]
59
+
60
+ Ключ сохранён в ~/.apigw/config.json
61
+
62
+ Следующие шаги:
63
+ [cyan]apigw config set user-id <ваш-user-id>[/cyan]
64
+ [cyan]apigw tools list[/cyan]
65
+ [cyan]apigw chat[/cyan]
66
+ """)
@@ -0,0 +1,125 @@
1
+ import typer
2
+ from rich.console import Console
3
+ from rich.table import Table
4
+ from rich.panel import Panel
5
+ from apigw_cli.client import APIGWClient
6
+ from apigw_cli.output import print_json_data
7
+
8
+ app = typer.Typer(help="Работа с инструментами")
9
+
10
+
11
+ @app.command("list")
12
+ def tools_list(
13
+ region: str = typer.Option(None, help="cis или global"),
14
+ service: str = typer.Option(None, help="фильтр по сервису"),
15
+ limit: int = typer.Option(20),
16
+ cursor: str = typer.Option(None, help="Курсор пагинации"),
17
+ ):
18
+ """Показать список доступных инструментов."""
19
+ client = APIGWClient()
20
+ params: dict = {"limit": limit}
21
+ if region:
22
+ params["region"] = region
23
+ if service:
24
+ params["service_slug"] = service
25
+ if cursor:
26
+ params["cursor"] = cursor
27
+
28
+ data = client.get("/tools", params=params)
29
+ tools = data.get("tools", [])
30
+
31
+ console = Console()
32
+
33
+ if not tools:
34
+ console.print("[yellow]Инструменты не найдены.[/yellow]")
35
+ return
36
+
37
+ table = Table(title=f"Инструменты ({len(tools)})")
38
+ table.add_column("Название", style="cyan")
39
+ table.add_column("Тип", style="yellow")
40
+ table.add_column("Регион")
41
+ table.add_column("Описание")
42
+
43
+ for t in tools:
44
+ table.add_row(
45
+ t["name"],
46
+ t.get("operation_type", "read"),
47
+ t.get("region", "global"),
48
+ (t.get("description") or "")[:60],
49
+ )
50
+ console.print(table)
51
+
52
+ pagination = data.get("pagination", {})
53
+ if pagination.get("has_more"):
54
+ console.print(
55
+ f"\n[dim]Ещё есть результаты. "
56
+ f"Используйте --cursor {pagination.get('next_cursor')}[/]"
57
+ )
58
+
59
+
60
+ @app.command("search")
61
+ def tools_search(query: str = typer.Argument(...)):
62
+ """Найти инструмент по описанию.
63
+
64
+ Пример: apigw tools search "отзывы wildberries"
65
+ """
66
+ client = APIGWClient()
67
+ data = client.post("/tools/discover",
68
+ json={"need": query, "limit": 5})
69
+ console = Console()
70
+
71
+ if not data.get("found"):
72
+ console.print("[yellow]Инструмент не найден.[/yellow]")
73
+ console.print("Создать: [cyan]apigw build <сервис> <действие>[/cyan]")
74
+ return
75
+
76
+ for t in data.get("tools", []):
77
+ score = t.get("relevance_score", 0)
78
+ console.print(Panel(
79
+ f"[bold]{t['name']}[/bold]\n"
80
+ f"{t.get('description', '')}\n\n"
81
+ f"[dim]Тип: {t.get('operation_type')} | "
82
+ f"Регион: {t.get('region')} | "
83
+ f"Релевантность: {score:.0%}[/dim]",
84
+ border_style="cyan",
85
+ ))
86
+
87
+
88
+ @app.command("schema")
89
+ def tool_schema(
90
+ tool_name: str = typer.Argument(..., help="Имя инструмента"),
91
+ ):
92
+ """Показать JSON-схему инструмента (input/output)."""
93
+ client = APIGWClient()
94
+ data = client.get(f"/tools/{tool_name}/schema")
95
+ print_json_data(data)
96
+
97
+
98
+ @app.command("services")
99
+ def list_services():
100
+ """Список доступных сервисов."""
101
+ client = APIGWClient()
102
+ data = client.get("/services")
103
+ console = Console()
104
+
105
+ services = data.get("services", data) if isinstance(data, dict) else data
106
+ if not isinstance(services, list):
107
+ print_json_data(data)
108
+ return
109
+
110
+ table = Table(title="Сервисы")
111
+ table.add_column("Slug", style="cyan")
112
+ table.add_column("Название")
113
+ table.add_column("Регион", style="yellow")
114
+ table.add_column("Auth")
115
+ table.add_column("Тулов", justify="right")
116
+
117
+ for s in services:
118
+ table.add_row(
119
+ s.get("slug", ""),
120
+ s.get("display_name", ""),
121
+ s.get("region", ""),
122
+ s.get("auth_type", ""),
123
+ str(s.get("tools_count", "")),
124
+ )
125
+ console.print(table)
@@ -0,0 +1,36 @@
1
+ from pathlib import Path
2
+ import json
3
+ import os
4
+
5
+ CONFIG_DIR = Path.home() / ".apigw"
6
+ CONFIG_FILE = CONFIG_DIR / "config.json"
7
+
8
+
9
+ def save_config(api_key: str, base_url: str, user_id: str | None = None):
10
+ CONFIG_DIR.mkdir(exist_ok=True)
11
+ CONFIG_FILE.write_text(json.dumps(
12
+ {"api_key": api_key, "base_url": base_url, "user_id": user_id},
13
+ indent=2,
14
+ ))
15
+ CONFIG_FILE.chmod(0o600)
16
+
17
+
18
+ def load_config() -> dict:
19
+ if not CONFIG_FILE.exists():
20
+ return {}
21
+ return json.loads(CONFIG_FILE.read_text())
22
+
23
+
24
+ def get_api_key() -> str:
25
+ key = load_config().get("api_key") or os.getenv("APIGW_API_KEY")
26
+ if not key:
27
+ raise SystemExit("❌ Не авторизован. Запустите: apigw register")
28
+ return key
29
+
30
+
31
+ def get_base_url() -> str:
32
+ return (
33
+ load_config().get("base_url")
34
+ or os.getenv("APIGW_BASE_URL")
35
+ or "https://api-gateway-production-8bd8.up.railway.app"
36
+ )
@@ -0,0 +1,20 @@
1
+ import typer
2
+
3
+ app = typer.Typer(
4
+ name="apigw",
5
+ help="API Gateway CLI — управление интеграциями из терминала",
6
+ no_args_is_help=True,
7
+ )
8
+
9
+ from apigw_cli.commands import register, tools, execute, connect, build, config_cmd, chat
10
+
11
+ app.command("register")(register.register)
12
+ app.add_typer(tools.app, name="tools")
13
+ app.command("execute")(execute.execute)
14
+ app.command("connect")(connect.connect)
15
+ app.command("build")(build.build)
16
+ app.add_typer(config_cmd.app, name="config")
17
+ app.command("chat")(chat.chat)
18
+
19
+ if __name__ == "__main__":
20
+ app()
@@ -0,0 +1,71 @@
1
+ """Helpers for pretty-printing CLI output via Rich."""
2
+
3
+ from rich.console import Console
4
+ from rich.table import Table
5
+ from rich.panel import Panel
6
+ from rich import print_json
7
+ import json
8
+
9
+ console = Console()
10
+
11
+
12
+ def print_success(message: str):
13
+ console.print(f"[bold green]✅ {message}[/]")
14
+
15
+
16
+ def print_error(message: str):
17
+ console.print(f"[bold red]❌ {message}[/]")
18
+
19
+
20
+ def print_warning(message: str):
21
+ console.print(f"[bold yellow]⚠️ {message}[/]")
22
+
23
+
24
+ def print_info(message: str):
25
+ console.print(f"[bold blue]ℹ️ {message}[/]")
26
+
27
+
28
+ def print_json_data(data: dict | list):
29
+ print_json(json.dumps(data, ensure_ascii=False, indent=2))
30
+
31
+
32
+ def print_tools_table(tools: list[dict]):
33
+ table = Table(title="Инструменты", show_lines=True)
34
+ table.add_column("Имя", style="cyan", no_wrap=True)
35
+ table.add_column("Сервис", style="green")
36
+ table.add_column("Тип", style="yellow")
37
+ table.add_column("Описание")
38
+
39
+ for t in tools:
40
+ table.add_row(
41
+ t.get("name", ""),
42
+ t.get("service_slug", ""),
43
+ t.get("operation_type", ""),
44
+ t.get("description", ""),
45
+ )
46
+
47
+ console.print(table)
48
+
49
+
50
+ def print_services_table(services: list[dict]):
51
+ table = Table(title="Сервисы", show_lines=True)
52
+ table.add_column("Slug", style="cyan", no_wrap=True)
53
+ table.add_column("Название", style="green")
54
+ table.add_column("Регион", style="yellow")
55
+ table.add_column("Auth", style="magenta")
56
+ table.add_column("Тулов", justify="right")
57
+
58
+ for s in services:
59
+ table.add_row(
60
+ s.get("slug", ""),
61
+ s.get("display_name", ""),
62
+ s.get("region", ""),
63
+ s.get("auth_type", ""),
64
+ str(s.get("tools_count", "")),
65
+ )
66
+
67
+ console.print(table)
68
+
69
+
70
+ def print_panel(title: str, content: str):
71
+ console.print(Panel(content, title=title, border_style="blue"))
@@ -0,0 +1,49 @@
1
+ Metadata-Version: 2.4
2
+ Name: apigw-cli
3
+ Version: 0.1.0
4
+ Summary: CLI для API Gateway
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: typer[all]>=0.12
8
+ Requires-Dist: httpx>=0.27
9
+ Requires-Dist: rich>=13
10
+ Requires-Dist: anthropic>=0.25
11
+ Requires-Dist: questionary>=2
12
+
13
+ # apigw-cli
14
+
15
+ CLI для API Gateway — управляйте интеграциями из терминала.
16
+
17
+ ## Установка
18
+
19
+ ```bash
20
+ pip install apigw-cli
21
+ ```
22
+
23
+ ## Быстрый старт
24
+
25
+ ```bash
26
+ apigw register
27
+ apigw config set user-id my_user
28
+ apigw tools list
29
+ apigw tools search "отзывы wildberries"
30
+ apigw connect wildberries --api-key MY_TOKEN
31
+ apigw execute wildberries__get_orders \
32
+ --input '{"date_from":"2026-03-01","date_to":"2026-03-23"}'
33
+ apigw chat
34
+ ```
35
+
36
+ ## Все команды
37
+
38
+ ```bash
39
+ apigw --help
40
+ apigw register # регистрация
41
+ apigw config set # настройка
42
+ apigw config show # просмотр конфига
43
+ apigw tools list # список инструментов
44
+ apigw tools search # поиск инструментов
45
+ apigw execute # выполнить инструмент
46
+ apigw connect # подключить сервис
47
+ apigw build # создать новый инструмент
48
+ apigw chat # интерактивный режим
49
+ ```
@@ -0,0 +1,21 @@
1
+ README.md
2
+ pyproject.toml
3
+ apigw_cli/__init__.py
4
+ apigw_cli/client.py
5
+ apigw_cli/config.py
6
+ apigw_cli/main.py
7
+ apigw_cli/output.py
8
+ apigw_cli.egg-info/PKG-INFO
9
+ apigw_cli.egg-info/SOURCES.txt
10
+ apigw_cli.egg-info/dependency_links.txt
11
+ apigw_cli.egg-info/entry_points.txt
12
+ apigw_cli.egg-info/requires.txt
13
+ apigw_cli.egg-info/top_level.txt
14
+ apigw_cli/commands/__init__.py
15
+ apigw_cli/commands/build.py
16
+ apigw_cli/commands/chat.py
17
+ apigw_cli/commands/config_cmd.py
18
+ apigw_cli/commands/connect.py
19
+ apigw_cli/commands/execute.py
20
+ apigw_cli/commands/register.py
21
+ apigw_cli/commands/tools.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ apigw = apigw_cli.main:app
@@ -0,0 +1,5 @@
1
+ typer[all]>=0.12
2
+ httpx>=0.27
3
+ rich>=13
4
+ anthropic>=0.25
5
+ questionary>=2
@@ -0,0 +1 @@
1
+ apigw_cli
@@ -0,0 +1,20 @@
1
+ [project]
2
+ name = "apigw-cli"
3
+ version = "0.1.0"
4
+ description = "CLI для API Gateway"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ dependencies = [
8
+ "typer[all]>=0.12",
9
+ "httpx>=0.27",
10
+ "rich>=13",
11
+ "anthropic>=0.25",
12
+ "questionary>=2",
13
+ ]
14
+
15
+ [project.scripts]
16
+ apigw = "apigw_cli.main:app"
17
+
18
+ [build-system]
19
+ requires = ["setuptools>=68"]
20
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+