codedthemes-cli 0.1.14__tar.gz → 0.1.16__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.14
3
+ Version: 0.1.16
4
4
  Summary: CLI tool for Code Theme and Integration
5
5
  Author: codedthemes
6
6
  Requires-Python: >=3.10
@@ -151,6 +151,14 @@ def handle_init():
151
151
 
152
152
  repo_abs_path = os.path.abspath(repo_root)
153
153
  repo_key = repo_abs_path.lower().replace(os.sep, "/")
154
+
155
+ # Check if already initialized to avoid redundant uploads
156
+ 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
+
154
162
  print("📦 Zipping and uploading repository (this may take a moment)...")
155
163
  upload_result = client.upload_workspace(repo_abs_path, user_email)
156
164
  workspace_id = upload_result.get("workspace_id")
@@ -232,10 +240,29 @@ def handle_init():
232
240
  sys.exit(1)
233
241
 
234
242
 
243
+ def clean_remote_path(p: str) -> str:
244
+ """
245
+ Strips the deeply nested remote container path if present.
246
+ e.g., 'home/runner/work/<owner>/<repo>/src/...' -> 'src/...'
247
+ """
248
+ if not p: return p
249
+ p = p.replace('\\', '/')
250
+ if p.startswith('/'): p = p[1:]
251
+ if p.startswith('home/runner/work/'):
252
+ parts = p.split('/')
253
+ if len(parts) > 4: # e.g. ['home', 'runner', 'work', 'owner', 'repo', 'src', ...]
254
+ return '/'.join(parts[5:])
255
+ return p
256
+
235
257
  def handle_apply(query: str):
236
258
  """
237
259
  Plans and executes changes based on a natural language query.
238
260
  """
261
+ if not query or not query.strip():
262
+ print("✖ Error: Query cannot be empty.")
263
+ print("💡 Example: codedthemes apply \"Update the primary color to deep purple\"")
264
+ return
265
+
239
266
  try:
240
267
  repo_root = detect_repo_root()
241
268
  repo_abs_path = os.path.abspath(repo_root)
@@ -327,10 +354,10 @@ def handle_apply(query: str):
327
354
  print(f"✖ Planning failed: {plan.get('message', 'Unknown error')}")
328
355
  return
329
356
 
330
- target_files = plan.get("files_to_modify", [])
357
+ target_files = [clean_remote_path(f) for f in plan.get("files_to_modify", [])]
331
358
  instructions = plan.get("plan_instructions", [])
332
- files_to_create = plan.get("files_to_create", [])
333
- files_to_delete = plan.get("files_to_delete", [])
359
+ files_to_create = [clean_remote_path(f) for f in plan.get("files_to_create", [])]
360
+ files_to_delete = [clean_remote_path(f) for f in plan.get("files_to_delete", [])]
334
361
 
335
362
  if not any([target_files, files_to_create, files_to_delete, instructions]):
336
363
  print("Information: No changes planned.")
@@ -388,41 +415,52 @@ def handle_apply(query: str):
388
415
  updates = res_data.get("updates_for_local", [])
389
416
  deletes = res_data.get("deleted_files", [])
390
417
 
391
- if updates or deletes:
392
- for item in updates:
393
- rel_path, code = item.get("path"), item.get("code")
394
- if not rel_path or code is None: continue
395
- abs_path = os.path.abspath(os.path.join(repo_abs_path, rel_path.replace('/', os.sep)))
418
+ # 1. Apply local updates
419
+ for item in updates:
420
+ rel_path = clean_remote_path(item.get("path"))
421
+ code = item.get("code")
422
+ if not rel_path or code is None: continue
423
+ abs_path = os.path.abspath(os.path.join(repo_abs_path, rel_path.replace('/', os.sep)))
424
+
425
+ try:
426
+ os.makedirs(os.path.dirname(abs_path), exist_ok=True)
427
+ with open(abs_path, 'w', encoding='utf-8') as f:
428
+ f.write(code)
429
+ print(f"✔ Updated: {rel_path}")
430
+ except Exception as e:
431
+ print(f"✖ Error writing {rel_path}: {e}")
396
432
 
433
+ # 2. Apply local deletions
434
+ for rel_path_raw in (deletes or []):
435
+ rel_path = clean_remote_path(rel_path_raw)
436
+ abs_path = os.path.normpath(os.path.join(repo_abs_path, rel_path.replace('/', os.sep)))
437
+ if os.path.exists(abs_path) and abs_path.startswith(os.path.abspath(repo_abs_path)):
397
438
  try:
398
- os.makedirs(os.path.dirname(abs_path), exist_ok=True)
399
- with open(abs_path, 'w', encoding='utf-8') as f:
400
- f.write(code)
401
- print(f"✔ Updated: {rel_path}")
402
- except Exception as e:
403
- print(f"✖ Error writing {rel_path}: {e}")
404
-
405
- for rel_path in (deletes or []):
406
- abs_path = os.path.normpath(os.path.join(repo_abs_path, rel_path.replace('/', os.sep)))
407
- if os.path.exists(abs_path) and abs_path.startswith(os.path.abspath(repo_abs_path)):
408
- try:
409
- if os.path.isfile(abs_path): os.remove(abs_path)
410
- elif os.path.isdir(abs_path): shutil.rmtree(abs_path)
411
- print(f"✔ Deleted: {rel_path}")
412
- except: pass
413
-
439
+ if os.path.isfile(abs_path): os.remove(abs_path)
440
+ elif os.path.isdir(abs_path): shutil.rmtree(abs_path)
441
+ print(f"✔ Deleted: {rel_path}")
442
+ except: pass
443
+
444
+ # 3. Synchronize state
445
+ if updates or deletes:
414
446
  sync_manager.update_sync_state(repo_abs_path, workspace_id, user_email)
415
447
  print("✔ Local sync state updated.")
416
448
 
417
- _details_raw = res_data.get("details", [])
418
- details_list = _details_raw.get("report", []) if isinstance(_details_raw, dict) else (_details_raw if isinstance(_details_raw, list) else [])
419
- if details_list:
420
- print("\n📊 Integration Report:")
421
- for item in details_list:
422
- status_icon = "✔" if item.get("success") else "✖"
423
- print(f" [{status_icon}] {item.get('file_path')}: {item.get('message', '')}")
424
- else:
425
- print(f"✔ {res_data.get('message', 'Changes applied successfully.')}")
449
+ # 4. Show Integration Report (Always show errors or details)
450
+ _details_raw = res_data.get("details", [])
451
+ details_list = _details_raw.get("report", []) if isinstance(_details_raw, dict) else (_details_raw if isinstance(_details_raw, list) else [])
452
+ if details_list:
453
+ print("\n📊 Integration Report:")
454
+ for item in details_list:
455
+ status_icon = "✔" if item.get("success") else "✖"
456
+ print(f" [{status_icon}] {item.get('file_path')}: {item.get('message', '')}")
457
+
458
+ # 5. Output Final Message if no updates were applied
459
+ if not updates and not deletes:
460
+ msg = res_data.get('message', 'Changes applied successfully.')
461
+ if isinstance(msg, str) and "AI MUST now sync" in msg:
462
+ msg = msg.split("AI MUST now sync")[0].strip()
463
+ print(f"✔ {msg}")
426
464
 
427
465
  except Exception as e:
428
466
  print(f"✖ Error: {e}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codedthemes-cli
3
- Version: 0.1.14
3
+ Version: 0.1.16
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.14"
7
+ version = "0.1.16"
8
8
  description = "CLI tool for Code Theme and Integration"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"