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.
@@ -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.
@@ -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,6 @@
1
+ include README.md
2
+ include LICENSE
3
+ include CHANGELOG.md
4
+ include PUBLISHING.md
5
+ recursive-include tests *.py
6
+ global-exclude __pycache__ *.py[cod] .DS_Store
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """LMGram command line interface."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,9 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+
5
+ from .cli import main
6
+
7
+
8
+ if __name__ == "__main__":
9
+ sys.exit(main())
@@ -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