socaity-cli 0.1.1__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.
Files changed (35) hide show
  1. socaity_cli-0.1.1/PKG-INFO +73 -0
  2. socaity_cli-0.1.1/README.md +56 -0
  3. socaity_cli-0.1.1/pyproject.toml +52 -0
  4. socaity_cli-0.1.1/setup.cfg +4 -0
  5. socaity_cli-0.1.1/socaity_cli/__init__.py +30 -0
  6. socaity_cli-0.1.1/socaity_cli/__main__.py +4 -0
  7. socaity_cli-0.1.1/socaity_cli/apipod_commands.py +82 -0
  8. socaity_cli-0.1.1/socaity_cli/auth.py +197 -0
  9. socaity_cli-0.1.1/socaity_cli/backend_client.py +23 -0
  10. socaity_cli-0.1.1/socaity_cli/cli.py +352 -0
  11. socaity_cli-0.1.1/socaity_cli/clients/__init__.py +18 -0
  12. socaity_cli-0.1.1/socaity_cli/clients/analytics.py +60 -0
  13. socaity_cli-0.1.1/socaity_cli/clients/catalog.py +147 -0
  14. socaity_cli-0.1.1/socaity_cli/clients/deployment.py +129 -0
  15. socaity_cli-0.1.1/socaity_cli/clients/files.py +82 -0
  16. socaity_cli-0.1.1/socaity_cli/clients/jobs.py +80 -0
  17. socaity_cli-0.1.1/socaity_cli/clients/projects.py +67 -0
  18. socaity_cli-0.1.1/socaity_cli/clients/sdk_install.py +16 -0
  19. socaity_cli-0.1.1/socaity_cli/credentials.py +128 -0
  20. socaity_cli-0.1.1/socaity_cli/deployment.py +349 -0
  21. socaity_cli-0.1.1/socaity_cli/errors.py +35 -0
  22. socaity_cli-0.1.1/socaity_cli/http.py +105 -0
  23. socaity_cli-0.1.1/socaity_cli/prompts.py +17 -0
  24. socaity_cli-0.1.1/socaity_cli/registry_push.py +122 -0
  25. socaity_cli-0.1.1/socaity_cli/requires.py +47 -0
  26. socaity_cli-0.1.1/socaity_cli/sdk_commands.py +28 -0
  27. socaity_cli-0.1.1/socaity_cli.egg-info/PKG-INFO +73 -0
  28. socaity_cli-0.1.1/socaity_cli.egg-info/SOURCES.txt +33 -0
  29. socaity_cli-0.1.1/socaity_cli.egg-info/dependency_links.txt +1 -0
  30. socaity_cli-0.1.1/socaity_cli.egg-info/entry_points.txt +2 -0
  31. socaity_cli-0.1.1/socaity_cli.egg-info/requires.txt +7 -0
  32. socaity_cli-0.1.1/socaity_cli.egg-info/top_level.txt +1 -0
  33. socaity_cli-0.1.1/test/test_cli.py +116 -0
  34. socaity_cli-0.1.1/test/test_credentials.py +39 -0
  35. socaity_cli-0.1.1/test/test_private_slot_error.py +47 -0
