codedthemes-cli 0.1.17__tar.gz → 0.1.18__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.18
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):
@@ -37,7 +36,7 @@ def handle_login(server_url: str = None):
37
36
  email = input("Email: ").strip()
38
37
  license_key = input("License key: ").strip()
39
38
 
40
- # Idempotent check: Are we already logged in as this user?
39
+ # Idempotent check: already logged in as this user?
41
40
  if existing_token:
42
41
  try:
43
42
  decoded = jwt.decode(existing_token, options={"verify_signature": False})
@@ -69,7 +68,7 @@ def handle_login(server_url: str = None):
69
68
  print("✖ Login failed: No access token returned. Please check your credentials.")
70
69
  sys.exit(1)
71
70
 
72
- # LAZY CONFIG: Only save if login succeeded
71
+
73
72
  save_config({
74
73
  "access_token": token,
75
74
  "server_url": client.server_url,
@@ -114,12 +113,9 @@ def handle_logout():
114
113
  "license_key": license_key,
115
114
  "device_id": device_id
116
115
  })
117
- except Exception as e:
118
- # Silently fail if server logout fails, we still want to clear local state
116
+ except Exception:
119
117
  pass
120
118
 
121
- # Local cleanup: Remove config.json but KEEP device.json
122
- from .config import CONFIG_FILE
123
119
  if CONFIG_FILE.exists():
124
120
  CONFIG_FILE.unlink()
125
121
 
@@ -152,12 +148,7 @@ def handle_init():
152
148
  repo_abs_path = os.path.abspath(repo_root)
153
149
  repo_key = repo_abs_path.lower().replace(os.sep, "/")
154
150
 
155
- # Check if already initialized to avoid redundant uploads
156
151
  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
152
 
162
153
  print("📦 Zipping and uploading repository (this may take a moment)...")
163
154
  upload_result = client.upload_workspace(repo_abs_path, user_email)
@@ -186,10 +177,8 @@ def handle_init():
186
177
 
187
178
  print("Choose your editor and add the MCP server:\n")
188
179
 
189
- # ---------------- ANTIGRAVITY ----------------
190
180
  print("🔹 Antigravity")
191
181
  print(" MCP Servers → Manage → Config\n")
192
-
193
182
  print(' {')
194
183
  print(' "mcpServers": {')
195
184
  print(' "code-theme-mcp": {')
@@ -200,10 +189,8 @@ def handle_init():
200
189
  print(' }')
201
190
  print(' }\n')
202
191
 
203
- # ---------------- VS CODE ----------------
204
192
  print("🔹 VS Code")
205
193
  print(" Ctrl+Shift+P → MCP: Add MCP Server → http\n")
206
-
207
194
  print(' {')
208
195
  print(' "servers": {')
209
196
  print(' "code-theme-mcp": {')
@@ -215,10 +202,8 @@ def handle_init():
215
202
  print(' "inputs": []')
216
203
  print(' }\n')
217
204
 
218
- # ---------------- CURSOR ----------------
219
205
  print("🔹 Cursor")
220
206
  print(" Settings → Features → MCP Servers\n")
221
-
222
207
  print(' {')
223
208
  print(' "mcpServers": {')
224
209
  print(' "code-theme-mcp": {')
@@ -230,9 +215,7 @@ def handle_init():
230
215
  print(' }\n')
231
216
 
232
217
  print("✔ Once saved, your AI assistant will detect available tools automatically.\n")
233
-
234
218
  print('💡 Example: "Update the primary color to deep purple"')
235
-
236
219
  print("="*60)
237
220
 
238
221
  except Exception as e:
@@ -338,7 +321,7 @@ def handle_apply(query: str):
338
321
  last_workspaces = config.get("workspaces", {})
339
322
  workspace_id = last_workspaces.get(repo_key)
340
323
 
341
- # Verify or re-initialize workspace
324
+
342
325
  if workspace_id:
343
326
  try:
344
327
  check = client.call("check_workspace", {"workspace_id": workspace_id})
@@ -349,7 +332,6 @@ def handle_apply(query: str):
349
332
  sys.exit(0)
350
333
 
351
334
  if isinstance(check, dict) and check.get("status") == "ok":
352
- # print(f"✔ Using active workspace: {workspace_id}")
353
335
  pass
354
336
  else:
355
337
  workspace_id = None
@@ -361,7 +343,6 @@ def handle_apply(query: str):
361
343
  sys.exit(0)
362
344
  workspace_id = None
363
345
 
364
-
365
346
  if not workspace_id:
366
347
  print("📦 Zipping and uploading repository (this may take a moment)...")
367
348
  upload_result = client.upload_workspace(repo_abs_path, user_email)
