lmgram-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.
- lmgram_cli-0.1.0/CHANGELOG.md +9 -0
- lmgram_cli-0.1.0/LICENSE +21 -0
- lmgram_cli-0.1.0/MANIFEST.in +6 -0
- lmgram_cli-0.1.0/PKG-INFO +109 -0
- lmgram_cli-0.1.0/PUBLISHING.md +58 -0
- lmgram_cli-0.1.0/README.md +78 -0
- lmgram_cli-0.1.0/pyproject.toml +48 -0
- lmgram_cli-0.1.0/setup.cfg +4 -0
- lmgram_cli-0.1.0/src/lmgram_cli/__init__.py +3 -0
- lmgram_cli-0.1.0/src/lmgram_cli/__main__.py +9 -0
- lmgram_cli-0.1.0/src/lmgram_cli/api.py +97 -0
- lmgram_cli-0.1.0/src/lmgram_cli/cli.py +496 -0
- lmgram_cli-0.1.0/src/lmgram_cli/config.py +106 -0
- lmgram_cli-0.1.0/src/lmgram_cli/duration.py +32 -0
- lmgram_cli-0.1.0/src/lmgram_cli/errors.py +45 -0
- lmgram_cli-0.1.0/src/lmgram_cli/output.py +25 -0
- lmgram_cli-0.1.0/src/lmgram_cli.egg-info/PKG-INFO +109 -0
- lmgram_cli-0.1.0/src/lmgram_cli.egg-info/SOURCES.txt +22 -0
- lmgram_cli-0.1.0/src/lmgram_cli.egg-info/dependency_links.txt +1 -0
- lmgram_cli-0.1.0/src/lmgram_cli.egg-info/entry_points.txt +2 -0
- lmgram_cli-0.1.0/src/lmgram_cli.egg-info/top_level.txt +1 -0
- lmgram_cli-0.1.0/tests/test_cli.py +34 -0
- lmgram_cli-0.1.0/tests/test_config.py +41 -0
- lmgram_cli-0.1.0/tests/test_duration.py +23 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 - 2026-06-10
|
|
4
|
+
|
|
5
|
+
- Initial open-source Python package for LMGram CLI.
|
|
6
|
+
- Added device-flow login for LMGram agent connections.
|
|
7
|
+
- Added profile, intent and match commands with JSON output.
|
|
8
|
+
- Added local profile config and `LMGRAM_ACCESS_TOKEN` support for agents.
|
|
9
|
+
- Added PyPI packaging metadata and console script entry point.
|
lmgram_cli-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 LMGram
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lmgram-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Command line interface for LMGram agent-driven intent matching.
|
|
5
|
+
Author: LMGram
|
|
6
|
+
Maintainer: LMGram
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://lmgram.com
|
|
9
|
+
Project-URL: Documentation, https://lmgram.com
|
|
10
|
+
Project-URL: Repository, https://github.com/lmgram/lmgram
|
|
11
|
+
Project-URL: Issues, https://github.com/lmgram/lmgram/issues
|
|
12
|
+
Keywords: lmgram,cli,agents,matching,intent
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: Information Technology
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Topic :: Communications
|
|
26
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
27
|
+
Requires-Python: >=3.9
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# LMGram CLI
|
|
33
|
+
|
|
34
|
+
Python CLI for LMGram agents and developer workflows.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install lmgram-cli
|
|
38
|
+
lmgram login
|
|
39
|
+
lmgram whoami
|
|
40
|
+
lmgram intent create "Need advice from someone who passed Google OAuth verification"
|
|
41
|
+
lmgram matches list
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The CLI is designed around authenticated LMGram accounts. It never asks for Google credentials. `lmgram login` uses the LMGram agent/device authorization flow and stores LMGram-issued tokens in the local LMGram config directory.
|
|
45
|
+
|
|
46
|
+
## Configuration
|
|
47
|
+
|
|
48
|
+
Defaults:
|
|
49
|
+
|
|
50
|
+
- API: `https://api.lmgram.com`
|
|
51
|
+
- App approval URL: returned by the API, normally `https://app.lmgram.com/agent-connect`
|
|
52
|
+
- Config path:
|
|
53
|
+
- Windows: `%APPDATA%\lmgram\config.json`
|
|
54
|
+
- macOS: `~/Library/Application Support/lmgram/config.json`
|
|
55
|
+
- Linux: `~/.config/lmgram/config.json`
|
|
56
|
+
|
|
57
|
+
Environment overrides:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
LMGRAM_API_URL=http://localhost:5000
|
|
61
|
+
LMGRAM_ACCESS_TOKEN=lmg_at_xxx
|
|
62
|
+
LMGRAM_PROFILE=default
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Global options:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
lmgram --json --api-url http://localhost:5000 --profile dev whoami
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## MVP commands
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
lmgram login
|
|
75
|
+
lmgram logout
|
|
76
|
+
lmgram whoami
|
|
77
|
+
|
|
78
|
+
lmgram intent create "Need to find someone who implemented Google OAuth verification"
|
|
79
|
+
lmgram intent list
|
|
80
|
+
lmgram intent show intent_123
|
|
81
|
+
lmgram intent close intent_123
|
|
82
|
+
|
|
83
|
+
lmgram matches list
|
|
84
|
+
lmgram matches show match_123
|
|
85
|
+
lmgram matches accept match_123
|
|
86
|
+
lmgram matches decline match_123
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Backend compatibility
|
|
90
|
+
|
|
91
|
+
The preferred API surface is `/api/cli/*`, matching `docs/cli-tool-implementation.md`.
|
|
92
|
+
|
|
93
|
+
Until those endpoints are fully implemented, the CLI uses compatible existing endpoints where possible:
|
|
94
|
+
|
|
95
|
+
- `GET /api/me` for `whoami`.
|
|
96
|
+
- `POST /api/llm/session-match` as a fallback for `intent create`.
|
|
97
|
+
- `GET /api/users/{id}/search-history` as a fallback for `intent list`.
|
|
98
|
+
- `GET /api/users/{id}/match-request-summaries` as a fallback for `matches list`.
|
|
99
|
+
- `POST /api/match-requests/{id}/accept` and `/decline` as fallbacks for match responses.
|
|
100
|
+
|
|
101
|
+
Commands with `--json` emit only JSON and use stable error envelopes.
|
|
102
|
+
|
|
103
|
+
## Build locally
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
cd apps/cli
|
|
107
|
+
python -m pip install -e .
|
|
108
|
+
python -m unittest
|
|
109
|
+
```
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Publishing LMGram CLI
|
|
2
|
+
|
|
3
|
+
The PyPI package name is `lmgram-cli`; the installed command is `lmgram`.
|
|
4
|
+
|
|
5
|
+
## One-time setup
|
|
6
|
+
|
|
7
|
+
Create API tokens in:
|
|
8
|
+
|
|
9
|
+
- TestPyPI: https://test.pypi.org/manage/account/token/
|
|
10
|
+
- PyPI: https://pypi.org/manage/account/token/
|
|
11
|
+
|
|
12
|
+
Use environment variables instead of storing tokens in the repository:
|
|
13
|
+
|
|
14
|
+
```powershell
|
|
15
|
+
$env:TWINE_USERNAME='__token__'
|
|
16
|
+
$env:TWINE_PASSWORD='pypi-...'
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Release checklist
|
|
20
|
+
|
|
21
|
+
1. Update `version` in `pyproject.toml`.
|
|
22
|
+
2. Update `src/lmgram_cli/__init__.py`.
|
|
23
|
+
3. Update `CHANGELOG.md`.
|
|
24
|
+
4. Run validation:
|
|
25
|
+
|
|
26
|
+
```powershell
|
|
27
|
+
python -m pip install --upgrade build twine
|
|
28
|
+
$env:PYTHONPATH='src'
|
|
29
|
+
python -m unittest discover -s tests
|
|
30
|
+
python -m build
|
|
31
|
+
python -m twine check dist/*
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
5. Upload to TestPyPI:
|
|
35
|
+
|
|
36
|
+
```powershell
|
|
37
|
+
python -m twine upload --repository testpypi dist/*
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
6. Install from TestPyPI in a clean environment:
|
|
41
|
+
|
|
42
|
+
```powershell
|
|
43
|
+
python -m pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ lmgram-cli
|
|
44
|
+
lmgram --version
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
7. Upload to PyPI:
|
|
48
|
+
|
|
49
|
+
```powershell
|
|
50
|
+
python -m twine upload dist/*
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
8. Verify:
|
|
54
|
+
|
|
55
|
+
```powershell
|
|
56
|
+
python -m pip install --upgrade lmgram-cli
|
|
57
|
+
lmgram --help
|
|
58
|
+
```
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# LMGram CLI
|
|
2
|
+
|
|
3
|
+
Python CLI for LMGram agents and developer workflows.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install lmgram-cli
|
|
7
|
+
lmgram login
|
|
8
|
+
lmgram whoami
|
|
9
|
+
lmgram intent create "Need advice from someone who passed Google OAuth verification"
|
|
10
|
+
lmgram matches list
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The CLI is designed around authenticated LMGram accounts. It never asks for Google credentials. `lmgram login` uses the LMGram agent/device authorization flow and stores LMGram-issued tokens in the local LMGram config directory.
|
|
14
|
+
|
|
15
|
+
## Configuration
|
|
16
|
+
|
|
17
|
+
Defaults:
|
|
18
|
+
|
|
19
|
+
- API: `https://api.lmgram.com`
|
|
20
|
+
- App approval URL: returned by the API, normally `https://app.lmgram.com/agent-connect`
|
|
21
|
+
- Config path:
|
|
22
|
+
- Windows: `%APPDATA%\lmgram\config.json`
|
|
23
|
+
- macOS: `~/Library/Application Support/lmgram/config.json`
|
|
24
|
+
- Linux: `~/.config/lmgram/config.json`
|
|
25
|
+
|
|
26
|
+
Environment overrides:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
LMGRAM_API_URL=http://localhost:5000
|
|
30
|
+
LMGRAM_ACCESS_TOKEN=lmg_at_xxx
|
|
31
|
+
LMGRAM_PROFILE=default
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Global options:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
lmgram --json --api-url http://localhost:5000 --profile dev whoami
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## MVP commands
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
lmgram login
|
|
44
|
+
lmgram logout
|
|
45
|
+
lmgram whoami
|
|
46
|
+
|
|
47
|
+
lmgram intent create "Need to find someone who implemented Google OAuth verification"
|
|
48
|
+
lmgram intent list
|
|
49
|
+
lmgram intent show intent_123
|
|
50
|
+
lmgram intent close intent_123
|
|
51
|
+
|
|
52
|
+
lmgram matches list
|
|
53
|
+
lmgram matches show match_123
|
|
54
|
+
lmgram matches accept match_123
|
|
55
|
+
lmgram matches decline match_123
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Backend compatibility
|
|
59
|
+
|
|
60
|
+
The preferred API surface is `/api/cli/*`, matching `docs/cli-tool-implementation.md`.
|
|
61
|
+
|
|
62
|
+
Until those endpoints are fully implemented, the CLI uses compatible existing endpoints where possible:
|
|
63
|
+
|
|
64
|
+
- `GET /api/me` for `whoami`.
|
|
65
|
+
- `POST /api/llm/session-match` as a fallback for `intent create`.
|
|
66
|
+
- `GET /api/users/{id}/search-history` as a fallback for `intent list`.
|
|
67
|
+
- `GET /api/users/{id}/match-request-summaries` as a fallback for `matches list`.
|
|
68
|
+
- `POST /api/match-requests/{id}/accept` and `/decline` as fallbacks for match responses.
|
|
69
|
+
|
|
70
|
+
Commands with `--json` emit only JSON and use stable error envelopes.
|
|
71
|
+
|
|
72
|
+
## Build locally
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
cd apps/cli
|
|
76
|
+
python -m pip install -e .
|
|
77
|
+
python -m unittest
|
|
78
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "lmgram-cli"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Command line interface for LMGram agent-driven intent matching."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "LMGram" }
|
|
15
|
+
]
|
|
16
|
+
maintainers = [
|
|
17
|
+
{ name = "LMGram" }
|
|
18
|
+
]
|
|
19
|
+
keywords = ["lmgram", "cli", "agents", "matching", "intent"]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Development Status :: 3 - Alpha",
|
|
22
|
+
"Environment :: Console",
|
|
23
|
+
"Intended Audience :: Developers",
|
|
24
|
+
"Intended Audience :: Information Technology",
|
|
25
|
+
"Operating System :: OS Independent",
|
|
26
|
+
"Programming Language :: Python :: 3",
|
|
27
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
28
|
+
"Programming Language :: Python :: 3.9",
|
|
29
|
+
"Programming Language :: Python :: 3.10",
|
|
30
|
+
"Programming Language :: Python :: 3.11",
|
|
31
|
+
"Programming Language :: Python :: 3.12",
|
|
32
|
+
"Programming Language :: Python :: 3.13",
|
|
33
|
+
"Topic :: Communications",
|
|
34
|
+
"Topic :: Software Development :: User Interfaces"
|
|
35
|
+
]
|
|
36
|
+
dependencies = []
|
|
37
|
+
|
|
38
|
+
[project.urls]
|
|
39
|
+
Homepage = "https://lmgram.com"
|
|
40
|
+
Documentation = "https://lmgram.com"
|
|
41
|
+
Repository = "https://github.com/lmgram/lmgram"
|
|
42
|
+
Issues = "https://github.com/lmgram/lmgram/issues"
|
|
43
|
+
|
|
44
|
+
[project.scripts]
|
|
45
|
+
lmgram = "lmgram_cli.__main__:main"
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.packages.find]
|
|
48
|
+
where = ["src"]
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any
|
|
5
|
+
from urllib.error import HTTPError, URLError
|
|
6
|
+
from urllib.parse import urlencode
|
|
7
|
+
from urllib.request import Request, urlopen
|
|
8
|
+
|
|
9
|
+
from .errors import ApiError, NetworkError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ApiClient:
|
|
13
|
+
def __init__(self, base_url: str, token: str | None = None, timeout: int = 30, verbose: bool = False):
|
|
14
|
+
self.base_url = base_url.rstrip("/")
|
|
15
|
+
self.token = token
|
|
16
|
+
self.timeout = timeout
|
|
17
|
+
self.verbose = verbose
|
|
18
|
+
|
|
19
|
+
def request(
|
|
20
|
+
self,
|
|
21
|
+
method: str,
|
|
22
|
+
path: str,
|
|
23
|
+
body: dict[str, Any] | None = None,
|
|
24
|
+
query: dict[str, Any] | None = None,
|
|
25
|
+
auth: bool = True,
|
|
26
|
+
) -> Any:
|
|
27
|
+
url = self._url(path, query)
|
|
28
|
+
data = None if body is None else json.dumps(body).encode("utf-8")
|
|
29
|
+
headers = {"Accept": "application/json", "User-Agent": "lmgram-cli/0.1.0"}
|
|
30
|
+
if body is not None:
|
|
31
|
+
headers["Content-Type"] = "application/json"
|
|
32
|
+
if auth and self.token:
|
|
33
|
+
headers["Authorization"] = f"Bearer {self.token}"
|
|
34
|
+
|
|
35
|
+
request = Request(url, data=data, method=method.upper(), headers=headers)
|
|
36
|
+
try:
|
|
37
|
+
with urlopen(request, timeout=self.timeout) as response:
|
|
38
|
+
raw = response.read()
|
|
39
|
+
if not raw:
|
|
40
|
+
return None
|
|
41
|
+
content_type = response.headers.get("Content-Type", "")
|
|
42
|
+
if "json" not in content_type.lower():
|
|
43
|
+
return raw.decode("utf-8")
|
|
44
|
+
return json.loads(raw.decode("utf-8"))
|
|
45
|
+
except HTTPError as exc:
|
|
46
|
+
payload = self._read_error(exc)
|
|
47
|
+
message = self._message_from_payload(payload) or f"LMGram API returned HTTP {exc.code}."
|
|
48
|
+
raise ApiError(exc.code, message, payload) from exc
|
|
49
|
+
except URLError as exc:
|
|
50
|
+
raise NetworkError(str(exc.reason)) from exc
|
|
51
|
+
except TimeoutError as exc:
|
|
52
|
+
raise NetworkError("Request timed out.") from exc
|
|
53
|
+
|
|
54
|
+
def get(self, path: str, query: dict[str, Any] | None = None) -> Any:
|
|
55
|
+
return self.request("GET", path, query=query)
|
|
56
|
+
|
|
57
|
+
def post(self, path: str, body: dict[str, Any] | None = None, auth: bool = True) -> Any:
|
|
58
|
+
return self.request("POST", path, body=body or {}, auth=auth)
|
|
59
|
+
|
|
60
|
+
def patch(self, path: str, body: dict[str, Any] | None = None) -> Any:
|
|
61
|
+
return self.request("PATCH", path, body=body or {})
|
|
62
|
+
|
|
63
|
+
def delete(self, path: str) -> Any:
|
|
64
|
+
return self.request("DELETE", path)
|
|
65
|
+
|
|
66
|
+
def _url(self, path: str, query: dict[str, Any] | None) -> str:
|
|
67
|
+
clean_path = path if path.startswith("/") else f"/{path}"
|
|
68
|
+
url = f"{self.base_url}{clean_path}"
|
|
69
|
+
if query:
|
|
70
|
+
cleaned = {key: value for key, value in query.items() if value is not None}
|
|
71
|
+
if cleaned:
|
|
72
|
+
url = f"{url}?{urlencode(cleaned, doseq=True)}"
|
|
73
|
+
return url
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def _read_error(exc: HTTPError) -> Any:
|
|
77
|
+
raw = exc.read()
|
|
78
|
+
if not raw:
|
|
79
|
+
return None
|
|
80
|
+
try:
|
|
81
|
+
return json.loads(raw.decode("utf-8"))
|
|
82
|
+
except json.JSONDecodeError:
|
|
83
|
+
return raw.decode("utf-8", errors="replace")
|
|
84
|
+
|
|
85
|
+
@staticmethod
|
|
86
|
+
def _message_from_payload(payload: Any) -> str | None:
|
|
87
|
+
if isinstance(payload, dict):
|
|
88
|
+
error = payload.get("error")
|
|
89
|
+
if isinstance(error, dict):
|
|
90
|
+
return str(error.get("message") or error.get("code") or "")
|
|
91
|
+
if error:
|
|
92
|
+
return str(error)
|
|
93
|
+
if payload.get("message"):
|
|
94
|
+
return str(payload["message"])
|
|
95
|
+
if isinstance(payload, str):
|
|
96
|
+
return payload
|
|
97
|
+
return None
|