@@ -0,0 +1,73 @@
1
+ Metadata-Version: 2.4
2
+ Name: socaity-cli
3
+ Version: 0.1.1
4
+ Summary: Socaity platform CLI: login, catalog browsing, SDK installs and APIPod deployment commands.
5
+ Author: SocAIty
6
+ License: MIT
7
+ Project-URL: Repository, https://github.com/SocAIty/socaity-cli
8
+ Project-URL: Homepage, https://www.socaity.ai
9
+ Requires-Python: >=3.8
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: httpx
12
+ Requires-Dist: socaity-schemas>=0.0.1
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest; extra == "dev"
15
+ Requires-Dist: ruff; extra == "dev"
16
+ Requires-Dist: mypy; extra == "dev"
17
+
18
+ # socaity-cli
19
+
20
+ The command line for the [socaity.ai](https://www.socaity.ai) platform: login, catalog browsing,
21
+ SDK installs, and APIPod deployment tooling. One `socaity` command, shared by the
22
+ [socaity SDK](https://github.com/SocAIty/socaity) and [APIPod](https://github.com/SocAIty/apipod).
23
+
24
+ The package is deliberately small: `httpx` and `socaity-schemas` are the only dependencies.
25
+ Commands that need more (stub generation, container builds) delegate to the `socaity` or
26
+ `apipod` package and tell you what to install when it is missing.
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ pip install socaity-cli
32
+ ```
33
+
34
+ You get `socaity-cli` as part of `pip install socaity` or `pip install apipod` as well.
35
+
36
+ ## Commands
37
+
38
+ | Command | Needs | What it does |
39
+ |---------|-------|--------------|
40
+ | `socaity login` / `logout` / `whoami` | - | Browser login, credentials in `~/.config/socaity/` |
41
+ | `socaity list services\|models` | - | Browse the AI catalog |
42
+ | `socaity search "flux schnel"` | - | Typo-tolerant search (`--collections services,models,jobs`) |
43
+ | `socaity jobs [job_id]` | login | List/search jobs; `--q`, `--expand data,files,billing,social`, `--visibility` |
44
+ | `socaity projects [project_id]` | login | Your projects; `--expand jobs` embeds member jobs |
45
+ | `socaity install <service>` | `socaity` | Generate an SDK stub for a service |
46
+ | `socaity update` | `socaity` | Sync installed services with the platform |
47
+ | `socaity scan` | `apipod` | Scan a project and generate `apipod.json` |
48
+ | `socaity build [file]` | `apipod` | Build the deployment container |
49
+ | `socaity start [entrypoint]` | `apipod` | Run an APIPod service locally |
50
+ | `socaity deploy [target]` | `apipod` | Deploy to Socaity (wraps `apipod deploy`) |
51
+
52
+ Use `-C DIR` with scan/build/start/deploy to point at a service project directory
53
+ (e.g. `socaity -C simple_test_service deploy serverless-runpod --yes`).
54
+
55
+ ## Configuration
56
+
57
+ | Env var | Default | Purpose |
58
+ |---------|---------|---------|
59
+ | `SOCAITY_API_KEY` | from login | API key (wins over stored credentials) |
60
+ | `SOCAITY_BACKEND_URL` | `https://webapi.socaity.ai` | Platform API |
61
+ | `SOCAITY_FRONTEND_URL` | `https://socaity.ai` | Web UI for browser login |
62
+
63
+ ## Using it as a library
64
+
65
+ `socaity` and `apipod` build on the same primitives this package exposes:
66
+
67
+ ```python
68
+ from socaity_cli import SocaityBackendClient, get_api_key, requires
69
+ ```
70
+
71
+ `SocaityBackendClient` covers catalog reads, search, and SDK install/update calls.
72
+ `requires("apipod")` guards a function behind an optional package with a friendly
73
+ install hint instead of an ImportError traceback.
@@ -0,0 +1,56 @@
1
+ # socaity-cli
2
+
3
+ The command line for the [socaity.ai](https://www.socaity.ai) platform: login, catalog browsing,
4
+ SDK installs, and APIPod deployment tooling. One `socaity` command, shared by the
5
+ [socaity SDK](https://github.com/SocAIty/socaity) and [APIPod](https://github.com/SocAIty/apipod).
6
+
7
+ The package is deliberately small: `httpx` and `socaity-schemas` are the only dependencies.
8
+ Commands that need more (stub generation, container builds) delegate to the `socaity` or
9
+ `apipod` package and tell you what to install when it is missing.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ pip install socaity-cli
15
+ ```
16
+
17
+ You get `socaity-cli` as part of `pip install socaity` or `pip install apipod` as well.
18
+
19
+ ## Commands
20
+
21
+ | Command | Needs | What it does |
22
+ |---------|-------|--------------|
23
+ | `socaity login` / `logout` / `whoami` | - | Browser login, credentials in `~/.config/socaity/` |
24
+ | `socaity list services\|models` | - | Browse the AI catalog |
25
+ | `socaity search "flux schnel"` | - | Typo-tolerant search (`--collections services,models,jobs`) |
26
+ | `socaity jobs [job_id]` | login | List/search jobs; `--q`, `--expand data,files,billing,social`, `--visibility` |
27
+ | `socaity projects [project_id]` | login | Your projects; `--expand jobs` embeds member jobs |
28
+ | `socaity install <service>` | `socaity` | Generate an SDK stub for a service |
29
+ | `socaity update` | `socaity` | Sync installed services with the platform |
30
+ | `socaity scan` | `apipod` | Scan a project and generate `apipod.json` |
31
+ | `socaity build [file]` | `apipod` | Build the deployment container |
32
+ | `socaity start [entrypoint]` | `apipod` | Run an APIPod service locally |
33
+ | `socaity deploy [target]` | `apipod` | Deploy to Socaity (wraps `apipod deploy`) |
34
+
35
+ Use `-C DIR` with scan/build/start/deploy to point at a service project directory
36
+ (e.g. `socaity -C simple_test_service deploy serverless-runpod --yes`).
37
+
38
+ ## Configuration
39
+
40
+ | Env var | Default | Purpose |
41
+ |---------|---------|---------|
42
+ | `SOCAITY_API_KEY` | from login | API key (wins over stored credentials) |
43
+ | `SOCAITY_BACKEND_URL` | `https://webapi.socaity.ai` | Platform API |
44
+ | `SOCAITY_FRONTEND_URL` | `https://socaity.ai` | Web UI for browser login |
45
+
46
+ ## Using it as a library
47
+
48
+ `socaity` and `apipod` build on the same primitives this package exposes:
49
+
50
+ ```python
51
+ from socaity_cli import SocaityBackendClient, get_api_key, requires
52
+ ```
53
+
54
+ `SocaityBackendClient` covers catalog reads, search, and SDK install/update calls.
55
+ `requires("apipod")` guards a function behind an optional package with a friendly
56
+ install hint instead of an ImportError traceback.
@@ -0,0 +1,52 @@
1
+ [build-system]
2
+ requires = ["setuptools>=42", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "socaity-cli"
7
+ version = "0.1.1"
8
+ description = "Socaity platform CLI: login, catalog browsing, SDK installs and APIPod deployment commands."
9
+ requires-python = ">=3.8"
10
+ authors = [
11
+ { name = "SocAIty" }
12
+ ]
13
+ license = { text = "MIT" }
14
+ readme = "README.md"
15
+ dependencies = [
16
+ "httpx",
17
+ "socaity-schemas>=0.0.1",
18
+ ]
19
+
20
+ [project.scripts]
21
+ socaity = "socaity_cli.cli:main"
22
+
23
+ [project.optional-dependencies]
24
+ dev = [
25
+ "pytest",
26
+ "ruff",
27
+ "mypy",
28
+ ]
29
+
30
+ [project.urls]
31
+ Repository = "https://github.com/SocAIty/socaity-cli"
32
+ Homepage = "https://www.socaity.ai"
33
+
34
+ [tool.setuptools.packages.find]
35
+ include = ["socaity_cli*"]
36
+
37
+ [tool.pytest.ini_options]
38
+ testpaths = ["test"]
39
+ # Resolve in-repo package and sibling schemas in local dev (CI installs from pip).
40
+ pythonpath = [".", "../socaity-schemas"]
41
+
42
+ [tool.ruff]
43
+ line-length = 120
44
+ exclude = ["venv", "dist", "build", "*.egg-info"]
45
+
46
+ [tool.ruff.lint]
47
+ ignore = ["E203", "E261", "E501", "W293", "W291"]
48
+
49
+ [tool.mypy]
50
+ ignore_missing_imports = true
51
+ follow_imports = "silent"
52
+ warn_unused_ignores = false
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,30 @@
1
+ """Socaity platform CLI.
2
+
3
+ Lightweight bundle of all Socaity CLI commands and the backend HTTP client
4
+ (httpx + socaity-schemas only). Heavy features delegate to the optional
5
+ ``socaity`` (SDK installs) and ``apipod`` (deployment tooling) packages via
6
+ the ``requires`` decorator.
7
+ """
8
+ from socaity_cli.backend_client import SocaityBackendClient
9
+ from socaity_cli.credentials import (
10
+ Credentials,
11
+ clear_credentials,
12
+ get_api_key,
13
+ load_credentials,
14
+ require_credentials,
15
+ save_credentials,
16
+ )
17
+ from socaity_cli.requires import requires
18
+ from socaity_cli.prompts import input_yes_no
19
+
20
+ __all__ = [
21
+ "SocaityBackendClient",
22
+ "Credentials",
23
+ "clear_credentials",
24
+ "get_api_key",
25
+ "load_credentials",
26
+ "require_credentials",
27
+ "save_credentials",
28
+ "requires",
29
+ "input_yes_no",
30
+ ]
@@ -0,0 +1,4 @@
1
+ from socaity_cli.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -0,0 +1,82 @@
1
+ """Bridge to the APIPod CLI (requires ``pip install apipod``).
2
+
3
+ Imports stay inside the functions: ``apipod`` is optional for the CLI and the
4
+ ``requires`` decorator guards each entry with a friendly install hint.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import sys
10
+ from typing import List, Optional
11
+
12
+ from socaity_cli.requires import requires
13
+
14
+
15
+ def _with_project_dir(argv: List[str], project_dir: Optional[str]) -> List[str]:
16
+ if project_dir:
17
+ return ["-C", project_dir, *argv]
18
+ return argv
19
+
20
+
21
+ @requires("apipod")
22
+ def _run_apipod_argv(argv: List[str]) -> None:
23
+ import apipod.cli
24
+
25
+ previous = sys.argv
26
+ sys.argv = ["apipod", *argv]
27
+ try:
28
+ apipod.cli.main()
29
+ finally:
30
+ sys.argv = previous
31
+
32
+
33
+ def run_scan(*, project_dir: Optional[str] = None) -> None:
34
+ _run_apipod_argv(_with_project_dir(["scan"], project_dir))
35
+
36
+
37
+ def run_build(target_file: Optional[str], *, project_dir: Optional[str] = None) -> None:
38
+ argv = ["build"]
39
+ if target_file:
40
+ argv.append(target_file)
41
+ _run_apipod_argv(_with_project_dir(argv, project_dir))
42
+
43
+
44
+ def run_start(
45
+ *,
46
+ entrypoint: Optional[str],
47
+ host: Optional[str],
48
+ port: Optional[int],
49
+ project_dir: Optional[str] = None,
50
+ ) -> None:
51
+ argv = ["start"]
52
+ if entrypoint:
53
+ argv.append(entrypoint)
54
+ if host:
55
+ argv.extend(["--host", host])
56
+ if port is not None:
57
+ argv.extend(["--port", str(port)])
58
+ _run_apipod_argv(_with_project_dir(argv, project_dir))
59
+
60
+
61
+ def run_deploy(
62
+ *,
63
+ target: Optional[str] = None,
64
+ project_dir: Optional[str] = None,
65
+ resume: Optional[str] = None,
66
+ skip_build: bool = False,
67
+ push_only: bool = False,
68
+ yes: bool = False,
69
+ ) -> None:
70
+ """Wrap ``apipod deploy`` (analyze, build, push, provision)."""
71
+ argv = ["deploy"]
72
+ if target:
73
+ argv.append(target)
74
+ if resume:
75
+ argv.extend(["--resume", resume])
76
+ if skip_build:
77
+ argv.append("--skip-build")
78
+ if push_only:
79
+ argv.append("--push-only")
80
+ if yes:
81
+ argv.append("--yes")
82
+ _run_apipod_argv(_with_project_dir(argv, project_dir))
@@ -0,0 +1,197 @@
1
+ """Browser-based CLI login (device authorization flow via web UI)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import shutil
7
+ import subprocess
8
+ import time
9
+ import webbrowser
10
+ from typing import Any, Dict, Optional
11
+ from urllib.parse import urljoin
12
+
13
+ import httpx
14
+
15
+ from socaity_cli.credentials import (
16
+ Credentials,
17
+ DEFAULT_BACKEND_URL,
18
+ DEFAULT_FRONTEND_URL,
19
+ require_credentials,
20
+ save_credentials,
21
+ )
22
+
23
+ USER_AGENT = "socaity-cli/0.1.0"
24
+
25
+
26
+ def _backend_base_url() -> str:
27
+ return os.environ.get("SOCAITY_BACKEND_URL", DEFAULT_BACKEND_URL).rstrip("/")
28
+
29
+
30
+ def _frontend_base_url(frontend_url: Optional[str] = None) -> str:
31
+ if frontend_url is not None:
32
+ value = frontend_url.strip()
33
+ return value.rstrip("/") if value else DEFAULT_FRONTEND_URL
34
+ return os.environ.get("SOCAITY_FRONTEND_URL", DEFAULT_FRONTEND_URL).rstrip("/")
35
+
36
+
37
+ def _web_api_url(base: str) -> str:
38
+ return urljoin(base + "/", "v1/")
39
+
40
+
41
+ def open_login_url(url: str) -> bool:
42
+ try:
43
+ if webbrowser.open(url):
44
+ return True
45
+ except Exception:
46
+ pass
47
+ for cmd in ("wslview", "xdg-open"):
48
+ exe = shutil.which(cmd)
49
+ if not exe:
50
+ continue
51
+ try:
52
+ subprocess.run(
53
+ [exe, url],
54
+ check=False,
55
+ timeout=8,
56
+ stdout=subprocess.DEVNULL,
57
+ stderr=subprocess.DEVNULL,
58
+ )
59
+ return True
60
+ except Exception:
61
+ continue
62
+ return False
63
+
64
+
65
+ def start_cli_login(
66
+ *,
67
+ backend_url: Optional[str] = None,
68
+ frontend_url: Optional[str] = None,
69
+ expires_in_seconds: int = 600,
70
+ ) -> Dict[str, Any]:
71
+ base = (backend_url or _backend_base_url()).rstrip("/")
72
+ payload: Dict[str, Any] = {"expires_in_seconds": expires_in_seconds}
73
+ payload["frontend_base_url"] = _frontend_base_url(frontend_url)
74
+ with httpx.Client(timeout=20.0) as client:
75
+ response = client.post(
76
+ _web_api_url(base) + "cli-auth/start",
77
+ json=payload,
78
+ headers={"User-Agent": USER_AGENT},
79
+ )
80
+ response.raise_for_status()
81
+ return response.json()
82
+
83
+
84
+ def poll_cli_login(
85
+ *,
86
+ request_id: str,
87
+ poll_code: str,
88
+ backend_url: Optional[str] = None,
89
+ ) -> Dict[str, Any]:
90
+ base = (backend_url or _backend_base_url()).rstrip("/")
91
+ with httpx.Client(timeout=20.0) as client:
92
+ response = client.post(
93
+ _web_api_url(base) + "cli-auth/poll",
94
+ json={"request_id": request_id, "poll_code": poll_code},
95
+ headers={"User-Agent": USER_AGENT},
96
+ )
97
+ response.raise_for_status()
98
+ return response.json()
99
+
100
+
101
+ def fetch_profile(*, api_key: str, backend_url: str) -> Dict[str, Any]:
102
+ base = backend_url.rstrip("/")
103
+ with httpx.Client(timeout=20.0) as client:
104
+ response = client.get(
105
+ _web_api_url(base) + "profile/profile",
106
+ headers={
107
+ "Authorization": f"Bearer {api_key}",
108
+ "User-Agent": USER_AGENT,
109
+ },
110
+ )
111
+ response.raise_for_status()
112
+ return response.json()
113
+
114
+
115
+ def run_login(
116
+ *,
117
+ no_browser: bool = False,
118
+ timeout: int = 300,
119
+ backend_url: Optional[str] = None,
120
+ frontend_url: Optional[str] = None,
121
+ ) -> Credentials:
122
+ base = (backend_url or _backend_base_url()).rstrip("/")
123
+
124
+ print("Login to Socaity")
125
+ session = start_cli_login(
126
+ backend_url=base,
127
+ frontend_url=frontend_url,
128
+ )
129
+ verification_uri = session["verification_uri"]
130
+ request_id = session["request_id"]
131
+ poll_code = session["poll_code"]
132
+ interval = max(int(session.get("interval_seconds", 2)), 1)
133
+
134
+ print(f"Open this URL to authenticate:\n{verification_uri}")
135
+
136
+ if not no_browser:
137
+ if open_login_url(verification_uri):
138
+ print("Browser opened for login.")
139
+ else:
140
+ print("Could not open a browser automatically. Open the URL manually.")
141
+
142
+ print("Waiting for login confirmation...")
143
+ deadline = time.time() + max(timeout, 10)
144
+
145
+ while time.time() < deadline:
146
+ poll_response = poll_cli_login(
147
+ request_id=request_id,
148
+ poll_code=poll_code,
149
+ backend_url=base,
150
+ )
151
+ status = poll_response.get("status")
152
+
153
+ if status == "pending":
154
+ time.sleep(interval)
155
+ continue
156
+ if status == "expired":
157
+ print("Login request expired. Run: socaity login")
158
+ raise SystemExit(1)
159
+ if status == "approved":
160
+ api_key = (poll_response.get("api_key") or "").strip()
161
+ if not api_key:
162
+ print("Login failed: missing API key in response.")
163
+ raise SystemExit(1)
164
+ creds = Credentials(
165
+ api_key=api_key,
166
+ user_id=poll_response.get("user_id"),
167
+ user_email=poll_response.get("user_email"),
168
+ backend_url=base,
169
+ )
170
+ path = save_credentials(creds)
171
+ email = creds.user_email or "user"
172
+ print(f"Logged in as {email}")
173
+ print(f"Credentials saved to {path}")
174
+ return creds
175
+
176
+ print(f"Login failed: unexpected status '{status}'")
177
+ raise SystemExit(1)
178
+
179
+ print("Login timed out. Run: socaity login")
180
+ raise SystemExit(1)
181
+
182
+
183
+ def run_whoami() -> None:
184
+ creds = require_credentials()
185
+ email = creds.user_email
186
+ user_id = creds.user_id
187
+ if not email or not user_id:
188
+ try:
189
+ profile = fetch_profile(api_key=creds.api_key, backend_url=creds.backend_url)
190
+ email = email or profile.get("email") or profile.get("user_email")
191
+ user_id = user_id or profile.get("id") or profile.get("user_id")
192
+ except Exception as exc:
193
+ print(f"Could not fetch profile: {exc}")
194
+ raise SystemExit(1) from exc
195
+ print(f"user_id: {user_id}")
196
+ print(f"email: {email}")
197
+ print(f"backend: {creds.backend_url}")
@@ -0,0 +1,23 @@
1
+ """Composed platform backend client (catalog, jobs, projects, analytics, files, sdk, deployment)."""
2
+ from socaity_cli.clients.analytics import AnalyticsClient
3
+ from socaity_cli.clients.catalog import CatalogClient
4
+ from socaity_cli.clients.deployment import DeploymentClient
5
+ from socaity_cli.clients.files import FilesClient
6
+ from socaity_cli.clients.jobs import JobsClient
7
+ from socaity_cli.clients.projects import ProjectsClient
8
+ from socaity_cli.clients.sdk_install import SdkInstallClient
9
+ from socaity_cli.http import BackendHttp
10
+
11
+
12
+ class SocaityBackendClient(
13
+ BackendHttp,
14
+ CatalogClient,
15
+ JobsClient,
16
+ ProjectsClient,
17
+ AnalyticsClient,
18
+ FilesClient,
19
+ SdkInstallClient,
20
+ DeploymentClient,
21
+ ):
22
+ """Facade over domain mixins. Prefer importing a domain client when you only need one surface."""
23
+ pass