@@ -371,7 +352,7 @@ def handle_apply(query: str):
371
352
  save_config(config)
372
353
  sync_manager.update_sync_state(repo_abs_path, workspace_id, user_email)
373
354
 
374
- # 1. Repository Analysis
355
+
375
356
  print("🚀 Analyzing repository...")
376
357
  analysis = client.call("repo_analyzer", {
377
358
  "repo_path": repo_abs_path,
@@ -388,7 +369,7 @@ def handle_apply(query: str):
388
369
  else:
389
370
  print(f"✔ {analysis}")
390
371
 
391
- # 2. Planning
372
+
392
373
  print("🔍 Detecting local changes...")
393
374
  local_changes = sync_manager.get_changed_files(repo_abs_path, user_email)
394
375
  if local_changes:
@@ -441,7 +422,7 @@ def handle_apply(query: str):
441
422
  return
442
423
  print("Please enter 'y' or 'n'.")
443
424
 
444
- # 3. Execution
425
+
445
426
  print("⚙ Executing plan...")
446
427
  result = client.call("execute_plan", {
447
428
  "query": query,
@@ -458,7 +439,7 @@ def handle_apply(query: str):
458
439
  print(f"✖ Execution failed: {error_msg}")
459
440
  return
460
441
 
461
- # 4. Local Patching
442
+
462
443
  res_data = result
463
444
  if isinstance(result, str):
464
445
  try: res_data = json.loads(result)
@@ -467,7 +448,7 @@ def handle_apply(query: str):
467
448
  updates = res_data.get("updates_for_local", [])
468
449
  deletes = res_data.get("deleted_files", [])
469
450
 
470
- # 1. Apply local updates
451
+
471
452
  for item in updates:
472
453
  rel_path = clean_remote_path(item.get("path"))
473
454
  code = item.get("code")
@@ -482,7 +463,7 @@ def handle_apply(query: str):
482
463
  except Exception as e:
483
464
  print(f"✖ Error writing {rel_path}: {e}")
484
465
 
485
- # 2. Apply local deletions
466
+
486
467
  for rel_path_raw in (deletes or []):
487
468
  rel_path = clean_remote_path(rel_path_raw)
488
469
  abs_path = os.path.normpath(os.path.join(repo_abs_path, rel_path.replace('/', os.sep)))
@@ -493,12 +474,12 @@ def handle_apply(query: str):
493
474
  print(f"✔ Deleted: {rel_path}")
494
475
  except: pass
495
476
 
496
- # 3. Synchronize state
477
+
497
478
  if updates or deletes:
498
479
  sync_manager.update_sync_state(repo_abs_path, workspace_id, user_email)
499
480
  print("✔ Local sync state updated.")
500
481
 
501
- # 4. Show Integration Report (Always show errors or details)
482
+
502
483
  _details_raw = res_data.get("details", [])
503
484
  details_list = _details_raw.get("report", []) if isinstance(_details_raw, dict) else (_details_raw if isinstance(_details_raw, list) else [])
504
485
  if details_list:
@@ -507,7 +488,7 @@ def handle_apply(query: str):
507
488
  status_icon = "✔" if item.get("success") else "✖"
508
489
  print(f" [{status_icon}] {item.get('file_path')}: {item.get('message', '')}")
509
490
 
510
- # 5. Output Final Message if no updates were applied
491
+
511
492
  if not updates and not deletes:
512
493
  msg = res_data.get('message', 'Changes applied successfully.')
513
494
  if isinstance(msg, str) and "AI MUST now sync" in msg:
@@ -529,7 +510,7 @@ def main():
529
510
  parser = argparse.ArgumentParser(prog="codedthemes", description="CodedThemes CLI client")
530
511
  parser.add_argument("-v", "--version", action="version", version=f"%(prog)s {__version__}")
531
512
 
532
- subparsers = parser.add_subparsers(dest="command", required=False) # Changed to False to allow --version to work alone
513
+ subparsers = parser.add_subparsers(dest="command", required=False)
533
514
 
534
515
 
535
516
  login_parser = subparsers.add_parser("login", help="Login to MCP server")
@@ -542,8 +523,6 @@ def main():
542
523
  subparsers.add_parser("reinit", help="Reactivate an evicted workspace and sync to cloud")
543
524
  subparsers.add_parser("logout", help="Log out from current device")
544
525
 
545
-
546
-
547
526
  args = parser.parse_args()
548
527
 
549
528
  if not args.command:
@@ -561,7 +540,5 @@ def main():
561
540
  elif args.command == "logout":
562
541
  handle_logout()
563
542
 
564
-
565
-
566
543
  if __name__ == "__main__":
567
544
  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.18
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.18"
8
8
  description = "CLI tool for Code Theme and Integration"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"