codedthemes-cli 0.1.10__tar.gz → 0.1.12__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.
- {codedthemes_cli-0.1.10 → codedthemes_cli-0.1.12}/PKG-INFO +1 -1
- {codedthemes_cli-0.1.10 → codedthemes_cli-0.1.12}/codedthemes/cli.py +53 -21
- codedthemes_cli-0.1.12/codedthemes/config.py +63 -0
- {codedthemes_cli-0.1.10 → codedthemes_cli-0.1.12}/codedthemes_cli.egg-info/PKG-INFO +1 -1
- {codedthemes_cli-0.1.10 → codedthemes_cli-0.1.12}/pyproject.toml +1 -1
- codedthemes_cli-0.1.10/codedthemes/config.py +0 -35
- {codedthemes_cli-0.1.10 → codedthemes_cli-0.1.12}/README.md +0 -0
- {codedthemes_cli-0.1.10 → codedthemes_cli-0.1.12}/codedthemes/__init__.py +0 -0
- {codedthemes_cli-0.1.10 → codedthemes_cli-0.1.12}/codedthemes/mcp_client.py +0 -0
- {codedthemes_cli-0.1.10 → codedthemes_cli-0.1.12}/codedthemes/patch_utils.py +0 -0
- {codedthemes_cli-0.1.10 → codedthemes_cli-0.1.12}/codedthemes/repo_utils.py +0 -0
- {codedthemes_cli-0.1.10 → codedthemes_cli-0.1.12}/codedthemes/sync_manager.py +0 -0
- {codedthemes_cli-0.1.10 → codedthemes_cli-0.1.12}/codedthemes_cli.egg-info/SOURCES.txt +0 -0
- {codedthemes_cli-0.1.10 → codedthemes_cli-0.1.12}/codedthemes_cli.egg-info/dependency_links.txt +0 -0
- {codedthemes_cli-0.1.10 → codedthemes_cli-0.1.12}/codedthemes_cli.egg-info/entry_points.txt +0 -0
- {codedthemes_cli-0.1.10 → codedthemes_cli-0.1.12}/codedthemes_cli.egg-info/requires.txt +0 -0
- {codedthemes_cli-0.1.10 → codedthemes_cli-0.1.12}/codedthemes_cli.egg-info/top_level.txt +0 -0
- {codedthemes_cli-0.1.10 → codedthemes_cli-0.1.12}/setup.cfg +0 -0
|
@@ -8,19 +8,48 @@ from getpass import getpass
|
|
|
8
8
|
import jwt
|
|
9
9
|
import requests
|
|
10
10
|
|
|
11
|
-
from .config import save_config, load_config
|
|
11
|
+
from .config import save_config, load_config, get_device_id
|
|
12
12
|
from .mcp_client import MCPClient
|
|
13
13
|
from .repo_utils import detect_repo_root, zip_repo
|
|
14
14
|
from .sync_manager import SyncManager
|
|
15
|
+
import socket
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
def handle_login(server_url: str = None):
|
|
18
19
|
"""
|
|
19
20
|
Handles user authentication with the MCP server.
|
|
20
21
|
"""
|
|
22
|
+
config = load_config()
|
|
23
|
+
existing_token = config.get("access_token")
|
|
24
|
+
if existing_token:
|
|
25
|
+
try:
|
|
26
|
+
decoded = jwt.decode(existing_token, options={"verify_signature": False})
|
|
27
|
+
existing_email = decoded.get("email", "unknown")
|
|
28
|
+
print(f"ℹ Currently logged in as: {existing_email}")
|
|
29
|
+
choice = input("Switch account? (y/N): ").strip().lower()
|
|
30
|
+
if choice not in ['y', 'yes']:
|
|
31
|
+
print("Login cancelled.")
|
|
32
|
+
return
|
|
33
|
+
except:
|
|
34
|
+
pass
|
|
35
|
+
|
|
21
36
|
email = input("Email: ").strip()
|
|
22
37
|
license_key = input("License key: ").strip()
|
|
23
38
|
|
|
39
|
+
# Idempotent check: Are we already logged in as this user?
|
|
40
|
+
if existing_token:
|
|
41
|
+
try:
|
|
42
|
+
decoded = jwt.decode(existing_token, options={"verify_signature": False})
|
|
43
|
+
if decoded.get("email") == email:
|
|
44
|
+
print(f"✔ Already logged in as {email}. Nothing changed.")
|
|
45
|
+
return
|
|
46
|
+
except:
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
device_id = get_device_id()
|
|
50
|
+
|
|
51
|
+
device_name = socket.gethostname()
|
|
52
|
+
|
|
24
53
|
client = MCPClient()
|
|
25
54
|
if server_url:
|
|
26
55
|
client.server_url = server_url
|
|
@@ -28,7 +57,9 @@ def handle_login(server_url: str = None):
|
|
|
28
57
|
try:
|
|
29
58
|
result = client.call("login", {
|
|
30
59
|
"email": email,
|
|
31
|
-
"license_key": license_key
|
|
60
|
+
"license_key": license_key,
|
|
61
|
+
"device_id": device_id,
|
|
62
|
+
"device_name": device_name
|
|
32
63
|
})
|
|
33
64
|
|
|
34
65
|
if isinstance(result, dict):
|
|
@@ -39,27 +70,13 @@ def handle_login(server_url: str = None):
|
|
|
39
70
|
print(f"✖ {result['message']}")
|
|
40
71
|
sys.exit(1)
|
|
41
72
|
|
|
42
|
-
# Get token from result
|
|
73
|
+
# Get token from result
|
|
43
74
|
token = result.get("access_token") if isinstance(result, dict) else None
|
|
44
75
|
|
|
45
|
-
if not token:
|
|
46
|
-
token_file = os.path.expanduser("~/.mcp_token")
|
|
47
|
-
if os.path.exists(token_file):
|
|
48
|
-
with open(token_file, 'r') as f:
|
|
49
|
-
token = f.read().strip()
|
|
50
|
-
|
|
51
76
|
if not token:
|
|
52
77
|
print("✖ Login failed: No access token returned. Please check your credentials.")
|
|
53
78
|
sys.exit(1)
|
|
54
79
|
|
|
55
|
-
# Store token for both local MCP and CLI config
|
|
56
|
-
token_file = os.path.expanduser("~/.mcp_token")
|
|
57
|
-
try:
|
|
58
|
-
with open(token_file, 'w') as f:
|
|
59
|
-
f.write(token)
|
|
60
|
-
except Exception as e:
|
|
61
|
-
print(f"Warning: Could not write {token_file}: {e}")
|
|
62
|
-
|
|
63
80
|
save_config({
|
|
64
81
|
"server_url": client.server_url,
|
|
65
82
|
"access_token": token
|
|
@@ -133,7 +150,8 @@ def handle_init():
|
|
|
133
150
|
print(' "mcpServers": {')
|
|
134
151
|
print(' "code-theme-mcp": {')
|
|
135
152
|
print(' "type": "sse",')
|
|
136
|
-
print(' "serverURL": "https://mcp.codedthemes.com/api/mcp"')
|
|
153
|
+
print(' "serverURL": "https://mcp.codedthemes.com/api/mcp",')
|
|
154
|
+
print(f' "env": {{ "CODEDTHEMES_TOKEN": "{client.token}" }}')
|
|
137
155
|
print(' }')
|
|
138
156
|
print(' }')
|
|
139
157
|
print(' }\n')
|
|
@@ -146,7 +164,8 @@ def handle_init():
|
|
|
146
164
|
print(' "servers": {')
|
|
147
165
|
print(' "code-theme-mcp": {')
|
|
148
166
|
print(' "url": "https://mcp.codedthemes.com/api/mcp",')
|
|
149
|
-
print(' "type": "http"')
|
|
167
|
+
print(' "type": "http",')
|
|
168
|
+
print(f' "env": {{ "CODEDTHEMES_TOKEN": "{client.token}" }}')
|
|
150
169
|
print(' }')
|
|
151
170
|
print(' },')
|
|
152
171
|
print(' "inputs": []')
|
|
@@ -160,7 +179,8 @@ def handle_init():
|
|
|
160
179
|
print(' "mcpServers": {')
|
|
161
180
|
print(' "code-theme-mcp": {')
|
|
162
181
|
print(' "transport": "sse",')
|
|
163
|
-
print(' "url": "https://mcp.codedthemes.com/api/mcp"')
|
|
182
|
+
print(' "url": "https://mcp.codedthemes.com/api/mcp",')
|
|
183
|
+
print(f' "env": {{ "CODEDTHEMES_TOKEN": "{client.token}" }}')
|
|
164
184
|
print(' }')
|
|
165
185
|
print(' }')
|
|
166
186
|
print(' }\n')
|
|
@@ -207,14 +227,26 @@ def handle_apply(query: str):
|
|
|
207
227
|
if workspace_id:
|
|
208
228
|
try:
|
|
209
229
|
check = client.call("check_workspace", {"workspace_id": workspace_id})
|
|
230
|
+
if isinstance(check, dict) and check.get("status") == "error" and check.get("message") == "WORKSPACE_EVICTED":
|
|
231
|
+
print("\n⚠ Your workspace is inactive.")
|
|
232
|
+
print("It was unloaded from the server due to 30 minutes of inactivity.")
|
|
233
|
+
print("\nRun '\033[1mcodedthemes init\033[0m' to restore your workspace.\n")
|
|
234
|
+
sys.exit(0)
|
|
235
|
+
|
|
210
236
|
if isinstance(check, dict) and check.get("status") == "ok":
|
|
211
237
|
# print(f"✔ Using active workspace: {workspace_id}")
|
|
212
238
|
pass
|
|
213
239
|
else:
|
|
214
240
|
workspace_id = None
|
|
215
|
-
except:
|
|
241
|
+
except Exception as e:
|
|
242
|
+
if "WORKSPACE_EVICTED" in str(e):
|
|
243
|
+
print("\n⚠ Your workspace is inactive.")
|
|
244
|
+
print("It was unloaded from the server due to 30 minutes of inactivity.")
|
|
245
|
+
print("\nRun '\033[1mcodedthemes init\033[0m' to restore your workspace.\n")
|
|
246
|
+
sys.exit(0)
|
|
216
247
|
workspace_id = None
|
|
217
248
|
|
|
249
|
+
|
|
218
250
|
if not workspace_id:
|
|
219
251
|
print("📦 Zipping and uploading repository (this may take a moment)...")
|
|
220
252
|
upload_result = client.upload_workspace(repo_abs_path, user_email)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import socket
|
|
3
|
+
import hashlib
|
|
4
|
+
import uuid
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
# Stores token at ~/.codedthemes/config.json
|
|
8
|
+
CONFIG_DIR = Path.home() / ".codedthemes"
|
|
9
|
+
CONFIG_FILE = CONFIG_DIR / "config.json"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def ensure_config_dir():
|
|
13
|
+
"""
|
|
14
|
+
Ensures the configuration directory exists.
|
|
15
|
+
"""
|
|
16
|
+
CONFIG_DIR.mkdir(exist_ok=True)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def save_config(data: dict):
|
|
20
|
+
"""
|
|
21
|
+
Saves the provided configuration data to the config file (merging with existing).
|
|
22
|
+
"""
|
|
23
|
+
ensure_config_dir()
|
|
24
|
+
existing = load_config()
|
|
25
|
+
existing.update(data)
|
|
26
|
+
with open(CONFIG_FILE, "w") as f:
|
|
27
|
+
json.dump(existing, f, indent=2)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_device_id():
|
|
31
|
+
"""
|
|
32
|
+
Generates or retrieves a unique device ID for the machine.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
config = load_config()
|
|
36
|
+
if "device_id" in config:
|
|
37
|
+
return config["device_id"]
|
|
38
|
+
|
|
39
|
+
# Generate a stable device ID based on hostname and machine UUID
|
|
40
|
+
try:
|
|
41
|
+
hostname = socket.gethostname()
|
|
42
|
+
device_uuid = str(uuid.getnode()) # MAC address based
|
|
43
|
+
raw_id = f"{hostname}-{device_uuid}"
|
|
44
|
+
device_id = hashlib.sha256(raw_id.encode()).hexdigest()[:12]
|
|
45
|
+
except:
|
|
46
|
+
device_id = str(uuid.uuid4())[:12]
|
|
47
|
+
|
|
48
|
+
save_config({"device_id": device_id})
|
|
49
|
+
return device_id
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def load_config():
|
|
54
|
+
"""
|
|
55
|
+
Loads the configuration data from the config file.
|
|
56
|
+
"""
|
|
57
|
+
if not CONFIG_FILE.exists():
|
|
58
|
+
return {}
|
|
59
|
+
with open(CONFIG_FILE, "r") as f:
|
|
60
|
+
try:
|
|
61
|
+
return json.load(f)
|
|
62
|
+
except json.JSONDecodeError:
|
|
63
|
+
return {}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
|
|
4
|
-
# Stores token at ~/.codedthemes/config.json
|
|
5
|
-
CONFIG_DIR = Path.home() / ".codedthemes"
|
|
6
|
-
CONFIG_FILE = CONFIG_DIR / "config.json"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def ensure_config_dir():
|
|
10
|
-
"""
|
|
11
|
-
Ensures the configuration directory exists.
|
|
12
|
-
"""
|
|
13
|
-
CONFIG_DIR.mkdir(exist_ok=True)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def save_config(data: dict):
|
|
17
|
-
"""
|
|
18
|
-
Saves the provided configuration data to the config file.
|
|
19
|
-
"""
|
|
20
|
-
ensure_config_dir()
|
|
21
|
-
with open(CONFIG_FILE, "w") as f:
|
|
22
|
-
json.dump(data, f, indent=2)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def load_config():
|
|
26
|
-
"""
|
|
27
|
-
Loads the configuration data from the config file.
|
|
28
|
-
"""
|
|
29
|
-
if not CONFIG_FILE.exists():
|
|
30
|
-
return {}
|
|
31
|
-
with open(CONFIG_FILE, "r") as f:
|
|
32
|
-
try:
|
|
33
|
-
return json.load(f)
|
|
34
|
-
except json.JSONDecodeError:
|
|
35
|
-
return {}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codedthemes_cli-0.1.10 → codedthemes_cli-0.1.12}/codedthemes_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|