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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codedthemes-cli
3
- Version: 0.1.10
3
+ Version: 0.1.12
4
4
  Summary: CLI tool for Code Theme and Integration
5
5
  Author: codedthemes
6
6
  Requires-Python: >=3.10
@@ -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 or local fallback
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codedthemes-cli
3
- Version: 0.1.10
3
+ Version: 0.1.12
4
4
  Summary: CLI tool for Code Theme and Integration
5
5
  Author: codedthemes
6
6
  Requires-Python: >=3.10
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "codedthemes-cli"
7
- version = "0.1.10"
7
+ version = "0.1.12"
8
8
  description = "CLI tool for Code Theme and Integration"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -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 {}