codedthemes-cli 0.1.17__tar.gz → 0.1.19__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.17
3
+ Version: 0.1.19
4
4
  Summary: CLI tool for Code Theme and Integration
5
5
  Author: codedthemes
6
6
  Requires-Python: >=3.10
@@ -1,4 +1,4 @@
1
- from .config import load_config, save_config
1
+ from .config import load_config, save_config, CONFIG_FILE
2
2
  from .mcp_client import MCPClient
3
3
  from .repo_utils import detect_repo_root, zip_repo
4
4
  from .patch_utils import apply_patch
@@ -1,18 +1,17 @@
1
1
  import argparse
2
- import sys
3
- import os
4
2
  import json
3
+ import os
5
4
  import shutil
6
- from getpass import getpass
5
+ import socket
6
+ import sys
7
7
 
8
8
  import jwt
9
9
  import requests
10
10
 
11
- from .config import save_config, load_config, get_device_id
11
+ from .config import save_config, load_config, get_device_id, CONFIG_FILE
12
12
  from .mcp_client import MCPClient
13
- from .repo_utils import detect_repo_root, zip_repo
13
+ from .repo_utils import detect_repo_root
14
14
  from .sync_manager import SyncManager
15
- import socket
16
15
 
17
16
 
18
17
  def handle_login(server_url: str = None):
@@ -29,7 +28,6 @@ def handle_login(server_url: str = None):
29
28
  choice = input("Switch account? (y/N): ").strip().lower()
30
29
  if choice not in ['y', 'yes']:
31
30
  print("Login cancelled.")
32
- print("Please enter your credentials to log in.")
33
31
  return
34
32
  except:
35
33
  pass
@@ -37,7 +35,7 @@ def handle_login(server_url: str = None):
37
35
  email = input("Email: ").strip()
38
36
  license_key = input("License key: ").strip()
39
37
 
40
- # Idempotent check: Are we already logged in as this user?
38
+ # Idempotent check: already logged in as this user?
41
39
  if existing_token:
42
40
  try:
43
41
  decoded = jwt.decode(existing_token, options={"verify_signature": False})
@@ -69,7 +67,7 @@ def handle_login(server_url: str = None):
69
67
  print("✖ Login failed: No access token returned. Please check your credentials.")
70
68
  sys.exit(1)
71
69
 
72
- # LAZY CONFIG: Only save if login succeeded
70
+
73
71
  save_config({
74
72
  "access_token": token,
75
73
  "server_url": client.server_url,
@@ -114,12 +112,9 @@ def handle_logout():
114
112
  "license_key": license_key,
115
113
  "device_id": device_id
116
114
  })
117
- except Exception as e:
118
- # Silently fail if server logout fails, we still want to clear local state
115
+ except Exception:
119
116
  pass
120
117
 
121
- # Local cleanup: Remove config.json but KEEP device.json
122
- from .config import CONFIG_FILE
123
118
  if CONFIG_FILE.exists():
124
119
  CONFIG_FILE.unlink()
125
120
 
@@ -152,12 +147,7 @@ def handle_init():
152
147
  repo_abs_path = os.path.abspath(repo_root)
153
148
  repo_key = repo_abs_path.lower().replace(os.sep, "/")
154
149
 
155
- # Check if already initialized to avoid redundant uploads
156
150
  config = load_config()
157
- existing_ws_id = config.get("workspaces", {}).get(repo_key)
158
- if existing_ws_id:
159
- # Fast check: exists?
160
- pass # We could call a /verify endpoint here, but for now let's just proceed with upload as "sync"
161
151
 
162
152
  print("📦 Zipping and uploading repository (this may take a moment)...")
163
153
  upload_result = client.upload_workspace(repo_abs_path, user_email)
@@ -186,24 +176,19 @@ def handle_init():
186
176
 
187
177
  print("Choose your editor and add the MCP server:\n")
188
178
 
189
- # ---------------- ANTIGRAVITY ----------------
190
179
  print("🔹 Antigravity")
191
180
  print(" MCP Servers → Manage → Config\n")
192
-
193
181
  print(' {')
194
182
  print(' "mcpServers": {')
195
183
  print(' "code-theme-mcp": {')
196
184
  print(' "type": "sse",')
