codedthemes-cli 0.1.0__tar.gz → 0.1.2__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.2/PKG-INFO +63 -0
- codedthemes_cli-0.1.2/README.md +53 -0
- {codedthemes_cli-0.1.0 → codedthemes_cli-0.1.2}/codedthemes/cli.py +48 -91
- {codedthemes_cli-0.1.0 → codedthemes_cli-0.1.2}/codedthemes/config.py +9 -0
- {codedthemes_cli-0.1.0 → codedthemes_cli-0.1.2}/codedthemes/mcp_client.py +20 -2
- codedthemes_cli-0.1.2/codedthemes/repo_utils.py +190 -0
- {codedthemes_cli-0.1.0 → codedthemes_cli-0.1.2}/codedthemes/sync_manager.py +18 -3
- codedthemes_cli-0.1.2/codedthemes_cli.egg-info/PKG-INFO +63 -0
- {codedthemes_cli-0.1.0 → codedthemes_cli-0.1.2}/pyproject.toml +1 -1
- codedthemes_cli-0.1.0/PKG-INFO +0 -49
- codedthemes_cli-0.1.0/README.md +0 -39
- codedthemes_cli-0.1.0/codedthemes/repo_utils.py +0 -40
- codedthemes_cli-0.1.0/codedthemes_cli.egg-info/PKG-INFO +0 -49
- {codedthemes_cli-0.1.0 → codedthemes_cli-0.1.2}/codedthemes/__init__.py +0 -0
- {codedthemes_cli-0.1.0 → codedthemes_cli-0.1.2}/codedthemes/patch_utils.py +0 -0
- {codedthemes_cli-0.1.0 → codedthemes_cli-0.1.2}/codedthemes_cli.egg-info/SOURCES.txt +0 -0
- {codedthemes_cli-0.1.0 → codedthemes_cli-0.1.2}/codedthemes_cli.egg-info/dependency_links.txt +0 -0
- {codedthemes_cli-0.1.0 → codedthemes_cli-0.1.2}/codedthemes_cli.egg-info/entry_points.txt +0 -0
- {codedthemes_cli-0.1.0 → codedthemes_cli-0.1.2}/codedthemes_cli.egg-info/requires.txt +0 -0
- {codedthemes_cli-0.1.0 → codedthemes_cli-0.1.2}/codedthemes_cli.egg-info/top_level.txt +0 -0
- {codedthemes_cli-0.1.0 → codedthemes_cli-0.1.2}/setup.cfg +0 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: codedthemes-cli
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: CLI tool for Code Theme and Integration
|
|
5
|
+
Author: codedthemes
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: requests
|
|
9
|
+
Requires-Dist: pyjwt
|
|
10
|
+
|
|
11
|
+
# CodedThemes CLI
|
|
12
|
+
|
|
13
|
+
CodedThemes CLI is a powerful command-line tool designed to integrate your local development environment with the CodedThemes MCP Server. It allows you to automate UI/UX modifications, refactor themes, and apply stylistic changes across your entire repository using AI-driven insights.
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- **Automated Theming**: Apply complex theme changes (e.g., branding updates) with simple natural language queries.
|
|
18
|
+
- **Local-to-Cloud Sync**: Securely sync your repository state to the CodedThemes engine for deep analysis.
|
|
19
|
+
- **Seamless Integration**: Automatically patches local files with surgical precision.
|
|
20
|
+
- **Sync Management**: Tracks local changes to ensure your manual edits are never overwritten.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
Install the CLI directly from PyPI:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install codedthemes-cli
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
### 1. Authentication
|
|
33
|
+
|
|
34
|
+
Authenticate with your CodedThemes account to enable secure communication with the MCP server.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
codedthemes login
|
|
38
|
+
```
|
|
39
|
+
*Enter your registered email and license key when prompted.*
|
|
40
|
+
|
|
41
|
+
### 2. Initialization (Optional)
|
|
42
|
+
|
|
43
|
+
Initialize your repository to establish a baseline for synchronization. This is recommended for first-time use in a project.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
codedthemes init
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 3. Applying Changes
|
|
50
|
+
|
|
51
|
+
Describe the changes you want to make in natural language. The CLI will analyze your repository, plan the changes, and ask for your approval before patching.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
codedthemes apply "Update the theme branding from Mantis to Berry"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Integration Report
|
|
58
|
+
|
|
59
|
+
After every successful `apply`, a granular report is generated showing exactly which files were modified, created, or deleted.
|
|
60
|
+
|
|
61
|
+
## Support
|
|
62
|
+
|
|
63
|
+
For issues or feature requests, please contact the CodedThemes support team.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# CodedThemes CLI
|
|
2
|
+
|
|
3
|
+
CodedThemes CLI is a powerful command-line tool designed to integrate your local development environment with the CodedThemes MCP Server. It allows you to automate UI/UX modifications, refactor themes, and apply stylistic changes across your entire repository using AI-driven insights.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Automated Theming**: Apply complex theme changes (e.g., branding updates) with simple natural language queries.
|
|
8
|
+
- **Local-to-Cloud Sync**: Securely sync your repository state to the CodedThemes engine for deep analysis.
|
|
9
|
+
- **Seamless Integration**: Automatically patches local files with surgical precision.
|
|
10
|
+
- **Sync Management**: Tracks local changes to ensure your manual edits are never overwritten.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
Install the CLI directly from PyPI:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install codedthemes-cli
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### 1. Authentication
|
|
23
|
+
|
|
24
|
+
Authenticate with your CodedThemes account to enable secure communication with the MCP server.
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
codedthemes login
|
|
28
|
+
```
|
|
29
|
+
*Enter your registered email and license key when prompted.*
|
|
30
|
+
|
|
31
|
+
### 2. Initialization (Optional)
|
|
32
|
+
|
|
33
|
+
Initialize your repository to establish a baseline for synchronization. This is recommended for first-time use in a project.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
codedthemes init
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 3. Applying Changes
|
|
40
|
+
|
|
41
|
+
Describe the changes you want to make in natural language. The CLI will analyze your repository, plan the changes, and ask for your approval before patching.
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
codedthemes apply "Update the theme branding from Mantis to Berry"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Integration Report
|
|
48
|
+
|
|
49
|
+
After every successful `apply`, a granular report is generated showing exactly which files were modified, created, or deleted.
|
|
50
|
+
|
|
51
|
+
## Support
|
|
52
|
+
|
|
53
|
+
For issues or feature requests, please contact the CodedThemes support team.
|
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
import sys
|
|
3
3
|
import os
|
|
4
|
-
import jwt
|
|
5
|
-
import requests
|
|
6
4
|
import json
|
|
7
5
|
import shutil
|
|
8
6
|
from getpass import getpass
|
|
9
7
|
|
|
8
|
+
import jwt
|
|
9
|
+
import requests
|
|
10
|
+
|
|
10
11
|
from .config import save_config, load_config
|
|
11
12
|
from .mcp_client import MCPClient
|
|
12
13
|
from .repo_utils import detect_repo_root, zip_repo
|
|
13
|
-
from .patch_utils import apply_patch
|
|
14
14
|
from .sync_manager import SyncManager
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def handle_login(server_url: str = None):
|
|
18
|
+
"""
|
|
19
|
+
Handles user authentication with the MCP server.
|
|
20
|
+
"""
|
|
18
21
|
email = input("Email: ").strip()
|
|
19
22
|
license_key = input("License key: ").strip()
|
|
20
23
|
|
|
@@ -28,33 +31,28 @@ def handle_login(server_url: str = None):
|
|
|
28
31
|
"license_key": license_key
|
|
29
32
|
})
|
|
30
33
|
|
|
31
|
-
|
|
32
34
|
if isinstance(result, dict):
|
|
33
|
-
# The server might have returned {"status": "error", "message": ...}
|
|
34
35
|
if result.get("status") == "error":
|
|
35
36
|
print(f"✖ Login failed: {result.get('message', 'Unknown error')}")
|
|
36
37
|
sys.exit(1)
|
|
37
|
-
# The app.py bridge wraps string returns in {"message": "..."}
|
|
38
38
|
elif "message" in result and "Login failed" in result["message"]:
|
|
39
39
|
print(f"✖ {result['message']}")
|
|
40
40
|
sys.exit(1)
|
|
41
41
|
|
|
42
|
-
#
|
|
42
|
+
# Get token from result or local fallback
|
|
43
43
|
token = result.get("access_token") if isinstance(result, dict) else None
|
|
44
44
|
|
|
45
|
-
# 2. Fallback: Try to read from local file (because server now returns plain text string)
|
|
46
45
|
if not token:
|
|
47
46
|
token_file = os.path.expanduser("~/.mcp_token")
|
|
48
47
|
if os.path.exists(token_file):
|
|
49
48
|
with open(token_file, 'r') as f:
|
|
50
49
|
token = f.read().strip()
|
|
51
50
|
|
|
52
|
-
# 3. Final Verification: Throw error if we STILL don't have a token
|
|
53
51
|
if not token:
|
|
54
52
|
print("✖ Login failed: No access token returned. Please check your credentials.")
|
|
55
53
|
sys.exit(1)
|
|
56
54
|
|
|
57
|
-
#
|
|
55
|
+
# Store token for both local MCP and CLI config
|
|
58
56
|
token_file = os.path.expanduser("~/.mcp_token")
|
|
59
57
|
try:
|
|
60
58
|
with open(token_file, 'w') as f:
|
|
@@ -78,6 +76,9 @@ def handle_login(server_url: str = None):
|
|
|
78
76
|
|
|
79
77
|
|
|
80
78
|
def handle_init():
|
|
79
|
+
"""
|
|
80
|
+
Initializes the local repository and syncs it with the cloud.
|
|
81
|
+
"""
|
|
81
82
|
try:
|
|
82
83
|
repo_root = detect_repo_root()
|
|
83
84
|
print(f"🔍 Found repository at {repo_root}")
|
|
@@ -87,7 +88,6 @@ def handle_init():
|
|
|
87
88
|
print("✖ Not logged in. Please run 'codedthemes login' first.")
|
|
88
89
|
sys.exit(1)
|
|
89
90
|
|
|
90
|
-
# 0. Get user email from token for registry profiling
|
|
91
91
|
user_email = "unknown_user"
|
|
92
92
|
try:
|
|
93
93
|
decoded = jwt.decode(client.token, options={"verify_signature": False})
|
|
@@ -104,14 +104,12 @@ def handle_init():
|
|
|
104
104
|
print("✖ Upload failed: No workspace ID returned.")
|
|
105
105
|
sys.exit(1)
|
|
106
106
|
|
|
107
|
-
# 1. Save workspace mapping in config.json
|
|
108
107
|
config = load_config()
|
|
109
108
|
last_workspaces = config.get("workspaces", {})
|
|
110
109
|
last_workspaces[repo_abs_path] = workspace_id
|
|
111
110
|
config["workspaces"] = last_workspaces
|
|
112
111
|
save_config(config)
|
|
113
112
|
|
|
114
|
-
# 2. Initialize sync state (HASHES) in workspace.json
|
|
115
113
|
sync_manager = SyncManager()
|
|
116
114
|
sync_manager.update_sync_state(repo_abs_path, workspace_id, user_email)
|
|
117
115
|
|
|
@@ -125,16 +123,15 @@ def handle_init():
|
|
|
125
123
|
|
|
126
124
|
|
|
127
125
|
def handle_apply(query: str):
|
|
126
|
+
"""
|
|
127
|
+
Plans and executes changes based on a natural language query.
|
|
128
|
+
"""
|
|
128
129
|
try:
|
|
129
130
|
repo_root = detect_repo_root()
|
|
131
|
+
repo_abs_path = str(repo_root)
|
|
130
132
|
print(f"🔍 Found repository at {repo_root}")
|
|
131
133
|
|
|
132
|
-
# Note: zipping logic is there but we are calling tools sequentially as per requirements
|
|
133
|
-
# zip_path = zip_repo(repo_root)
|
|
134
|
-
|
|
135
134
|
client = MCPClient()
|
|
136
|
-
|
|
137
|
-
# 0. Get user email from token for registry profiling
|
|
138
135
|
user_email = "unknown_user"
|
|
139
136
|
if client.token:
|
|
140
137
|
try:
|
|
@@ -144,17 +141,14 @@ def handle_apply(query: str):
|
|
|
144
141
|
pass
|
|
145
142
|
|
|
146
143
|
sync_manager = SyncManager()
|
|
147
|
-
|
|
148
144
|
config = load_config()
|
|
149
145
|
last_workspaces = config.get("workspaces", {})
|
|
150
|
-
repo_abs_path = str(repo_root)
|
|
151
146
|
workspace_id = last_workspaces.get(repo_abs_path)
|
|
152
147
|
|
|
153
|
-
#
|
|
148
|
+
# Verify or re-initialize workspace
|
|
154
149
|
if workspace_id:
|
|
155
150
|
logger_debug = False # toggle for debug
|
|
156
151
|
try:
|
|
157
|
-
# Quick check if workspace is still on server
|
|
158
152
|
check = client.call("check_workspace", {"workspace_id": workspace_id})
|
|
159
153
|
if isinstance(check, dict) and check.get("status") == "ok":
|
|
160
154
|
print(f"✔ Using active workspace: {workspace_id}")
|
|
@@ -167,16 +161,12 @@ def handle_apply(query: str):
|
|
|
167
161
|
print("📦 Zipping and uploading repository (this may take a moment)...")
|
|
168
162
|
upload_result = client.upload_workspace(repo_abs_path, user_email)
|
|
169
163
|
workspace_id = upload_result.get("workspace_id")
|
|
170
|
-
|
|
171
|
-
# Save workspace_id for future reuse
|
|
172
164
|
last_workspaces[repo_abs_path] = workspace_id
|
|
173
165
|
config["workspaces"] = last_workspaces
|
|
174
166
|
save_config(config)
|
|
175
|
-
|
|
176
|
-
# Init sync state locally
|
|
177
167
|
sync_manager.update_sync_state(repo_abs_path, workspace_id, user_email)
|
|
178
168
|
|
|
179
|
-
#
|
|
169
|
+
# 1. Repository Analysis
|
|
180
170
|
print("🚀 Analyzing repository...")
|
|
181
171
|
analysis = client.call("repo_analyzer", {
|
|
182
172
|
"repo_path": repo_abs_path,
|
|
@@ -188,13 +178,12 @@ def handle_apply(query: str):
|
|
|
188
178
|
return
|
|
189
179
|
|
|
190
180
|
if isinstance(analysis, str):
|
|
191
|
-
# The tool might return a string message shown in CLI
|
|
192
181
|
if "Analysis Skipped" in analysis:
|
|
193
182
|
print("✔ Repository is up-to-date. Skipping upload.")
|
|
194
183
|
else:
|
|
195
184
|
print(f"✔ {analysis}")
|
|
196
185
|
|
|
197
|
-
#
|
|
186
|
+
# 2. Planning
|
|
198
187
|
print("🔍 Detecting local changes...")
|
|
199
188
|
local_changes = sync_manager.get_changed_files(repo_abs_path, user_email)
|
|
200
189
|
if local_changes:
|
|
@@ -204,7 +193,7 @@ def handle_apply(query: str):
|
|
|
204
193
|
plan = client.call("plan_changes", {
|
|
205
194
|
"query": query,
|
|
206
195
|
"workspace_id": workspace_id,
|
|
207
|
-
"repo_path":
|
|
196
|
+
"repo_path": repo_abs_path,
|
|
208
197
|
"changes_from_cli": local_changes
|
|
209
198
|
})
|
|
210
199
|
|
|
@@ -213,54 +202,50 @@ def handle_apply(query: str):
|
|
|
213
202
|
return
|
|
214
203
|
|
|
215
204
|
target_files = plan.get("files_to_modify", [])
|
|
216
|
-
|
|
205
|
+
instructions = plan.get("plan_instructions", [])
|
|
217
206
|
files_to_create = plan.get("files_to_create", [])
|
|
218
207
|
files_to_delete = plan.get("files_to_delete", [])
|
|
219
208
|
|
|
220
|
-
if not target_files
|
|
209
|
+
if not any([target_files, files_to_create, files_to_delete, instructions]):
|
|
221
210
|
print("Information: No changes planned.")
|
|
222
211
|
return
|
|
223
212
|
|
|
224
213
|
print("\n📝 Execution Plan:")
|
|
225
|
-
if
|
|
214
|
+
if instructions:
|
|
226
215
|
print("Instructions:")
|
|
227
|
-
for idx, inst in enumerate(
|
|
216
|
+
for idx, inst in enumerate(instructions, 1):
|
|
228
217
|
print(f" {idx}. {inst}")
|
|
229
218
|
|
|
230
219
|
if files_to_create:
|
|
231
220
|
print("\nFiles to create:")
|
|
232
|
-
for f in files_to_create:
|
|
233
|
-
|
|
234
|
-
|
|
221
|
+
for f in files_to_create: print(f" + {f}")
|
|
222
|
+
|
|
223
|
+
if target_files:
|
|
235
224
|
print("\nFiles to modify:")
|
|
236
|
-
for f in target_files:
|
|
237
|
-
print(f" ~ {f}")
|
|
225
|
+
for f in target_files: print(f" ~ {f}")
|
|
238
226
|
|
|
239
227
|
if files_to_delete:
|
|
240
228
|
print("\nFiles to delete:")
|
|
241
|
-
for f in files_to_delete:
|
|
242
|
-
print(f" - {f}")
|
|
229
|
+
for f in files_to_delete: print(f" - {f}")
|
|
243
230
|
|
|
244
231
|
while True:
|
|
245
232
|
choice = input("\nDo you want to proceed with this plan? (y/n): ").strip().lower()
|
|
246
|
-
if choice in ['y', 'yes']:
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
print("✖ Pipeline terminated by user. Please provide a new query.")
|
|
233
|
+
if choice in ['y', 'yes']: break
|
|
234
|
+
if choice in ['n', 'no']:
|
|
235
|
+
print("✖ Pipeline terminated by user.")
|
|
250
236
|
return
|
|
251
|
-
|
|
252
|
-
print("Please enter 'y' for yes or 'n' for no.")
|
|
237
|
+
print("Please enter 'y' or 'n'.")
|
|
253
238
|
|
|
254
|
-
#
|
|
239
|
+
# 3. Execution
|
|
255
240
|
print("⚙ Executing plan...")
|
|
256
241
|
result = client.call("execute_plan", {
|
|
257
242
|
"query": query,
|
|
258
|
-
"plan_instructions":
|
|
243
|
+
"plan_instructions": instructions,
|
|
259
244
|
"files_to_modify": target_files,
|
|
260
245
|
"files_to_create": files_to_create,
|
|
261
246
|
"files_to_delete": files_to_delete,
|
|
262
247
|
"workspace_id": workspace_id,
|
|
263
|
-
"repo_path":
|
|
248
|
+
"repo_path": repo_abs_path
|
|
264
249
|
})
|
|
265
250
|
|
|
266
251
|
if not result or (isinstance(result, dict) and (result.get("status") == "error" or "detail" in result)):
|
|
@@ -268,8 +253,7 @@ def handle_apply(query: str):
|
|
|
268
253
|
print(f"✖ Execution failed: {error_msg}")
|
|
269
254
|
return
|
|
270
255
|
|
|
271
|
-
#
|
|
272
|
-
# Ensure we are looking at a dictionary
|
|
256
|
+
# 4. Local Patching
|
|
273
257
|
res_data = result
|
|
274
258
|
if isinstance(result, str):
|
|
275
259
|
try: res_data = json.loads(result)
|
|
@@ -278,67 +262,42 @@ def handle_apply(query: str):
|
|
|
278
262
|
updates = res_data.get("updates_for_local", [])
|
|
279
263
|
deletes = res_data.get("deleted_files", [])
|
|
280
264
|
|
|
281
|
-
if not updates:
|
|
282
|
-
print(" (!) No file updates received in the response payload.")
|
|
283
|
-
|
|
284
265
|
if updates or deletes:
|
|
285
|
-
# 1. Update/Create files
|
|
286
266
|
for item in updates:
|
|
287
267
|
rel_path, code = item.get("path"), item.get("code")
|
|
288
268
|
if not rel_path or code is None: continue
|
|
289
|
-
|
|
290
|
-
# Normalize path for Windows
|
|
291
|
-
clean_rel_path = rel_path.replace('/', os.sep)
|
|
292
|
-
abs_path = os.path.abspath(os.path.join(str(repo_root), clean_rel_path))
|
|
269
|
+
abs_path = os.path.abspath(os.path.join(repo_abs_path, rel_path.replace('/', os.sep)))
|
|
293
270
|
|
|
294
271
|
try:
|
|
295
272
|
os.makedirs(os.path.dirname(abs_path), exist_ok=True)
|
|
296
273
|
with open(abs_path, 'w', encoding='utf-8') as f:
|
|
297
274
|
f.write(code)
|
|
298
|
-
print(f"✔ Updated
|
|
299
|
-
except Exception as
|
|
300
|
-
print(f"✖ Error writing {rel_path}: {
|
|
275
|
+
print(f"✔ Updated: {rel_path}")
|
|
276
|
+
except Exception as e:
|
|
277
|
+
print(f"✖ Error writing {rel_path}: {e}")
|
|
301
278
|
|
|
302
|
-
# 2. Delete files
|
|
303
279
|
for rel_path in (deletes or []):
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
if os.path.exists(abs_path) and abs_path.startswith(os.path.abspath(str(repo_root))):
|
|
280
|
+
abs_path = os.path.normpath(os.path.join(repo_abs_path, rel_path.replace('/', os.sep)))
|
|
281
|
+
if os.path.exists(abs_path) and abs_path.startswith(os.path.abspath(repo_abs_path)):
|
|
307
282
|
try:
|
|
308
283
|
if os.path.isfile(abs_path): os.remove(abs_path)
|
|
309
284
|
elif os.path.isdir(abs_path): shutil.rmtree(abs_path)
|
|
310
|
-
print(f"✔ Deleted
|
|
285
|
+
print(f"✔ Deleted: {rel_path}")
|
|
311
286
|
except: pass
|
|
312
287
|
|
|
313
|
-
# Finally, update the sync state so these aren't treated as local user changes next time
|
|
314
288
|
sync_manager.update_sync_state(repo_abs_path, workspace_id, user_email)
|
|
315
289
|
print("✔ Local sync state updated.")
|
|
316
290
|
|
|
317
|
-
# Display granular report details if available
|
|
318
291
|
_details_raw = res_data.get("details", [])
|
|
319
292
|
details_list = _details_raw.get("report", []) if isinstance(_details_raw, dict) else (_details_raw if isinstance(_details_raw, list) else [])
|
|
320
293
|
if details_list:
|
|
321
294
|
print("\n📊 Integration Report:")
|
|
322
295
|
for item in details_list:
|
|
323
296
|
status_icon = "✔" if item.get("success") else "✖"
|
|
324
|
-
|
|
325
|
-
msg = item.get("message", "")
|
|
326
|
-
print(f" [{status_icon}] {f_name}: {msg}")
|
|
297
|
+
print(f" [{status_icon}] {item.get('file_path')}: {item.get('message', '')}")
|
|
327
298
|
else:
|
|
328
|
-
|
|
329
|
-
msg = res_data.get("message", "Changes applied successfully (no local updates needed).")
|
|
330
|
-
print(f"✔ {msg}")
|
|
299
|
+
print(f"✔ {res_data.get('message', 'Changes applied successfully.')}")
|
|
331
300
|
|
|
332
|
-
# If there was a report but no updates (already up to date), display why
|
|
333
|
-
_details_raw = res_data.get("details", [])
|
|
334
|
-
details_list = _details_raw.get("report", []) if isinstance(_details_raw, dict) else (_details_raw if isinstance(_details_raw, list) else [])
|
|
335
|
-
if details_list:
|
|
336
|
-
print("\n📊 Integration Report (Files already up-to-date):")
|
|
337
|
-
for item in details_list:
|
|
338
|
-
status_icon = "✔" if item.get("success") else "✖"
|
|
339
|
-
f_name = item.get("file_path")
|
|
340
|
-
msg = item.get("message", "")
|
|
341
|
-
print(f" [{status_icon}] {f_name}: {msg}")
|
|
342
301
|
except Exception as e:
|
|
343
302
|
print(f"✖ Error: {e}")
|
|
344
303
|
sys.exit(1)
|
|
@@ -346,15 +305,15 @@ def handle_apply(query: str):
|
|
|
346
305
|
|
|
347
306
|
def main():
|
|
348
307
|
parser = argparse.ArgumentParser(prog="codedthemes", description="CodedThemes CLI client")
|
|
349
|
-
subparsers = parser.add_subparsers(dest="command")
|
|
308
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
350
309
|
|
|
351
310
|
login_parser = subparsers.add_parser("login", help="Login to MCP server")
|
|
352
311
|
login_parser.add_argument("--server", help="MCP server URL")
|
|
353
312
|
|
|
354
313
|
apply_parser = subparsers.add_parser("apply", help="Apply changes based on a query")
|
|
355
|
-
apply_parser.add_argument("query", help="
|
|
314
|
+
apply_parser.add_argument("query", help="Description of changes to make")
|
|
356
315
|
|
|
357
|
-
|
|
316
|
+
subparsers.add_parser("init", help="Initialize repository and sync to cloud")
|
|
358
317
|
|
|
359
318
|
args = parser.parse_args()
|
|
360
319
|
|
|
@@ -364,8 +323,6 @@ def main():
|
|
|
364
323
|
handle_init()
|
|
365
324
|
elif args.command == "apply":
|
|
366
325
|
handle_apply(args.query)
|
|
367
|
-
else:
|
|
368
|
-
parser.print_help()
|
|
369
326
|
|
|
370
327
|
|
|
371
328
|
if __name__ == "__main__":
|
|
@@ -7,16 +7,25 @@ CONFIG_FILE = CONFIG_DIR / "config.json"
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def ensure_config_dir():
|
|
10
|
+
"""
|
|
11
|
+
Ensures the configuration directory exists.
|
|
12
|
+
"""
|
|
10
13
|
CONFIG_DIR.mkdir(exist_ok=True)
|
|
11
14
|
|
|
12
15
|
|
|
13
16
|
def save_config(data: dict):
|
|
17
|
+
"""
|
|
18
|
+
Saves the provided configuration data to the config file.
|
|
19
|
+
"""
|
|
14
20
|
ensure_config_dir()
|
|
15
21
|
with open(CONFIG_FILE, "w") as f:
|
|
16
22
|
json.dump(data, f, indent=2)
|
|
17
23
|
|
|
18
24
|
|
|
19
25
|
def load_config():
|
|
26
|
+
"""
|
|
27
|
+
Loads the configuration data from the config file.
|
|
28
|
+
"""
|
|
20
29
|
if not CONFIG_FILE.exists():
|
|
21
30
|
return {}
|
|
22
31
|
with open(CONFIG_FILE, "r") as f:
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import requests
|
|
3
3
|
from .config import load_config
|
|
4
|
+
from .repo_utils import zip_repo
|
|
4
5
|
|
|
5
6
|
class MCPClient:
|
|
7
|
+
"""
|
|
8
|
+
Client for interacting with the CodedThemes MCP Server.
|
|
9
|
+
"""
|
|
6
10
|
def __init__(self):
|
|
7
11
|
config = load_config()
|
|
8
12
|
self.server_url = (
|
|
@@ -13,8 +17,9 @@ class MCPClient:
|
|
|
13
17
|
self.token = config.get("access_token")
|
|
14
18
|
|
|
15
19
|
def upload_workspace(self, repo_path: str, user_id: str):
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
"""
|
|
21
|
+
Zips and uploads the repository to create a new workspace.
|
|
22
|
+
"""
|
|
18
23
|
zip_path = zip_repo(repo_path)
|
|
19
24
|
repo_name = os.path.basename(repo_path)
|
|
20
25
|
|
|
@@ -35,6 +40,16 @@ class MCPClient:
|
|
|
35
40
|
timeout=300
|
|
36
41
|
)
|
|
37
42
|
|
|
43
|
+
if response.status_code == 413:
|
|
44
|
+
zip_size_mb = os.path.getsize(zip_path) / (1024 * 1024)
|
|
45
|
+
raise Exception(
|
|
46
|
+
f"Upload failed: File is too large (413). Zip size: {zip_size_mb:.1f} MB.\n"
|
|
47
|
+
" Please reduce your repository size:\n"
|
|
48
|
+
" • Add large files/folders to .gitignore\n"
|
|
49
|
+
" • Remove binary assets (images, videos, fonts)\n"
|
|
50
|
+
" • Delete unused dependencies"
|
|
51
|
+
)
|
|
52
|
+
|
|
38
53
|
if response.status_code != 200:
|
|
39
54
|
raise Exception(f"Upload failed: {response.status_code} - {response.text}")
|
|
40
55
|
|
|
@@ -44,6 +59,9 @@ class MCPClient:
|
|
|
44
59
|
os.remove(zip_path)
|
|
45
60
|
|
|
46
61
|
def call(self, tool_name: str, payload: dict):
|
|
62
|
+
"""
|
|
63
|
+
Calls a remote MCP tool.
|
|
64
|
+
"""
|
|
47
65
|
headers = {}
|
|
48
66
|
if self.token:
|
|
49
67
|
headers["Authorization"] = f"Bearer {self.token}"
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import zipfile
|
|
3
|
+
import tempfile
|
|
4
|
+
import logging
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from fnmatch import fnmatch
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger("codedthemes")
|
|
9
|
+
|
|
10
|
+
# ── Shared exclusion constants (used by both zip_repo and SyncManager) ────────
|
|
11
|
+
|
|
12
|
+
EXCLUDE_DIRS = {
|
|
13
|
+
# Version control
|
|
14
|
+
".git", ".svn", ".hg",
|
|
15
|
+
# Dependencies
|
|
16
|
+
"node_modules", "bower_components", "vendor", "venv", ".venv", "env",
|
|
17
|
+
# Build outputs
|
|
18
|
+
"build", "dist", "out", "target", ".output", "_build",
|
|
19
|
+
# Framework caches
|
|
20
|
+
".next", ".nuxt", ".svelte-kit", ".angular", ".turbo",
|
|
21
|
+
".parcel-cache", ".cache", ".temp", ".tmp",
|
|
22
|
+
# IDE / editor
|
|
23
|
+
".idea", ".vscode", ".vs",
|
|
24
|
+
# Python
|
|
25
|
+
"__pycache__", ".tox", ".mypy_cache", ".pytest_cache", ".ruff_cache",
|
|
26
|
+
# Testing
|
|
27
|
+
"coverage", ".nyc_output", "htmlcov",
|
|
28
|
+
# Misc
|
|
29
|
+
".terraform", ".serverless",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
EXCLUDE_EXTENSIONS = {
|
|
33
|
+
# Images
|
|
34
|
+
".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".webp", ".ico", ".svg",
|
|
35
|
+
# Fonts
|
|
36
|
+
".woff", ".woff2", ".ttf", ".eot", ".otf",
|
|
37
|
+
# Video / audio
|
|
38
|
+
".mp4", ".webm", ".avi", ".mov", ".mp3", ".wav", ".ogg",
|
|
39
|
+
# Archives
|
|
40
|
+
".zip", ".tar", ".gz", ".rar", ".7z",
|
|
41
|
+
# Documents
|
|
42
|
+
".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx",
|
|
43
|
+
# Source maps
|
|
44
|
+
".map",
|
|
45
|
+
# Compiled / binary
|
|
46
|
+
".exe", ".dll", ".so", ".dylib", ".o", ".pyc", ".pyo", ".class",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
EXCLUDE_FILES = {
|
|
50
|
+
"package-lock.json", "yarn.lock", "pnpm-lock.yaml",
|
|
51
|
+
"composer.lock", "Gemfile.lock", "poetry.lock",
|
|
52
|
+
".DS_Store", "Thumbs.db", "desktop.ini",
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
MAX_SINGLE_FILE_BYTES = 2 * 1024 * 1024 # 2 MB
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# ── .gitignore parsing ────────────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
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
|
+
"""
|
|
66
|
+
gitignore_path = os.path.join(repo_root, ".gitignore")
|
|
67
|
+
patterns = []
|
|
68
|
+
if not os.path.isfile(gitignore_path):
|
|
69
|
+
return patterns
|
|
70
|
+
try:
|
|
71
|
+
with open(gitignore_path, "r", encoding="utf-8", errors="ignore") as f:
|
|
72
|
+
for line in f:
|
|
73
|
+
line = line.strip()
|
|
74
|
+
if not line or line.startswith("#"):
|
|
75
|
+
continue
|
|
76
|
+
# Ignore negation patterns (!) for simplicity
|
|
77
|
+
if line.startswith("!"):
|
|
78
|
+
continue
|
|
79
|
+
patterns.append(line.rstrip("/"))
|
|
80
|
+
except Exception:
|
|
81
|
+
pass
|
|
82
|
+
return patterns
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _is_gitignored(rel_path, patterns):
|
|
86
|
+
"""
|
|
87
|
+
Checks if a relative path matches any .gitignore pattern.
|
|
88
|
+
"""
|
|
89
|
+
parts = rel_path.replace("\\", "/").split("/")
|
|
90
|
+
for pattern in patterns:
|
|
91
|
+
# Directory-level match: check each path component
|
|
92
|
+
for part in parts:
|
|
93
|
+
if fnmatch(part, pattern):
|
|
94
|
+
return True
|
|
95
|
+
# Full-path match
|
|
96
|
+
if fnmatch(rel_path.replace("\\", "/"), pattern):
|
|
97
|
+
return True
|
|
98
|
+
if fnmatch(rel_path.replace("\\", "/"), f"**/{pattern}"):
|
|
99
|
+
return True
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# ── Core functions ────────────────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
def detect_repo_root(start_path=None):
|
|
106
|
+
"""
|
|
107
|
+
Traverses up from start_path to find a repository root containing
|
|
108
|
+
package.json, pyproject.toml, ai.json, or .git.
|
|
109
|
+
"""
|
|
110
|
+
if not start_path:
|
|
111
|
+
start_path = os.getcwd()
|
|
112
|
+
|
|
113
|
+
current = Path(start_path).resolve()
|
|
114
|
+
|
|
115
|
+
while current != current.parent:
|
|
116
|
+
if any((current / f).exists() for f in ["package.json", "pyproject.toml", "ai.json", ".git"]):
|
|
117
|
+
return current
|
|
118
|
+
current = current.parent
|
|
119
|
+
|
|
120
|
+
raise Exception("No repository root found.")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def zip_repo(repo_root):
|
|
124
|
+
"""
|
|
125
|
+
Creates a temporary ZIP archive of the repository with aggressive filtering:
|
|
126
|
+
- Excludes common dependency / build / cache directories
|
|
127
|
+
- Excludes binary, media, font, and archive files by extension
|
|
128
|
+
- Excludes lock files
|
|
129
|
+
- Skips files larger than 2 MB
|
|
130
|
+
- Respects .gitignore patterns at the repo root
|
|
131
|
+
"""
|
|
132
|
+
temp_zip = tempfile.NamedTemporaryFile(delete=False, suffix=".zip")
|
|
133
|
+
temp_zip.close()
|
|
134
|
+
|
|
135
|
+
gitignore_patterns = _load_gitignore_patterns(str(repo_root))
|
|
136
|
+
file_count = 0
|
|
137
|
+
skipped_count = 0
|
|
138
|
+
|
|
139
|
+
with zipfile.ZipFile(temp_zip.name, "w", zipfile.ZIP_DEFLATED) as z:
|
|
140
|
+
for root, dirs, files in os.walk(repo_root):
|
|
141
|
+
# Prune excluded directories (in-place)
|
|
142
|
+
dirs[:] = [
|
|
143
|
+
d for d in dirs
|
|
144
|
+
if d not in EXCLUDE_DIRS
|
|
145
|
+
and not d.startswith(".") # skip all hidden dirs (e.g. .env, .husky)
|
|
146
|
+
or d in {".github"} # but keep .github
|
|
147
|
+
]
|
|
148
|
+
|
|
149
|
+
for file in files:
|
|
150
|
+
full_path = os.path.join(root, file)
|
|
151
|
+
rel_path = os.path.relpath(full_path, repo_root).replace(os.sep, "/")
|
|
152
|
+
|
|
153
|
+
# Skip by exact filename
|
|
154
|
+
if file in EXCLUDE_FILES:
|
|
155
|
+
skipped_count += 1
|
|
156
|
+
continue
|
|
157
|
+
|
|
158
|
+
# Skip by extension
|
|
159
|
+
_, ext = os.path.splitext(file)
|
|
160
|
+
if ext.lower() in EXCLUDE_EXTENSIONS:
|
|
161
|
+
skipped_count += 1
|
|
162
|
+
continue
|
|
163
|
+
|
|
164
|
+
# Skip large files
|
|
165
|
+
try:
|
|
166
|
+
if os.path.getsize(full_path) > MAX_SINGLE_FILE_BYTES:
|
|
167
|
+
logger.debug(f"Skipping large file: {rel_path}")
|
|
168
|
+
skipped_count += 1
|
|
169
|
+
continue
|
|
170
|
+
except OSError:
|
|
171
|
+
continue
|
|
172
|
+
|
|
173
|
+
# Skip .gitignore-matched paths
|
|
174
|
+
if gitignore_patterns and _is_gitignored(rel_path, gitignore_patterns):
|
|
175
|
+
skipped_count += 1
|
|
176
|
+
continue
|
|
177
|
+
|
|
178
|
+
z.write(full_path, rel_path)
|
|
179
|
+
file_count += 1
|
|
180
|
+
|
|
181
|
+
zip_size_mb = os.path.getsize(temp_zip.name) / (1024 * 1024)
|
|
182
|
+
logger.info(f"Zip created: {file_count} files, {zip_size_mb:.1f} MB (skipped {skipped_count})")
|
|
183
|
+
|
|
184
|
+
if zip_size_mb > 50:
|
|
185
|
+
logger.warning(
|
|
186
|
+
f"Zip file is {zip_size_mb:.1f} MB — upload may be slow or fail. "
|
|
187
|
+
"Consider adding large assets to .gitignore."
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
return temp_zip.name
|
|
@@ -2,11 +2,11 @@ import os
|
|
|
2
2
|
import json
|
|
3
3
|
import hashlib
|
|
4
4
|
from datetime import datetime
|
|
5
|
+
from .repo_utils import EXCLUDE_DIRS
|
|
5
6
|
|
|
6
7
|
class SyncManager:
|
|
7
8
|
"""
|
|
8
|
-
Manages local file hashes to
|
|
9
|
-
Ensures `.codedthemes/workspace.json` is accurately tracked across CLI runs.
|
|
9
|
+
Manages local file hashes to track manual changes and maintain synchronization state.
|
|
10
10
|
"""
|
|
11
11
|
def __init__(self):
|
|
12
12
|
self.config_dir = os.path.expanduser("~/.codedthemes")
|
|
@@ -15,6 +15,9 @@ class SyncManager:
|
|
|
15
15
|
self.workspaces = self.load()
|
|
16
16
|
|
|
17
17
|
def load(self):
|
|
18
|
+
"""
|
|
19
|
+
Loads the workspace synchronization state from the cloud config.
|
|
20
|
+
"""
|
|
18
21
|
if os.path.exists(self.config_file):
|
|
19
22
|
try:
|
|
20
23
|
with open(self.config_file, 'r') as f: return json.load(f)
|
|
@@ -22,13 +25,19 @@ class SyncManager:
|
|
|
22
25
|
return {}
|
|
23
26
|
|
|
24
27
|
def save(self):
|
|
28
|
+
"""
|
|
29
|
+
Saves the workspace synchronization state locally.
|
|
30
|
+
"""
|
|
25
31
|
with open(self.config_file, 'w') as f:
|
|
26
32
|
json.dump(self.workspaces, f, indent=2)
|
|
27
33
|
|
|
28
34
|
def compute_hashes(self, repo_path):
|
|
35
|
+
"""
|
|
36
|
+
Computes MD5 hashes for all relevant files in the repository.
|
|
37
|
+
"""
|
|
29
38
|
hashes = {}
|
|
30
39
|
for root, dirs, files in os.walk(repo_path):
|
|
31
|
-
dirs[:] = [d for d in dirs if d not in
|
|
40
|
+
dirs[:] = [d for d in dirs if d not in EXCLUDE_DIRS]
|
|
32
41
|
for file in files:
|
|
33
42
|
abs_path = os.path.join(root, file)
|
|
34
43
|
try:
|
|
@@ -41,6 +50,9 @@ class SyncManager:
|
|
|
41
50
|
return hashes
|
|
42
51
|
|
|
43
52
|
def update_sync_state(self, repo_path, workspace_id, user_id):
|
|
53
|
+
"""
|
|
54
|
+
Updates the synchronization state with the latest file hashes.
|
|
55
|
+
"""
|
|
44
56
|
repo_key = f"{user_id}:{os.path.abspath(repo_path)}"
|
|
45
57
|
self.workspaces[repo_key] = {
|
|
46
58
|
"workspace_id": workspace_id,
|
|
@@ -51,6 +63,9 @@ class SyncManager:
|
|
|
51
63
|
self.save()
|
|
52
64
|
|
|
53
65
|
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
|
+
"""
|
|
54
69
|
repo_key = f"{user_id}:{os.path.abspath(repo_path)}"
|
|
55
70
|
if repo_key not in self.workspaces: return []
|
|
56
71
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: codedthemes-cli
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: CLI tool for Code Theme and Integration
|
|
5
|
+
Author: codedthemes
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: requests
|
|
9
|
+
Requires-Dist: pyjwt
|
|
10
|
+
|
|
11
|
+
# CodedThemes CLI
|
|
12
|
+
|
|
13
|
+
CodedThemes CLI is a powerful command-line tool designed to integrate your local development environment with the CodedThemes MCP Server. It allows you to automate UI/UX modifications, refactor themes, and apply stylistic changes across your entire repository using AI-driven insights.
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- **Automated Theming**: Apply complex theme changes (e.g., branding updates) with simple natural language queries.
|
|
18
|
+
- **Local-to-Cloud Sync**: Securely sync your repository state to the CodedThemes engine for deep analysis.
|
|
19
|
+
- **Seamless Integration**: Automatically patches local files with surgical precision.
|
|
20
|
+
- **Sync Management**: Tracks local changes to ensure your manual edits are never overwritten.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
Install the CLI directly from PyPI:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install codedthemes-cli
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
### 1. Authentication
|
|
33
|
+
|
|
34
|
+
Authenticate with your CodedThemes account to enable secure communication with the MCP server.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
codedthemes login
|
|
38
|
+
```
|
|
39
|
+
*Enter your registered email and license key when prompted.*
|
|
40
|
+
|
|
41
|
+
### 2. Initialization (Optional)
|
|
42
|
+
|
|
43
|
+
Initialize your repository to establish a baseline for synchronization. This is recommended for first-time use in a project.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
codedthemes init
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 3. Applying Changes
|
|
50
|
+
|
|
51
|
+
Describe the changes you want to make in natural language. The CLI will analyze your repository, plan the changes, and ask for your approval before patching.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
codedthemes apply "Update the theme branding from Mantis to Berry"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Integration Report
|
|
58
|
+
|
|
59
|
+
After every successful `apply`, a granular report is generated showing exactly which files were modified, created, or deleted.
|
|
60
|
+
|
|
61
|
+
## Support
|
|
62
|
+
|
|
63
|
+
For issues or feature requests, please contact the CodedThemes support team.
|
codedthemes_cli-0.1.0/PKG-INFO
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: codedthemes-cli
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: CLI tool for Code Theme and Integration
|
|
5
|
-
Author: codedthemes
|
|
6
|
-
Requires-Python: >=3.10
|
|
7
|
-
Description-Content-Type: text/markdown
|
|
8
|
-
Requires-Dist: requests
|
|
9
|
-
Requires-Dist: pyjwt
|
|
10
|
-
|
|
11
|
-
# CodedThemes CLI
|
|
12
|
-
|
|
13
|
-
The `codedthemes` CLI is a powerful command-line interface that allows you to interact with the CodeThemeMCP server. It enables you to analyze and modify your repositories using AI, with a focus on theme and style management.
|
|
14
|
-
|
|
15
|
-
## Installation
|
|
16
|
-
|
|
17
|
-
Install the CLI globally using pip:
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
pip install code-theme-mcp-cli
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
## Getting Started
|
|
24
|
-
|
|
25
|
-
### 1. Login
|
|
26
|
-
|
|
27
|
-
Before applying any changes, you must authenticate your session with the MCP Server.
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
codedthemes login
|
|
31
|
-
```
|
|
32
|
-
*You will be prompted to enter your Email and License key.*
|
|
33
|
-
|
|
34
|
-
### 2. Apply Changes
|
|
35
|
-
|
|
36
|
-
Navigate to your target project repository and run the `apply` command with your desired changes in quotes:
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
codedthemes apply "Update the theme branding from Mantis to Berry"
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
The CLI will automatically:
|
|
43
|
-
1. Package and securely send your repository to the remote server for analysis.
|
|
44
|
-
2. Plan the necessary code modifications using AI.
|
|
45
|
-
3. Automatically apply the patches back to your local files.
|
|
46
|
-
|
|
47
|
-
## Support
|
|
48
|
-
|
|
49
|
-
For issues or feature requests, please contact the CodedThemes support team.
|
codedthemes_cli-0.1.0/README.md
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# CodedThemes CLI
|
|
2
|
-
|
|
3
|
-
The `codedthemes` CLI is a powerful command-line interface that allows you to interact with the CodeThemeMCP server. It enables you to analyze and modify your repositories using AI, with a focus on theme and style management.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
Install the CLI globally using pip:
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
pip install code-theme-mcp-cli
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## Getting Started
|
|
14
|
-
|
|
15
|
-
### 1. Login
|
|
16
|
-
|
|
17
|
-
Before applying any changes, you must authenticate your session with the MCP Server.
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
codedthemes login
|
|
21
|
-
```
|
|
22
|
-
*You will be prompted to enter your Email and License key.*
|
|
23
|
-
|
|
24
|
-
### 2. Apply Changes
|
|
25
|
-
|
|
26
|
-
Navigate to your target project repository and run the `apply` command with your desired changes in quotes:
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
codedthemes apply "Update the theme branding from Mantis to Berry"
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
The CLI will automatically:
|
|
33
|
-
1. Package and securely send your repository to the remote server for analysis.
|
|
34
|
-
2. Plan the necessary code modifications using AI.
|
|
35
|
-
3. Automatically apply the patches back to your local files.
|
|
36
|
-
|
|
37
|
-
## Support
|
|
38
|
-
|
|
39
|
-
For issues or feature requests, please contact the CodedThemes support team.
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import zipfile
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
import tempfile
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def detect_repo_root(start_path=None):
|
|
8
|
-
if not start_path:
|
|
9
|
-
start_path = os.getcwd()
|
|
10
|
-
|
|
11
|
-
current = Path(start_path).resolve()
|
|
12
|
-
|
|
13
|
-
while current != current.parent:
|
|
14
|
-
if (current / "package.json").exists() or (current / "pyproject.toml").exists() or (current / "ai.json").exists() or (current / ".git").exists():
|
|
15
|
-
return current
|
|
16
|
-
current = current.parent
|
|
17
|
-
|
|
18
|
-
raise Exception("No repository root found.")
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def zip_repo(repo_root):
|
|
22
|
-
temp_zip = tempfile.NamedTemporaryFile(delete=False, suffix=".zip")
|
|
23
|
-
temp_zip.close() # Close so zipfile can open it
|
|
24
|
-
|
|
25
|
-
with zipfile.ZipFile(temp_zip.name, "w", zipfile.ZIP_DEFLATED) as z:
|
|
26
|
-
for root, dirs, files in os.walk(repo_root):
|
|
27
|
-
# Exclude heavy/build directories
|
|
28
|
-
exclude_dirs = {
|
|
29
|
-
"node_modules", ".git", "venv", ".next", "build", "dist",
|
|
30
|
-
"out", ".cache", "target", ".idea", ".vscode", "__pycache__"
|
|
31
|
-
}
|
|
32
|
-
dirs[:] = [d for d in dirs if d not in exclude_dirs]
|
|
33
|
-
|
|
34
|
-
for file in files:
|
|
35
|
-
full_path = os.path.join(root, file)
|
|
36
|
-
# FORCE FORWARD SLASHES for zip entry names to ensure Linux compatibility
|
|
37
|
-
rel_path = os.path.relpath(full_path, repo_root).replace(os.sep, '/')
|
|
38
|
-
z.write(full_path, rel_path)
|
|
39
|
-
|
|
40
|
-
return temp_zip.name
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: codedthemes-cli
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: CLI tool for Code Theme and Integration
|
|
5
|
-
Author: codedthemes
|
|
6
|
-
Requires-Python: >=3.10
|
|
7
|
-
Description-Content-Type: text/markdown
|
|
8
|
-
Requires-Dist: requests
|
|
9
|
-
Requires-Dist: pyjwt
|
|
10
|
-
|
|
11
|
-
# CodedThemes CLI
|
|
12
|
-
|
|
13
|
-
The `codedthemes` CLI is a powerful command-line interface that allows you to interact with the CodeThemeMCP server. It enables you to analyze and modify your repositories using AI, with a focus on theme and style management.
|
|
14
|
-
|
|
15
|
-
## Installation
|
|
16
|
-
|
|
17
|
-
Install the CLI globally using pip:
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
pip install code-theme-mcp-cli
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
## Getting Started
|
|
24
|
-
|
|
25
|
-
### 1. Login
|
|
26
|
-
|
|
27
|
-
Before applying any changes, you must authenticate your session with the MCP Server.
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
codedthemes login
|
|
31
|
-
```
|
|
32
|
-
*You will be prompted to enter your Email and License key.*
|
|
33
|
-
|
|
34
|
-
### 2. Apply Changes
|
|
35
|
-
|
|
36
|
-
Navigate to your target project repository and run the `apply` command with your desired changes in quotes:
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
codedthemes apply "Update the theme branding from Mantis to Berry"
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
The CLI will automatically:
|
|
43
|
-
1. Package and securely send your repository to the remote server for analysis.
|
|
44
|
-
2. Plan the necessary code modifications using AI.
|
|
45
|
-
3. Automatically apply the patches back to your local files.
|
|
46
|
-
|
|
47
|
-
## Support
|
|
48
|
-
|
|
49
|
-
For issues or feature requests, please contact the CodedThemes support team.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codedthemes_cli-0.1.0 → codedthemes_cli-0.1.2}/codedthemes_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|