malibupoint 0.2.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.
- malibupoint-0.2.0/PKG-INFO +82 -0
- malibupoint-0.2.0/README.md +56 -0
- malibupoint-0.2.0/malibupoint.egg-info/PKG-INFO +82 -0
- malibupoint-0.2.0/malibupoint.egg-info/SOURCES.txt +17 -0
- malibupoint-0.2.0/malibupoint.egg-info/dependency_links.txt +1 -0
- malibupoint-0.2.0/malibupoint.egg-info/entry_points.txt +2 -0
- malibupoint-0.2.0/malibupoint.egg-info/requires.txt +5 -0
- malibupoint-0.2.0/malibupoint.egg-info/top_level.txt +1 -0
- malibupoint-0.2.0/pyproject.toml +43 -0
- malibupoint-0.2.0/setup.cfg +4 -0
- malibupoint-0.2.0/stark_jarvis/__init__.py +3 -0
- malibupoint-0.2.0/stark_jarvis/__main__.py +5 -0
- malibupoint-0.2.0/stark_jarvis/auth.py +296 -0
- malibupoint-0.2.0/stark_jarvis/chat.py +440 -0
- malibupoint-0.2.0/stark_jarvis/config.py +158 -0
- malibupoint-0.2.0/stark_jarvis/display.py +669 -0
- malibupoint-0.2.0/stark_jarvis/input_ui.py +146 -0
- malibupoint-0.2.0/stark_jarvis/main.py +325 -0
- malibupoint-0.2.0/stark_jarvis/oneshot.py +83 -0
|
@@ -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,56 @@
|
|
|
1
|
+
# stark-jarvis
|
|
2
|
+
|
|
3
|
+
**J.A.R.V.I.S. Terminal Client** — Access your AI assistant from any terminal, anywhere.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install stark-jarvis
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Connect to your JARVIS server
|
|
15
|
+
jarvis login https://your-jarvis-server.com
|
|
16
|
+
|
|
17
|
+
# Interactive chat
|
|
18
|
+
jarvis
|
|
19
|
+
|
|
20
|
+
# One-shot query
|
|
21
|
+
jarvis "What's the weather in New York?"
|
|
22
|
+
|
|
23
|
+
# Use a specific model
|
|
24
|
+
jarvis --model gemini "Explain quantum computing"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Commands
|
|
28
|
+
|
|
29
|
+
| Command | Description |
|
|
30
|
+
|---------|-------------|
|
|
31
|
+
| `jarvis login <url>` | Authenticate with a JARVIS server |
|
|
32
|
+
| `jarvis` | Start interactive chat |
|
|
33
|
+
| `jarvis "message"` | Send a one-shot query |
|
|
34
|
+
| `jarvis --model <provider>` | Use specific provider (claude, gemini, stark_protocol) |
|
|
35
|
+
| `jarvis status` | Show connection status |
|
|
36
|
+
| `jarvis logout` | Clear stored credentials |
|
|
37
|
+
| `jarvis purge` | Remove all config from this machine |
|
|
38
|
+
|
|
39
|
+
## Interactive Commands
|
|
40
|
+
|
|
41
|
+
Once in a chat session:
|
|
42
|
+
|
|
43
|
+
| Command | Description |
|
|
44
|
+
|---------|-------------|
|
|
45
|
+
| `/model <provider>` | Switch LLM provider |
|
|
46
|
+
| `/model` | Show current provider |
|
|
47
|
+
| `/new` | Start new conversation |
|
|
48
|
+
| `/help` | Show help |
|
|
49
|
+
| `exit` | Quit |
|
|
50
|
+
|
|
51
|
+
## Security
|
|
52
|
+
|
|
53
|
+
- Credentials are encrypted with a local access code (PBKDF2 + Fernet)
|
|
54
|
+
- Nothing is stored in plain text
|
|
55
|
+
- `jarvis purge` removes everything from the machine
|
|
56
|
+
- Designed for Iron Man 3 scenarios — use any device, clean up when done
|
|
@@ -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,17 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
malibupoint.egg-info/PKG-INFO
|
|
4
|
+
malibupoint.egg-info/SOURCES.txt
|
|
5
|
+
malibupoint.egg-info/dependency_links.txt
|
|
6
|
+
malibupoint.egg-info/entry_points.txt
|
|
7
|
+
malibupoint.egg-info/requires.txt
|
|
8
|
+
malibupoint.egg-info/top_level.txt
|
|
9
|
+
stark_jarvis/__init__.py
|
|
10
|
+
stark_jarvis/__main__.py
|
|
11
|
+
stark_jarvis/auth.py
|
|
12
|
+
stark_jarvis/chat.py
|
|
13
|
+
stark_jarvis/config.py
|
|
14
|
+
stark_jarvis/display.py
|
|
15
|
+
stark_jarvis/input_ui.py
|
|
16
|
+
stark_jarvis/main.py
|
|
17
|
+
stark_jarvis/oneshot.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
stark_jarvis
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "malibupoint"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "J.A.R.V.I.S. Terminal Client — Stark Secure Server access from any terminal"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Stark Industries"},
|
|
14
|
+
]
|
|
15
|
+
keywords = ["jarvis", "ai", "assistant", "cli", "terminal", "stark"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Environment :: Console",
|
|
19
|
+
"Intended Audience :: End Users/Desktop",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Topic :: Communications :: Chat",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"websockets>=12.0",
|
|
30
|
+
"httpx>=0.25.0",
|
|
31
|
+
"cryptography>=41.0",
|
|
32
|
+
"prompt-toolkit>=3.0",
|
|
33
|
+
"pyotp>=2.9",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
jarvis = "stark_jarvis.main:main"
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://github.com/carterhamm/JustARatherVeryIntelligentSystem"
|
|
41
|
+
|
|
42
|
+
[tool.setuptools.packages.find]
|
|
43
|
+
include = ["stark_jarvis*"]
|
|
@@ -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}")
|