197
- print(' "serverURL": "https://mcp.codedthemes.com/api/mcp",')
198
- print(f' "env": {{ "CODEDTHEMES_TOKEN": "{client.token}" }}')
185
+ print(' "serverURL": "https://mcp.codedthemes.com/api/mcp"')
199
186
  print(' }')
200
187
  print(' }')
201
188
  print(' }\n')
202
189
 
203
- # ---------------- VS CODE ----------------
204
190
  print("🔹 VS Code")
205
191
  print(" Ctrl+Shift+P → MCP: Add MCP Server → http\n")
206
-
207
192
  print(' {')
208
193
  print(' "servers": {')
209
194
  print(' "code-theme-mcp": {')
@@ -215,10 +200,8 @@ def handle_init():
215
200
  print(' "inputs": []')
216
201
  print(' }\n')
217
202
 
218
- # ---------------- CURSOR ----------------
219
203
  print("🔹 Cursor")
220
204
  print(" Settings → Features → MCP Servers\n")
221
-
222
205
  print(' {')
223
206
  print(' "mcpServers": {')
224
207
  print(' "code-theme-mcp": {')
@@ -230,9 +213,7 @@ def handle_init():
230
213
  print(' }\n')
231
214
 
232
215
  print("✔ Once saved, your AI assistant will detect available tools automatically.\n")
233
-
234
216
  print('💡 Example: "Update the primary color to deep purple"')
235
-
236
217
  print("="*60)
237
218
 
238
219
  except Exception as e:
@@ -338,7 +319,7 @@ def handle_apply(query: str):
338
319
  last_workspaces = config.get("workspaces", {})
339
320
  workspace_id = last_workspaces.get(repo_key)
340
321
 
341
- # Verify or re-initialize workspace
322
+
342
323
  if workspace_id:
343
324
  try:
344
325
  check = client.call("check_workspace", {"workspace_id": workspace_id})
@@ -349,7 +330,6 @@ def handle_apply(query: str):
349
330
  sys.exit(0)
350
331
 
351
332
  if isinstance(check, dict) and check.get("status") == "ok":
352
- # print(f"✔ Using active workspace: {workspace_id}")
353
333
  pass
354
334
  else:
355
335
  workspace_id = None
@@ -361,7 +341,6 @@ def handle_apply(query: str):
361
341
  sys.exit(0)
362
342
  workspace_id = None
363
343
 
364
-
365
344
  if not workspace_id:
366
345
  print("📦 Zipping and uploading repository (this may take a moment)...")
367
346
  upload_result = client.upload_workspace(repo_abs_path, user_email)
@@ -371,7 +350,7 @@ def handle_apply(query: str):
371
350
  save_config(config)
372
351
  sync_manager.update_sync_state(repo_abs_path, workspace_id, user_email)
373
352
 
374
- # 1. Repository Analysis
353
+
375
354
  print("🚀 Analyzing repository...")
