malibupoint 0.2.0__py3-none-any.whl

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,82 @@
1
+ Metadata-Version: 2.4
2
+ Name: malibupoint
3
+ Version: 0.2.0
4
+ Summary: J.A.R.V.I.S. Terminal Client — Stark Secure Server access from any terminal
5
+ Author: Stark Industries
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/carterhamm/JustARatherVeryIntelligentSystem
8
+ Keywords: jarvis,ai,assistant,cli,terminal,stark
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: End Users/Desktop
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Communications :: Chat
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: websockets>=12.0
22
+ Requires-Dist: httpx>=0.25.0
23
+ Requires-Dist: cryptography>=41.0
24
+ Requires-Dist: prompt-toolkit>=3.0
25
+ Requires-Dist: pyotp>=2.9
26
+
27
+ # stark-jarvis
28
+
29
+ **J.A.R.V.I.S. Terminal Client** — Access your AI assistant from any terminal, anywhere.
30
+
31
+ ## Install
32
+
33
+ ```bash
34
+ pip install stark-jarvis
35
+ ```
36
+
37
+ ## Quick Start
38
+
39
+ ```bash
40
+ # Connect to your JARVIS server
41
+ jarvis login https://your-jarvis-server.com
42
+
43
+ # Interactive chat
44
+ jarvis
45
+
46
+ # One-shot query
47
+ jarvis "What's the weather in New York?"
48
+
49
+ # Use a specific model
50
+ jarvis --model gemini "Explain quantum computing"
51
+ ```
52
+
53
+ ## Commands
54
+
55
+ | Command | Description |
56
+ |---------|-------------|
57
+ | `jarvis login <url>` | Authenticate with a JARVIS server |
58
+ | `jarvis` | Start interactive chat |
59
+ | `jarvis "message"` | Send a one-shot query |
60
+ | `jarvis --model <provider>` | Use specific provider (claude, gemini, stark_protocol) |
61
+ | `jarvis status` | Show connection status |
62
+ | `jarvis logout` | Clear stored credentials |
63
+ | `jarvis purge` | Remove all config from this machine |
64
+
65
+ ## Interactive Commands
66
+
67
+ Once in a chat session:
68
+
69
+ | Command | Description |
70
+ |---------|-------------|
71
+ | `/model <provider>` | Switch LLM provider |
72
+ | `/model` | Show current provider |
73
+ | `/new` | Start new conversation |
74
+ | `/help` | Show help |
75
+ | `exit` | Quit |
76
+
77
+ ## Security
78
+
79
+ - Credentials are encrypted with a local access code (PBKDF2 + Fernet)
80
+ - Nothing is stored in plain text
81
+ - `jarvis purge` removes everything from the machine
82
+ - Designed for Iron Man 3 scenarios — use any device, clean up when done
@@ -0,0 +1,14 @@
1
+ stark_jarvis/__init__.py,sha256=gUb6TxOIH5dNFooGmPdxUnPCPodDvZ4ESFbd89bNqaU,97
2
+ stark_jarvis/__main__.py,sha256=Baw1nx8xqMVTHmnE1_gY9tscrJr2e5z5V83X8yU-tYw,92
3
+ stark_jarvis/auth.py,sha256=tg1rO7aSU8oDkNIZGM5pohppyZTZdIvpd58-xhQ09-U,11597
4
+ stark_jarvis/chat.py,sha256=BfDCh-GxaWPbRkyd2tbAgWzKB91MPEY31MpZG2qE1JI,16640
5
+ stark_jarvis/config.py,sha256=L9VZQmk5s5GzRqmb4hSs2bg8Dz83yrusxs9AmT9PE5Q,5630
6
+ stark_jarvis/display.py,sha256=GqDJEAcSh5rrZj4VapURREjNBH4XngLPvm9VIggFCZA,21755
7
+ stark_jarvis/input_ui.py,sha256=xs_pAuun7Bndfew38S5TfKE7c6s0aQ3UAy8wpn1TFOg,4756
8
+ stark_jarvis/main.py,sha256=JdE6QO4mmhSG6lYh7QtlAa2XBeE5b7iXQ70QaQKLTc0,10015
9
+ stark_jarvis/oneshot.py,sha256=X5Na2FDgq38xg6dt-ZJX6ed3SNekK8_RFsoYwCv-l94,2585
10
+ malibupoint-0.2.0.dist-info/METADATA,sha256=MzKezq9dwwtQGuiPZquUvaN7RZuI70abHF_i4_03MhY,2395
11
+ malibupoint-0.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
12
+ malibupoint-0.2.0.dist-info/entry_points.txt,sha256=RC468KHCnkH--jGGKpB7PKOokeG60yqp9MmzkAnIVvc,50
13
+ malibupoint-0.2.0.dist-info/top_level.txt,sha256=C6bgixkZYqYgG-48NIpFX3RfeGBZGP6vSjDbp8fp0N4,13
14
+ malibupoint-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ jarvis = stark_jarvis.main:main
@@ -0,0 +1 @@
1
+ stark_jarvis
@@ -0,0 +1,3 @@
1
+ """malibupoint — J.A.R.V.I.S. Terminal Client (Stark Secure Server)."""
2
+
3
+ __version__ = "0.2.0"
@@ -0,0 +1,5 @@
1
+ """Entry point for `python -m stark_jarvis`."""
2
+
3
+ from stark_jarvis.main import main
4
+
5
+ main()
stark_jarvis/auth.py ADDED
@@ -0,0 +1,296 @@
1
+ """Authentication — 4-layer CLI access control.
2
+
3
+ First-time setup (`jarvis login`):
4
+ 1. Set gate username + password (static, local)
5
+ 2. Enter Setup Token (one-time, proves ownership)
6
+ 3. Enter JARVIS Username (verified against server)
7
+ 4. Choose SHT (Secure Handshake Token — user-chosen, stored on server)
8
+
9
+ Every subsequent access:
10
+ Layer 1: Gate Username (local)
11
+ Layer 2: Gate Password (local)
12
+ Layer 3: SHT (server-verified)
13
+ Layer 4: JARVIS Username (server account lookup → JWT tokens)
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import getpass
19
+ import hashlib
20
+ import sys
21
+ import time
22
+ from typing import Optional
23
+
24
+ import httpx
25
+
26
+ from stark_jarvis.config import config, DEFAULT_SERVER, _get_salt
27
+
28
+ # ANSI colours — JARVIS blue palette
29
+ _BLUE = "\x1b[38;2;0;212;255m"
30
+ _RED = "\x1b[38;2;239;68;68m"
31
+ _GREEN = "\x1b[38;2;52;211;153m"
32
+ _DIM = "\x1b[2m"
33
+ _BOLD = "\x1b[1m"
34
+ _RESET = "\x1b[0m"
35
+
36
+ # Lockout config
37
+ _MAX_ATTEMPTS = 5
38
+ _LOCKOUT_SECONDS = 900 # 15 minutes
39
+
40
+
41
+ def _hash_value(value: str) -> str:
42
+ """Hash a credential with PBKDF2 for local storage/verification."""
43
+ salt = _get_salt()
44
+ return hashlib.pbkdf2_hmac("sha256", value.encode(), salt, iterations=100_000).hex()
45
+
46
+
47
+ def _check_lockout() -> None:
48
+ """Check if the CLI is locked out from too many failures."""
49
+ lockout_until = config.get("lockout_until")
50
+ if lockout_until and time.time() < lockout_until:
51
+ remaining = int(lockout_until - time.time())
52
+ minutes = remaining // 60
53
+ seconds = remaining % 60
54
+ print(f" {_RED}Access locked. Try again in {minutes}m {seconds}s.{_RESET}")
55
+ sys.exit(1)
56
+
57
+
58
+ def _record_failure() -> None:
59
+ """Record a failed attempt and enforce lockout."""
60
+ attempts = config.get("failed_attempts", 0) + 1
61
+ config.set("failed_attempts", attempts)
62
+ if attempts >= _MAX_ATTEMPTS:
63
+ config.set("lockout_until", time.time() + _LOCKOUT_SECONDS)
64
+ config.set("failed_attempts", 0)
65
+ print(f" {_RED}Too many failed attempts. Locked for 15 minutes.{_RESET}")
66
+ sys.exit(1)
67
+ remaining = _MAX_ATTEMPTS - attempts
68
+ print(f" {_RED}Incorrect. {remaining} attempt(s) remaining.{_RESET}")
69
+
70
+
71
+ def _clear_failures() -> None:
72
+ """Clear failure counter on success."""
73
+ config.set("failed_attempts", 0)
74
+ config.set("lockout_until", None)
75
+
76
+
77
+ def _api_url(path: str) -> str:
78
+ """Build full API URL."""
79
+ base = config.server_url or DEFAULT_SERVER
80
+ return f"{base}/api/v1{path}"
81
+
82
+
83
+ # ══════════════════════════════════════════════════════════════════════════
84
+ # First-time setup: `jarvis login`
85
+ # ══════════════════════════════════════════════════════════════════════════
86
+
87
+
88
+ def login(server_url: str) -> None:
89
+ """Login to JARVIS. First-time: full setup. Already configured: re-authenticate."""
90
+ config.server_url = server_url
91
+
92
+ # If already set up, just re-authenticate to get fresh tokens
93
+ if config.is_setup():
94
+ print(f"\n {_BLUE}{_BOLD}Stark Secure Server Login{_RESET}")
95
+ print(f" {_DIM}Reconnecting to {server_url}{_RESET}\n")
96
+ access_token, refresh_token = unlock()
97
+ config.save_session(access_token, refresh_token)
98
+ return
99
+
100
+ # ── First-time setup ──
101
+ print(f"\n {_BLUE}{_BOLD}╔══════════════════════════════════════╗{_RESET}")
102
+ print(f" {_BLUE}{_BOLD}║ Stark Secure Server — CLI Setup ║{_RESET}")
103
+ print(f" {_BLUE}{_BOLD}╚══════════════════════════════════════╝{_RESET}")
104
+ print(f" {_DIM}Connecting to {server_url}{_RESET}\n")
105
+
106
+ # Verify server is reachable
107
+ try:
108
+ resp = httpx.get(f"{server_url}/api/v1/auth/setup-status", timeout=10.0)
109
+ resp.raise_for_status()
110
+ setup_complete = resp.json().get("setup_complete", False)
111
+ except httpx.ConnectError:
112
+ print(f" {_RED}Cannot reach server at {server_url}{_RESET}")
113
+ sys.exit(1)
114
+ except Exception:
115
+ print(f" {_RED}Server error.{_RESET}")
116
+ sys.exit(1)
117
+
118
+ if not setup_complete:
119
+ print(f" {_RED}No owner account exists yet.{_RESET}")
120
+ print(f" {_DIM}Create your account at {server_url} first, then run this again.{_RESET}")
121
+ sys.exit(1)
122
+
123
+ # ── Step 1: Set gate credentials ──
124
+ print(f" {_BLUE}{_BOLD}Step 1:{_RESET} Set your CLI gate credentials")
125
+ print(f" {_DIM}Static credentials that guard CLI access.{_RESET}\n")
126
+
127
+ gate_user = input(f" {_BLUE}Gate Username: {_RESET}").strip()
128
+ if not gate_user:
129
+ print(f" {_RED}Cannot be empty.{_RESET}")
130
+ sys.exit(1)
131
+
132
+ gate_pass = getpass.getpass(f" {_BLUE}Gate Password: {_RESET}")
133
+ if not gate_pass:
134
+ print(f" {_RED}Cannot be empty.{_RESET}")
135
+ sys.exit(1)
136
+ gate_pass2 = getpass.getpass(f" {_BLUE}Confirm Gate Password: {_RESET}")
137
+ if gate_pass != gate_pass2:
138
+ print(f" {_RED}Passwords do not match.{_RESET}")
139
+ sys.exit(1)
140
+
141
+ config.set("gate_username_hash", _hash_value(gate_user))
142
+ config.set("gate_password_hash", _hash_value(gate_pass))
143
+ print(f" {_GREEN}Gate credentials set.{_RESET}\n")
144
+
145
+ # ── Step 2: Setup Token ──
146
+ print(f" {_BLUE}{_BOLD}Step 2:{_RESET} Enter the Setup Token")
147
+ print(f" {_DIM}One-time token to prove ownership.{_RESET}\n")
148
+
149
+ setup_token = getpass.getpass(f" {_BLUE}Setup Token: {_RESET}")
150
+ if not setup_token:
151
+ print(f" {_RED}Cannot be empty.{_RESET}")
152
+ sys.exit(1)
153
+
154
+ # ── Step 3: JARVIS Username ──
155
+ print(f"\n {_BLUE}{_BOLD}Step 3:{_RESET} Enter your JARVIS username")
156
+ print(f" {_DIM}The username you registered with on the site.{_RESET}\n")
157
+
158
+ username = input(f" {_BLUE}JARVIS Username: {_RESET}").strip()
159
+ if not username:
160
+ print(f" {_RED}Cannot be empty.{_RESET}")
161
+ sys.exit(1)
162
+
163
+ # ── Step 4: Choose SHT ──
164
+ print(f"\n {_BLUE}{_BOLD}Step 4:{_RESET} Set your Secure Handshake Token (SHT)")
165
+ print(f" {_DIM}This passphrase is required every time you access J.A.R.V.I.S.{_RESET}")
166
+ print(f" {_DIM}Same across CLI and website.{_RESET}\n")
167
+
168
+ sht = getpass.getpass(f" {_BLUE}SHT: {_RESET}")
169
+ if not sht or len(sht) < 4:
170
+ print(f" {_RED}SHT must be at least 4 characters.{_RESET}")
171
+ sys.exit(1)
172
+ sht2 = getpass.getpass(f" {_BLUE}Confirm SHT: {_RESET}")
173
+ if sht != sht2:
174
+ print(f" {_RED}SHT does not match.{_RESET}")
175
+ sys.exit(1)
176
+
177
+ # ── Send to server (one call) ──
178
+ try:
179
+ resp = httpx.post(
180
+ f"{server_url}/api/v1/auth/cli-setup",
181
+ json={"setup_token": setup_token, "username": username, "sht": sht},
182
+ timeout=15.0,
183
+ )
184
+ resp.raise_for_status()
185
+ except httpx.HTTPStatusError as exc:
186
+ detail = exc.response.json().get("detail", str(exc))
187
+ print(f" {_RED}{detail}{_RESET}")
188
+ sys.exit(1)
189
+ except httpx.ConnectError:
190
+ print(f" {_RED}Cannot reach server.{_RESET}")
191
+ sys.exit(1)
192
+ except Exception as exc:
193
+ print(f" {_RED}Setup failed: {exc}{_RESET}")
194
+ sys.exit(1)
195
+
196
+ config.set("jarvis_username", username)
197
+ config.set("sht_hash", _hash_value(sht))
198
+ _clear_failures()
199
+
200
+ print(f"\n {_GREEN}{_BOLD}Stark Secure Server — connection established.{_RESET}")
201
+ print(f" {_GREEN}J.A.R.V.I.S. is ready, Sir.{_RESET}\n")
202
+
203
+
204
+ # ══════════════════════════════════════════════════════════════════════════
205
+ # Unlock: every subsequent access
206
+ # ══════════════════════════════════════════════════════════════════════════
207
+
208
+
209
+ def unlock() -> tuple[str, str]:
210
+ """Full 4-layer authentication. Returns (access_token, refresh_token)."""
211
+ _check_lockout()
212
+
213
+ if not config.is_setup():
214
+ print(f" {_RED}CLI not configured. Run: jarvis login{_RESET}")
215
+ sys.exit(1)
216
+
217
+ print(f"\n {_BLUE}{_BOLD}Stark Secure Server Login{_RESET}\n")
218
+
219
+ # Layer 1: Gate Username
220
+ gate_user = input(f" {_BLUE}Gate Username: {_RESET}").strip()
221
+ if not gate_user or _hash_value(gate_user) != config.get("gate_username_hash"):
222
+ _record_failure()
223
+ sys.exit(1)
224
+
225
+ # Layer 2: Gate Password
226
+ gate_pass = getpass.getpass(f" {_BLUE}Gate Password: {_RESET}")
227
+ if not gate_pass or _hash_value(gate_pass) != config.get("gate_password_hash"):
228
+ _record_failure()
229
+ sys.exit(1)
230
+
231
+ # Layer 3: SHT — local pre-check then server verification
232
+ sht = getpass.getpass(f" {_BLUE}Secure Handshake Token: {_RESET}")
233
+ if not sht or _hash_value(sht) != config.get("sht_hash"):
234
+ _record_failure()
235
+ sys.exit(1)
236
+
237
+ # Layer 4: Use stored JARVIS username — server verifies SHT + username
238
+ jarvis_user = config.get("jarvis_username")
239
+ if not jarvis_user:
240
+ print(f" {_RED}No JARVIS username stored. Run: jarvis login{_RESET}")
241
+ sys.exit(1)
242
+
243
+ try:
244
+ resp = httpx.post(
245
+ _api_url("/auth/cli-login"),
246
+ json={"sht": sht, "username": jarvis_user},
247
+ timeout=15.0,
248
+ )
249
+ if resp.status_code == 200:
250
+ data = resp.json()
251
+ _clear_failures()
252
+ access_token = data["access_token"]
253
+ refresh_token = data["refresh_token"]
254
+ config.save_session(access_token, refresh_token)
255
+ username = data.get("user", {}).get("username", jarvis_user)
256
+ print(f"\n {_GREEN}Authenticated as {username}.{_RESET}")
257
+ print(f" {_GREEN}Stark Secure Server — session active.{_RESET}\n")
258
+ return access_token, refresh_token
259
+ else:
260
+ _record_failure()
261
+ sys.exit(1)
262
+ except httpx.ConnectError:
263
+ print(f" {_RED}Cannot reach server.{_RESET}")
264
+ sys.exit(1)
265
+ except Exception:
266
+ _record_failure()
267
+ sys.exit(1)
268
+
269
+ # Unreachable but satisfies type checker
270
+ sys.exit(1)
271
+
272
+
273
+ # ══════════════════════════════════════════════════════════════════════════
274
+ # Token refresh & logout
275
+ # ══════════════════════════════════════════════════════════════════════════
276
+
277
+
278
+ def refresh_access_token(refresh_token: str) -> tuple[str, str]:
279
+ """Use refresh token to get a new access token pair."""
280
+ try:
281
+ resp = httpx.post(
282
+ _api_url("/auth/refresh"),
283
+ json={"refresh_token": refresh_token},
284
+ timeout=15.0,
285
+ )
286
+ resp.raise_for_status()
287
+ data = resp.json()
288
+ return data["access_token"], data["refresh_token"]
289
+ except Exception:
290
+ return "", ""
291
+
292
+
293
+ def logout() -> None:
294
+ """Clear all stored credentials and config."""
295
+ config.clear_all()
296
+ print(f" {_GREEN}Logged out. Stark Secure Server session terminated.{_RESET}")