cite-agent 1.0.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.

Potentially problematic release.


This version of cite-agent might be problematic. Click here for more details.

@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Nocturnal Archive
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,16 @@
1
+ include README.md
2
+ include LICENSE
3
+ include requirements.txt
4
+ include requirements-dev.txt
5
+ recursive-include docs *.md
6
+ recursive-include nocturnal_archive *.py *.txt *.md *.json
7
+ recursive-include tools *.py
8
+ recursive-include tests *.py
9
+ prune .github
10
+ prune .claude
11
+ prune .venv
12
+ exclude .env*
13
+ exclude *.log
14
+ exclude __pycache__
15
+ exclude *.pyc
16
+ exclude .DS_Store
@@ -0,0 +1,234 @@
1
+ Metadata-Version: 2.4
2
+ Name: cite-agent
3
+ Version: 1.0.0
4
+ Summary: AI Research Assistant - Backend-Only Distribution
5
+ Home-page: https://github.com/Spectating101/cite-agent
6
+ Author: Cite-Agent Team
7
+ Author-email: contact@citeagent.dev
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Science/Research
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: requests>=2.31.0
19
+ Requires-Dist: aiohttp>=3.9.0
20
+ Requires-Dist: python-dotenv>=1.0.0
21
+ Requires-Dist: pydantic>=2.5.0
22
+ Requires-Dist: rich>=13.7.0
23
+ Requires-Dist: keyring>=24.3.0
24
+ Dynamic: author
25
+ Dynamic: author-email
26
+ Dynamic: classifier
27
+ Dynamic: description
28
+ Dynamic: description-content-type
29
+ Dynamic: home-page
30
+ Dynamic: license-file
31
+ Dynamic: requires-dist
32
+ Dynamic: requires-python
33
+ Dynamic: summary
34
+
35
+ # Cite-Agent
36
+
37
+ > A fast, affordable AI research assistant with built-in citation capabilities
38
+
39
+ **Cite-Agent** is a terminal-based AI assistant designed for research and finance, offering 70B model access at half the cost of comparable services.
40
+
41
+ ---
42
+
43
+ ## 🎯 What is Cite-Agent?
44
+
45
+ Cite-Agent is an AI-powered research tool that:
46
+ - Answers complex questions using state-of-the-art 70B language models
47
+ - Provides accurate, source-grounded responses (hence "cite")
48
+ - Executes code (Python, R, SQL) for data analysis
49
+ - Operates through a beautiful terminal interface
50
+ - Costs $10/month (vs $20+ for Claude/ChatGPT)
51
+
52
+ ---
53
+
54
+ ## ✨ Key Features
55
+
56
+ ### 🧠 **Powerful AI**
57
+ - Access to Llama 3.3 70B via Cerebras & Groq
58
+ - Multi-provider fallback for 99.9% uptime
59
+ - 50 queries/day (50,000 tokens)
60
+ - 2-5 second response times
61
+
62
+ ### 🎓 **Truth-Seeking**
63
+ - Explicitly designed to correct user errors
64
+ - Admits uncertainty instead of guessing
65
+ - Cites sources and reasoning
66
+ - Anti-appeasement prompts
67
+
68
+ ### 💻 **Code Execution**
69
+ - Python, R, and SQL support
70
+ - Data analysis and visualization
71
+ - Financial calculations
72
+ - Research automation
73
+
74
+ ### 🔒 **Secure & Private**
75
+ - API keys never leave the backend
76
+ - JWT-based authentication
77
+ - Rate limiting per user
78
+ - HTTPS-only communication
79
+
80
+ ### 📊 **Analytics**
81
+ - Track your usage
82
+ - Monitor token consumption
83
+ - View query history
84
+ - Download statistics
85
+
86
+ ---
87
+
88
+ ## 💰 Pricing
89
+
90
+ | Plan | Price | Queries/Day | Features |
91
+ |------|-------|-------------|----------|
92
+ | **Student** | $10/month (300 NTD) | 50 | Full access |
93
+ | **Public** | $10/month (400 NTD) | 50 | Full access |
94
+
95
+ **Beta**: First 50 users get 3 months free
96
+
97
+ ---
98
+
99
+ ## 🚀 Quick Start
100
+
101
+ ### For Users
102
+
103
+ 1. **Download** for your OS:
104
+ - [Windows](https://cite-agent-api.herokuapp.com/api/downloads/windows)
105
+ - [macOS](https://cite-agent-api.herokuapp.com/api/downloads/macos)
106
+ - [Linux](https://cite-agent-api.herokuapp.com/api/downloads/linux)
107
+
108
+ 2. **Install** the downloaded package
109
+
110
+ 3. **Run** `cite-agent` in your terminal
111
+
112
+ 4. **Register** with email + password
113
+
114
+ 5. **Start asking questions!**
115
+
116
+ ### For Developers
117
+
118
+ See [DEVELOPMENT.md](./docs/DEVELOPMENT.md) for setup instructions.
119
+
120
+ ---
121
+
122
+ ## 🏗️ Architecture
123
+
124
+ ```
125
+ User's Machine Backend (Heroku) AI Providers
126
+ ┌─────────────┐ ┌──────────────┐ ┌──────────┐
127
+ │ │ │ │ │ │
128
+ │ Terminal │─────────────▶│ FastAPI │─────────────▶│ Cerebras │
129
+ │ UI │ │ API │ │ (70B) │
130
+ │ │◀─────────────│ │◀─────────────│ │
131
+ │ (cite_ │ │ - Auth │ └──────────┘
132
+ │ agent/) │ │ - Rate │
133
+ │ │ │ Limiting │ ┌──────────┐
134
+ └─────────────┘ │ - Analytics │ │ │
135
+ │ - Proxy │─────────────▶│ Groq │
136
+ │ │ │ (70B) │
137
+ └──────────────┘ │ │
138
+ │ └──────────┘
139
+ ┌──────────────┐
140
+ │ PostgreSQL │
141
+ │ Database │
142
+ └──────────────┘
143
+ ```
144
+
145
+ ### Components
146
+
147
+ **Frontend (cite_agent/)**
148
+ - Terminal UI using `rich` library
149
+ - JWT authentication
150
+ - Query management
151
+ - Local session storage
152
+
153
+ **Backend (cite-agent-api/)**
154
+ - FastAPI REST API
155
+ - User authentication & rate limiting
156
+ - Multi-provider LLM routing
157
+ - Analytics & tracking
158
+ - PostgreSQL database
159
+
160
+ **AI Providers**
161
+ - **Primary**: Cerebras (14,400 RPD × 3 keys = 43,200/day)
162
+ - **Backup**: Groq (1,000 RPD × 3 keys = 3,000/day)
163
+ - **Total capacity**: ~46,000 queries/day
164
+
165
+ ---
166
+
167
+ ## 📖 Documentation
168
+
169
+ - **[DEVELOPMENT.md](./docs/DEVELOPMENT.md)** - Developer setup guide
170
+ - **[DEPLOYMENT.md](./DEPLOYMENT.md)** - How to deploy (Heroku)
171
+ - **[API_REFERENCE.md](./docs/API_REFERENCE.md)** - API endpoints
172
+ - **[ARCHITECTURE.md](./ARCHITECTURE.md)** - System design details
173
+ - **[ROADMAP.md](./ROADMAP.md)** - Future plans
174
+
175
+ ---
176
+
177
+ ## 🛠️ Tech Stack
178
+
179
+ **Frontend**
180
+ - Python 3.13+
181
+ - `rich` for terminal UI
182
+ - `httpx` for async HTTP
183
+ - `pydantic` for data validation
184
+
185
+ **Backend**
186
+ - FastAPI
187
+ - PostgreSQL
188
+ - JWT authentication
189
+ - Multi-provider LLM integration
190
+
191
+ **AI Providers**
192
+ - Cerebras Inference API
193
+ - Groq Cloud
194
+ - Cloudflare Workers AI (fallback)
195
+ - OpenRouter (fallback)
196
+
197
+ **Deployment**
198
+ - Heroku (backend + database)
199
+ - GitHub Releases (installers)
200
+
201
+ ---
202
+
203
+ ## 🤝 Contributing
204
+
205
+ We're not accepting contributions yet (private beta), but stay tuned!
206
+
207
+ ---
208
+
209
+ ## 📜 License
210
+
211
+ Proprietary - All rights reserved
212
+
213
+ ---
214
+
215
+ ## 📧 Support
216
+
217
+ - **Email**: s1133958@mail.yzu.edu.tw
218
+ - **Issues**: Coming soon
219
+ - **Documentation**: See `/docs` directory
220
+
221
+ ---
222
+
223
+ ## 🙏 Acknowledgments
224
+
225
+ - **Cerebras** for generous free tier (14,400 RPD)
226
+ - **Groq** for fast 70B inference
227
+ - **GitHub Student Pack** for free hosting credits
228
+ - **FastAPI** for excellent async framework
229
+
230
+ ---
231
+
232
+ **Built with ❤️ for researchers and analysts**
233
+
234
+ *Cite-Agent - Because accuracy matters more than agreeableness*
@@ -0,0 +1,200 @@
1
+ # Cite-Agent
2
+
3
+ > A fast, affordable AI research assistant with built-in citation capabilities
4
+
5
+ **Cite-Agent** is a terminal-based AI assistant designed for research and finance, offering 70B model access at half the cost of comparable services.
6
+
7
+ ---
8
+
9
+ ## 🎯 What is Cite-Agent?
10
+
11
+ Cite-Agent is an AI-powered research tool that:
12
+ - Answers complex questions using state-of-the-art 70B language models
13
+ - Provides accurate, source-grounded responses (hence "cite")
14
+ - Executes code (Python, R, SQL) for data analysis
15
+ - Operates through a beautiful terminal interface
16
+ - Costs $10/month (vs $20+ for Claude/ChatGPT)
17
+
18
+ ---
19
+
20
+ ## ✨ Key Features
21
+
22
+ ### 🧠 **Powerful AI**
23
+ - Access to Llama 3.3 70B via Cerebras & Groq
24
+ - Multi-provider fallback for 99.9% uptime
25
+ - 50 queries/day (50,000 tokens)
26
+ - 2-5 second response times
27
+
28
+ ### 🎓 **Truth-Seeking**
29
+ - Explicitly designed to correct user errors
30
+ - Admits uncertainty instead of guessing
31
+ - Cites sources and reasoning
32
+ - Anti-appeasement prompts
33
+
34
+ ### 💻 **Code Execution**
35
+ - Python, R, and SQL support
36
+ - Data analysis and visualization
37
+ - Financial calculations
38
+ - Research automation
39
+
40
+ ### 🔒 **Secure & Private**
41
+ - API keys never leave the backend
42
+ - JWT-based authentication
43
+ - Rate limiting per user
44
+ - HTTPS-only communication
45
+
46
+ ### 📊 **Analytics**
47
+ - Track your usage
48
+ - Monitor token consumption
49
+ - View query history
50
+ - Download statistics
51
+
52
+ ---
53
+
54
+ ## 💰 Pricing
55
+
56
+ | Plan | Price | Queries/Day | Features |
57
+ |------|-------|-------------|----------|
58
+ | **Student** | $10/month (300 NTD) | 50 | Full access |
59
+ | **Public** | $10/month (400 NTD) | 50 | Full access |
60
+
61
+ **Beta**: First 50 users get 3 months free
62
+
63
+ ---
64
+
65
+ ## 🚀 Quick Start
66
+
67
+ ### For Users
68
+
69
+ 1. **Download** for your OS:
70
+ - [Windows](https://cite-agent-api.herokuapp.com/api/downloads/windows)
71
+ - [macOS](https://cite-agent-api.herokuapp.com/api/downloads/macos)
72
+ - [Linux](https://cite-agent-api.herokuapp.com/api/downloads/linux)
73
+
74
+ 2. **Install** the downloaded package
75
+
76
+ 3. **Run** `cite-agent` in your terminal
77
+
78
+ 4. **Register** with email + password
79
+
80
+ 5. **Start asking questions!**
81
+
82
+ ### For Developers
83
+
84
+ See [DEVELOPMENT.md](./docs/DEVELOPMENT.md) for setup instructions.
85
+
86
+ ---
87
+
88
+ ## 🏗️ Architecture
89
+
90
+ ```
91
+ User's Machine Backend (Heroku) AI Providers
92
+ ┌─────────────┐ ┌──────────────┐ ┌──────────┐
93
+ │ │ │ │ │ │
94
+ │ Terminal │─────────────▶│ FastAPI │─────────────▶│ Cerebras │
95
+ │ UI │ │ API │ │ (70B) │
96
+ │ │◀─────────────│ │◀─────────────│ │
97
+ │ (cite_ │ │ - Auth │ └──────────┘
98
+ │ agent/) │ │ - Rate │
99
+ │ │ │ Limiting │ ┌──────────┐
100
+ └─────────────┘ │ - Analytics │ │ │
101
+ │ - Proxy │─────────────▶│ Groq │
102
+ │ │ │ (70B) │
103
+ └──────────────┘ │ │
104
+ │ └──────────┘
105
+ ┌──────────────┐
106
+ │ PostgreSQL │
107
+ │ Database │
108
+ └──────────────┘
109
+ ```
110
+
111
+ ### Components
112
+
113
+ **Frontend (cite_agent/)**
114
+ - Terminal UI using `rich` library
115
+ - JWT authentication
116
+ - Query management
117
+ - Local session storage
118
+
119
+ **Backend (cite-agent-api/)**
120
+ - FastAPI REST API
121
+ - User authentication & rate limiting
122
+ - Multi-provider LLM routing
123
+ - Analytics & tracking
124
+ - PostgreSQL database
125
+
126
+ **AI Providers**
127
+ - **Primary**: Cerebras (14,400 RPD × 3 keys = 43,200/day)
128
+ - **Backup**: Groq (1,000 RPD × 3 keys = 3,000/day)
129
+ - **Total capacity**: ~46,000 queries/day
130
+
131
+ ---
132
+
133
+ ## 📖 Documentation
134
+
135
+ - **[DEVELOPMENT.md](./docs/DEVELOPMENT.md)** - Developer setup guide
136
+ - **[DEPLOYMENT.md](./DEPLOYMENT.md)** - How to deploy (Heroku)
137
+ - **[API_REFERENCE.md](./docs/API_REFERENCE.md)** - API endpoints
138
+ - **[ARCHITECTURE.md](./ARCHITECTURE.md)** - System design details
139
+ - **[ROADMAP.md](./ROADMAP.md)** - Future plans
140
+
141
+ ---
142
+
143
+ ## 🛠️ Tech Stack
144
+
145
+ **Frontend**
146
+ - Python 3.13+
147
+ - `rich` for terminal UI
148
+ - `httpx` for async HTTP
149
+ - `pydantic` for data validation
150
+
151
+ **Backend**
152
+ - FastAPI
153
+ - PostgreSQL
154
+ - JWT authentication
155
+ - Multi-provider LLM integration
156
+
157
+ **AI Providers**
158
+ - Cerebras Inference API
159
+ - Groq Cloud
160
+ - Cloudflare Workers AI (fallback)
161
+ - OpenRouter (fallback)
162
+
163
+ **Deployment**
164
+ - Heroku (backend + database)
165
+ - GitHub Releases (installers)
166
+
167
+ ---
168
+
169
+ ## 🤝 Contributing
170
+
171
+ We're not accepting contributions yet (private beta), but stay tuned!
172
+
173
+ ---
174
+
175
+ ## 📜 License
176
+
177
+ Proprietary - All rights reserved
178
+
179
+ ---
180
+
181
+ ## 📧 Support
182
+
183
+ - **Email**: s1133958@mail.yzu.edu.tw
184
+ - **Issues**: Coming soon
185
+ - **Documentation**: See `/docs` directory
186
+
187
+ ---
188
+
189
+ ## 🙏 Acknowledgments
190
+
191
+ - **Cerebras** for generous free tier (14,400 RPD)
192
+ - **Groq** for fast 70B inference
193
+ - **GitHub Student Pack** for free hosting credits
194
+ - **FastAPI** for excellent async framework
195
+
196
+ ---
197
+
198
+ **Built with ❤️ for researchers and analysts**
199
+
200
+ *Cite-Agent - Because accuracy matters more than agreeableness*
@@ -0,0 +1,7 @@
1
+ """
2
+ This is a DISTRIBUTION build.
3
+ Local LLM calling code has been removed.
4
+ All queries must go through the centralized backend.
5
+ """
6
+ DISTRIBUTION_BUILD = True
7
+ BACKEND_ONLY = True
@@ -0,0 +1,66 @@
1
+ """
2
+ Nocturnal Archive - Beta Agent
3
+
4
+ A Groq-powered research and finance co-pilot with deterministic tooling and
5
+ prior stacks preserved only in Git history, kept out of the runtime footprint.
6
+ """
7
+
8
+ from .enhanced_ai_agent import EnhancedNocturnalAgent, ChatRequest, ChatResponse
9
+
10
+ __version__ = "0.9.0b1"
11
+ __author__ = "Nocturnal Archive Team"
12
+ __email__ = "contact@nocturnal.dev"
13
+
14
+ __all__ = [
15
+ "EnhancedNocturnalAgent",
16
+ "ChatRequest",
17
+ "ChatResponse"
18
+ ]
19
+
20
+ # Package metadata
21
+ PACKAGE_NAME = "nocturnal-archive"
22
+ PACKAGE_VERSION = __version__
23
+ PACKAGE_DESCRIPTION = "Beta CLI agent for finance + research workflows"
24
+ PACKAGE_URL = "https://github.com/Spectating101/nocturnal-archive"
25
+
26
+ def get_version():
27
+ """Get the package version"""
28
+ return __version__
29
+
30
+ def quick_start():
31
+ """Print quick start instructions"""
32
+ print("""
33
+ 🚀 Nocturnal Archive Quick Start
34
+ ================================
35
+
36
+ 1. Install the package and CLI:
37
+ pip install nocturnal-archive
38
+
39
+ 2. Configure your Groq key:
40
+ nocturnal --setup
41
+
42
+ 3. Ask a question:
43
+ nocturnal "Compare Apple and Microsoft net income this quarter"
44
+
45
+ 4. Prefer embedding in code? Minimal example:
46
+ ```python
47
+ import asyncio
48
+ from nocturnal_archive import EnhancedNocturnalAgent, ChatRequest
49
+
50
+ async def main():
51
+ agent = EnhancedNocturnalAgent()
52
+ await agent.initialize()
53
+
54
+ response = await agent.process_request(ChatRequest(question="List repo workspace files"))
55
+ print(response.response)
56
+
57
+ await agent.close()
58
+
59
+ asyncio.run(main())
60
+ ```
61
+
62
+ Full installation instructions live in docs/INSTALL.md.
63
+ """)
64
+
65
+ if __name__ == "__main__":
66
+ quick_start()
@@ -0,0 +1,130 @@
1
+ """Account provisioning utilities for the Nocturnal Archive CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import os
7
+ from dataclasses import dataclass
8
+ from typing import Any, Dict, Optional
9
+
10
+
11
+ class AccountProvisioningError(RuntimeError):
12
+ """Raised when account provisioning fails."""
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class AccountCredentials:
17
+ """User account credentials for backend authentication.
18
+
19
+ Production mode: User gets JWT tokens, NOT API keys.
20
+ Backend has the API keys, user just authenticates with JWT.
21
+ """
22
+ account_id: str
23
+ email: str
24
+ auth_token: str # JWT for backend authentication
25
+ refresh_token: str
26
+ telemetry_token: str
27
+ issued_at: Optional[str] = None
28
+
29
+ @classmethod
30
+ def from_payload(cls, email: str, payload: Dict[str, Any]) -> "AccountCredentials":
31
+ try:
32
+ return cls(
33
+ account_id=str(payload["accountId"]),
34
+ email=email,
35
+ auth_token=str(payload["authToken"]),
36
+ refresh_token=str(payload.get("refreshToken", "")),
37
+ telemetry_token=str(payload.get("telemetryToken", "")),
38
+ issued_at=str(payload.get("issuedAt", "")) or None,
39
+ )
40
+ except KeyError as exc: # pragma: no cover - defensive guard
41
+ raise AccountProvisioningError(
42
+ f"Account provisioning payload missing field: {exc!s}" # noqa: TRY200
43
+ ) from exc
44
+
45
+
46
+ class AccountClient:
47
+ """Minimal client for authenticating against the control plane.
48
+
49
+ If ``NOCTURNAL_CONTROL_PLANE_URL`` is unset the client falls back to an
50
+ offline deterministic token generator so local development and CI remain
51
+ hermetic.
52
+ """
53
+
54
+ def __init__(self, base_url: Optional[str] = None, timeout: int = 10):
55
+ self.base_url = (
56
+ base_url
57
+ or os.getenv("NOCTURNAL_CONTROL_PLANE_URL")
58
+ or "https://cite-agent-api-720dfadd602c.herokuapp.com"
59
+ )
60
+ self.timeout = timeout
61
+
62
+ def provision(self, email: str, password: str) -> AccountCredentials:
63
+ if self.base_url:
64
+ payload = self._request_credentials(email, password)
65
+ return AccountCredentials.from_payload(email=email, payload=payload)
66
+ return self._generate_offline_credentials(email, password)
67
+
68
+ # -- internal helpers -------------------------------------------------
69
+ def _request_credentials(self, email: str, password: str) -> Dict[str, Any]:
70
+ try: # pragma: no cover - requires network
71
+ import requests # type: ignore
72
+ except Exception as exc: # pragma: no cover - executed when requests missing
73
+ raise AccountProvisioningError(
74
+ "The 'requests' package is required for control-plane authentication"
75
+ ) from exc
76
+
77
+ endpoint = self.base_url.rstrip("/") + "/api/beta/login"
78
+ body = {"email": email, "password": password, "client": "cli"}
79
+ try:
80
+ response = requests.post(endpoint, json=body, timeout=self.timeout)
81
+ except Exception as exc: # pragma: no cover - network failure
82
+ raise AccountProvisioningError("Failed to reach control plane") from exc
83
+
84
+ if response.status_code >= 400:
85
+ detail = self._extract_error_detail(response)
86
+ raise AccountProvisioningError(
87
+ f"Authentication failed (status {response.status_code}): {detail}"
88
+ )
89
+
90
+ try:
91
+ payload = response.json()
92
+ except ValueError as exc: # pragma: no cover - invalid JSON
93
+ raise AccountProvisioningError("Control plane returned invalid JSON") from exc
94
+
95
+ if not isinstance(payload, dict): # pragma: no cover - sanity guard
96
+ raise AccountProvisioningError("Control plane response must be an object")
97
+ return payload
98
+
99
+ @staticmethod
100
+ def _extract_error_detail(response: Any) -> str:
101
+ try: # pragma: no cover - best effort decoding
102
+ data = response.json()
103
+ if isinstance(data, dict) and data.get("detail"):
104
+ return str(data["detail"])
105
+ except Exception:
106
+ pass
107
+ return response.text.strip() or "unknown error"
108
+
109
+ @staticmethod
110
+ def _generate_offline_credentials(email: str, password: str) -> AccountCredentials:
111
+ seed = f"{email.lower()}::{password}"
112
+ digest = hashlib.sha256(seed.encode("utf-8")).hexdigest()
113
+ auth_token = digest[12:44]
114
+ refresh_token = digest[44:]
115
+ telemetry_token = digest[24:56]
116
+ return AccountCredentials(
117
+ account_id=digest[:12],
118
+ email=email,
119
+ auth_token=auth_token,
120
+ refresh_token=refresh_token,
121
+ telemetry_token=telemetry_token,
122
+ issued_at=None,
123
+ )
124
+
125
+
126
+ __all__ = [
127
+ "AccountClient",
128
+ "AccountCredentials",
129
+ "AccountProvisioningError",
130
+ ]