376
355
  analysis = client.call("repo_analyzer", {
377
356
  "repo_path": repo_abs_path,
@@ -388,7 +367,7 @@ def handle_apply(query: str):
388
367
  else:
389
368
  print(f"✔ {analysis}")
390
369
 
391
- # 2. Planning
370
+
392
371
  print("🔍 Detecting local changes...")
393
372
  local_changes = sync_manager.get_changed_files(repo_abs_path, user_email)
394
373
  if local_changes:
@@ -441,7 +420,7 @@ def handle_apply(query: str):
441
420
  return
442
421
  print("Please enter 'y' or 'n'.")
443
422
 
444
- # 3. Execution
423
+
445
424
  print("⚙ Executing plan...")
446
425
  result = client.call("execute_plan", {
447
426
  "query": query,
@@ -458,7 +437,7 @@ def handle_apply(query: str):
458
437
  print(f"✖ Execution failed: {error_msg}")
459
438
  return
460
439
 
461
- # 4. Local Patching
440
+
462
441
  res_data = result
463
442
  if isinstance(result, str):
464
443
  try: res_data = json.loads(result)
@@ -467,7 +446,7 @@ def handle_apply(query: str):
467
446
  updates = res_data.get("updates_for_local", [])
468
447
  deletes = res_data.get("deleted_files", [])
469
448
 
470
- # 1. Apply local updates
449
+
471
450
  for item in updates:
472
451
  rel_path = clean_remote_path(item.get("path"))
473
452
  code = item.get("code")
@@ -482,7 +461,7 @@ def handle_apply(query: str):
482
461
  except Exception as e:
483
462
  print(f"✖ Error writing {rel_path}: {e}")
484
463
 
485
- # 2. Apply local deletions
464
+
486
465
  for rel_path_raw in (deletes or []):
487
466
  rel_path = clean_remote_path(rel_path_raw)
488
467
  abs_path = os.path.normpath(os.path.join(repo_abs_path, rel_path.replace('/', os.sep)))
@@ -493,12 +472,12 @@ def handle_apply(query: str):
493
472
  print(f"✔ Deleted: {rel_path}")
494
473
  except: pass
495
474
 
496
- # 3. Synchronize state
475
+
497
476
  if updates or deletes:
498
477
  sync_manager.update_sync_state(repo_abs_path, workspace_id, user_email)
499
478
  print("✔ Local sync state updated.")
500
479
 
501
- # 4. Show Integration Report (Always show errors or details)
480
+
502
481
  _details_raw = res_data.get("details", [])
503
482
  details_list = _details_raw.get("report", []) if isinstance(_details_raw, dict) else (_details_raw if isinstance(_details_raw, list) else [])
504
483
  if details_list:
@@ -507,7 +486,7 @@ def handle_apply(query: str):
507
486
  status_icon = "✔" if item.get("success") else "✖"
508
487
  print(f" [{status_icon}] {item.get('file_path')}: {item.get('message', '')}")
509
488
 
510
- # 5. Output Final Message if no updates were applied
489
+
511
490
  if not updates and not deletes:
512
491
  msg = res_data.get('message', 'Changes applied successfully.')
513
492
  if isinstance(msg, str) and "AI MUST now sync" in msg:
@@ -529,7 +508,7 @@ def main():
529
508
  parser = argparse.ArgumentParser(prog="codedthemes", description="CodedThemes CLI client")
530
509
  parser.add_argument("-v", "--version", action="version", version=f"%(prog)s {__version__}")
531
510
 
532
- subparsers = parser.add_subparsers(dest="command", required=False) # Changed to False to allow --version to work alone
511
+ subparsers = parser.add_subparsers(dest="command", required=False)
533
512
 
534
513
 
535
514
  login_parser = subparsers.add_parser("login", help="Login to MCP server")
@@ -542,8 +521,6 @@ def main():
542
521
  subparsers.add_parser("reinit", help="Reactivate an evicted workspace and sync to cloud")
543
522
  subparsers.add_parser("logout", help="Log out from current device")
544
523
 
545
-
546
-
547
524
  args = parser.parse_args()
548
525
 
549
526
  if not args.command:
@@ -561,7 +538,5 @@ def main():
561
538
  elif args.command == "logout":
562
539
  handle_logout()
563
540
 
564
-
565
-
566
541
  if __name__ == "__main__":
567
542
  main()
@@ -4,22 +4,17 @@ import hashlib
4
4
  import uuid
5
5
  from pathlib import Path
6
6
 
7
- # Stores token at ~/.codedthemes/config.json
8
7
  CONFIG_DIR = Path.home() / ".codedthemes"
9
8
  CONFIG_FILE = CONFIG_DIR / "config.json"
10
9
 
11
10
 
12
11
  def ensure_config_dir():
13
- """
14
- Ensures the configuration directory exists.
15
- """
12
+ """Ensures the configuration directory exists."""
16
13
  CONFIG_DIR.mkdir(exist_ok=True)
17
14
 
18
15
 
19
16
  def save_config(data: dict):
20
- """
21
- Saves the provided configuration data to the config file (merging with existing).
22
- """
17
+ """Saves configuration data to the config file (merging with existing)."""
23
18
  ensure_config_dir()
24
19
  existing = load_config()
25
20
  existing.update(data)
@@ -28,9 +23,7 @@ def save_config(data: dict):
28
23
 
29
24
 
30
25
  def get_device_id():
31
- """
32
- Returns a unique device ID, stored globally in ~/.codedthemes/device.json.
33
- """
26
+ """Returns a unique device ID, stored in ~/.codedthemes/device.json."""
34
27
  device_file = CONFIG_DIR / "device.json"
35
28
 
36
29
  if device_file.exists():
@@ -42,7 +35,6 @@ def get_device_id():
42
35
  except:
43
36
  pass
44
37
 
45
- # Generate new device ID
46
38
  try:
47
39
  hostname = socket.gethostname()
48
40
  device_uuid = str(uuid.getnode())
@@ -51,7 +43,6 @@ def get_device_id():
51
43
  except:
52
44
  device_id = str(uuid.uuid4())[:12]
53
45
 
54
- # Save globally (create dir if not exists)
55
46
  CONFIG_DIR.mkdir(exist_ok=True)
56
47
  try:
57
48
  with open(device_file, "w") as f:
@@ -62,12 +53,8 @@ def get_device_id():
62
53
  return device_id
63
54
 
64
55
 
65
-
66
-
67
56
  def load_config():
68
- """
69
- Loads the configuration data from the config file.
70
- """
57
+ """Loads configuration data from the config file."""
71
58
  if not CONFIG_FILE.exists():
72
59
  return {}
73
60
  with open(CONFIG_FILE, "r") as f:
@@ -1,12 +1,14 @@
1
1
  import os
2
+
2
3
  import requests
4
+
3
5
  from .config import load_config
4
6
  from .repo_utils import zip_repo
5
7
 
8
+
6
9
  class MCPClient:
7
- """
8
- Client for interacting with the CodedThemes MCP Server.
9
- """
10
+ """Client for interacting with the CodedThemes MCP Server."""
11
+
10
12
  def __init__(self):
11
13
  config = load_config()
12
14
  self.server_url = (
@@ -17,9 +19,7 @@ class MCPClient:
17
19
  self.token = config.get("access_token")
18
20
 
19
21
  def upload_workspace(self, repo_path: str, user_id: str):
20
- """
21
- Zips and uploads the repository to create a new workspace.
22
- """
22
+ """Zips and uploads the repository to create a new workspace."""
23
23
  zip_path = zip_repo(repo_path)
24
24
  repo_name = os.path.basename(repo_path)
25
25
 
@@ -59,14 +59,11 @@ class MCPClient:
59
59
  os.remove(zip_path)
60
60
 
61
61
  def call(self, tool_name: str, payload: dict):
62
- """
63
- Calls a remote MCP tool or the login endpoint.
64
- """
62
+ """Calls a remote MCP tool or the login/logout endpoint."""
65
63
  headers = {}
66
64
  if self.token:
67
65
  headers["Authorization"] = f"Bearer {self.token}"
68
66
 
69
- # Login and Logout use dedicated endpoints
70
67
  if tool_name == "login":
71
68
  url = f"{self.server_url}/auth/login"
72
69
  elif tool_name == "logout":
@@ -74,7 +71,6 @@ class MCPClient:
74
71
  else:
75
72
  url = f"{self.server_url}/tools/{tool_name}"
76
73
 
77
-
78
74
  response = requests.post(
79
75
  url,
80
76
  json=payload,
@@ -2,7 +2,7 @@ import subprocess
2
2
 
3
3
 
4
4
  def apply_patch(patch_text: str):
5
- # Using 'git apply -' to apply from stdin
5
+ """Applies a git patch from stdin."""
6
6
  process = subprocess.Popen(
7
7
  ["git", "apply", "-"],
8
8
  stdin=subprocess.PIPE,
@@ -1,48 +1,31 @@
1
1
  import os
2
- import zipfile
3
- import tempfile
4
2
  import logging
5
3
  from pathlib import Path
6
4
  from fnmatch import fnmatch
5
+ import zipfile
6
+ import tempfile
7
7
 
8
8
  logger = logging.getLogger("codedthemes")
9
9
 
10
- # ── Shared exclusion constants (used by both zip_repo and SyncManager) ────────
11
-
12
10
  EXCLUDE_DIRS = {
13
- # Version control
14
11
  ".git", ".svn", ".hg",
15
- # Dependencies
16
12
  "node_modules", "bower_components", "vendor", "venv", ".venv", "env",
17
- # Build outputs
18
13
  "build", "dist", "out", "target", ".output", "_build",
19
- # Framework caches
20
14
  ".next", ".nuxt", ".svelte-kit", ".angular", ".turbo",
21
15
  ".parcel-cache", ".cache", ".temp", ".tmp",
22
- # IDE / editor
23
16
  ".idea", ".vscode", ".vs",
24
- # Python
25
17
  "__pycache__", ".tox", ".mypy_cache", ".pytest_cache", ".ruff_cache",
26
- # Testing
27
18
  "coverage", ".nyc_output", "htmlcov",
28
- # Misc
29
19
  ".terraform", ".serverless",
30
20
  }
31
21
 
32
22
  EXCLUDE_EXTENSIONS = {
33
- # Images
34
23
  ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".webp", ".ico", ".svg",
35
- # Fonts
36
24
  ".woff", ".woff2", ".ttf", ".eot", ".otf",
37
- # Video / audio
38
25
  ".mp4", ".webm", ".avi", ".mov", ".mp3", ".wav", ".ogg",
39
- # Archives
40
26
  ".zip", ".tar", ".gz", ".rar", ".7z",
41
- # Documents
42
27
  ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx",
43
- # Source maps
44
28
  ".map",
45
- # Compiled / binary
46
29
  ".exe", ".dll", ".so", ".dylib", ".o", ".pyc", ".pyo", ".class",
47
30
  }
48
31
 
@@ -55,14 +38,8 @@ EXCLUDE_FILES = {
55
38
  MAX_SINGLE_FILE_BYTES = 2 * 1024 * 1024 # 2 MB
56
39
 
57
40
 
58
- # ── .gitignore parsing ────────────────────────────────────────────────────────
59
-
60
41
  def _load_gitignore_patterns(repo_root):
61
- """
62
- Reads .gitignore at the repo root and returns a list of patterns.
63
- Supports basic glob patterns; does NOT implement full git-ignore spec
64
- (negation, nested .gitignore, etc.) but covers the common cases.
65
- """
42
+ """Reads .gitignore at the repo root and returns a list of glob patterns."""
66
43
  gitignore_path = os.path.join(repo_root, ".gitignore")
67
44
  patterns = []
68
45
  if not os.path.isfile(gitignore_path):
@@ -73,7 +50,6 @@ def _load_gitignore_patterns(repo_root):
73
50
  line = line.strip()
74
51
  if not line or line.startswith("#"):
75
52
  continue
76
- # Ignore negation patterns (!) for simplicity
77
53
  if line.startswith("!"):
78
54
  continue
79
55
  patterns.append(line.rstrip("/"))
@@ -83,16 +59,12 @@ def _load_gitignore_patterns(repo_root):
83
59
 
84
60
 
85
61
  def _is_gitignored(rel_path, patterns):
86
- """
87
- Checks if a relative path matches any .gitignore pattern.
88
- """
62
+ """Checks if a relative path matches any .gitignore pattern."""
89
63
  parts = rel_path.replace("\\", "/").split("/")
90
64
  for pattern in patterns:
91
- # Directory-level match: check each path component
92
65
  for part in parts:
93
66
  if fnmatch(part, pattern):
94
67
  return True
95
- # Full-path match
96
68
  if fnmatch(rel_path.replace("\\", "/"), pattern):
97
69
  return True
98
70
  if fnmatch(rel_path.replace("\\", "/"), f"**/{pattern}"):
@@ -100,13 +72,10 @@ def _is_gitignored(rel_path, patterns):
100
72
  return False
101
73
 
102
74
 
103
- # ── Core functions ────────────────────────────────────────────────────────────
104
-
105
75
  def detect_repo_root(start_path=None):
106
76
  """
107
77
  Checks if the current directory contains ai.json or .git.
108
- Strictly restricted to the current path (no upward traversal) to avoid
109
- incorrectly detecting parent repos.
78
+ Strictly restricted to the current path (no upward traversal).
110
79
  """
111
80
  if not start_path:
112
81
  start_path = os.getcwd()
@@ -119,8 +88,6 @@ def detect_repo_root(start_path=None):
119
88
  raise Exception("No repository root found. Please run this command inside a Git repository or one initialized with 'codedthemes init'.")
120
89
 
121
90
 
122
-
123
-
124
91
  def zip_repo(repo_root):
125
92
  """
126
93
  Creates a temporary ZIP archive of the repository with aggressive filtering:
@@ -139,30 +106,26 @@ def zip_repo(repo_root):
139
106
 
140
107
  with zipfile.ZipFile(temp_zip.name, "w", zipfile.ZIP_DEFLATED) as z:
141
108
  for root, dirs, files in os.walk(repo_root):
142
- # Prune excluded directories (in-place)
143
109
  dirs[:] = [
144
110
  d for d in dirs
145
111
  if d not in EXCLUDE_DIRS
146
- and not d.startswith(".") # skip all hidden dirs (e.g. .env, .husky)
147
- or d in {".github"} # but keep .github
112
+ and not d.startswith(".")
113
+ or d in {".github"}
148
114
  ]
149
115
 
150
116
  for file in files:
151
117
  full_path = os.path.join(root, file)
152
118
  rel_path = os.path.relpath(full_path, repo_root).replace(os.sep, "/")
153
119
 
154
- # Skip by exact filename
155
120
  if file in EXCLUDE_FILES:
156
121
  skipped_count += 1
157
122
  continue
158
123
 
159
- # Skip by extension
160
124
  _, ext = os.path.splitext(file)
161
125
  if ext.lower() in EXCLUDE_EXTENSIONS:
162
126
  skipped_count += 1
163
127
  continue
164
128
 
165
- # Skip large files
166
129
  try:
167
130
  if os.path.getsize(full_path) > MAX_SINGLE_FILE_BYTES:
168
131
  logger.debug(f"Skipping large file: {rel_path}")
@@ -171,7 +134,6 @@ def zip_repo(repo_root):
171
134
  except OSError:
172
135
  continue
173
136
 
174
- # Skip .gitignore-matched paths
175
137
  if gitignore_patterns and _is_gitignored(rel_path, gitignore_patterns):
176
138
  skipped_count += 1
177
139
  continue
@@ -2,12 +2,13 @@ import os
2
2
  import json
3
3
  import hashlib
4
4
  from datetime import datetime
5
+
5
6
  from .repo_utils import EXCLUDE_DIRS
6
7
 
8
+
7
9
  class SyncManager:
8
- """
9
- Manages local file hashes to track manual changes and maintain synchronization state.
10
- """
10
+ """Manages local file hashes to track changes and maintain synchronization state."""
11
+
11
12
  def __init__(self):
12
13
  self.config_dir = os.path.expanduser("~/.codedthemes")
13
14
  self.config_file = os.path.join(self.config_dir, "workspace.json")
@@ -15,9 +16,7 @@ class SyncManager:
15
16
  self.workspaces = self.load()
16
17
 
17
18
  def load(self):
18
- """
19
- Loads the workspace synchronization state from the cloud config.
20
- """
19
+ """Loads the workspace synchronization state."""
21
20
  if os.path.exists(self.config_file):
22
21
  try:
23
22
  with open(self.config_file, 'r') as f: return json.load(f)
@@ -25,16 +24,12 @@ class SyncManager:
25
24
  return {}
26
25
 
27
26
  def save(self):
28
- """
29
- Saves the workspace synchronization state locally.
30
- """
27
+ """Saves the workspace synchronization state."""
31
28
  with open(self.config_file, 'w') as f:
32
29
  json.dump(self.workspaces, f, indent=2)
33
30
 
34
31
  def compute_hashes(self, repo_path):
35
- """
36
- Computes MD5 hashes for all relevant files in the repository.
37
- """
32
+ """Computes MD5 hashes for all relevant files in the repository."""
38
33
  hashes = {}
39
34
  for root, dirs, files in os.walk(repo_path):
40
35
  dirs[:] = [d for d in dirs if d not in EXCLUDE_DIRS]
@@ -50,9 +45,7 @@ class SyncManager:
50
45
  return hashes
51
46
 
52
47
  def update_sync_state(self, repo_path, workspace_id, user_id):
53
- """
54
- Updates the synchronization state with the latest file hashes.
55
- """
48
+ """Updates the synchronization state with the latest file hashes."""
56
49
  repo_key = f"{user_id}:{os.path.abspath(repo_path)}"
57
50
  self.workspaces[repo_key] = {
58
51
  "workspace_id": workspace_id,
@@ -63,9 +56,7 @@ class SyncManager:
63
56
  self.save()
64
57
 
65
58
  def get_changed_files(self, repo_path, user_id):
66
- """
67
- Identifies files that have been modified or deleted locally since the last sync.
68
- """
59
+ """Identifies files modified or deleted locally since the last sync."""
69
60
  repo_key = f"{user_id}:{os.path.abspath(repo_path)}"
70
61
  if repo_key not in self.workspaces: return []
71
62
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codedthemes-cli
3
- Version: 0.1.17
3
+ Version: 0.1.19
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.17"
7
+ version = "0.1.19"
8
8
  description = "CLI tool for Code Theme and Integration"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"