codedthemes-cli 0.1.15__tar.gz → 0.1.17__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.15
3
+ Version: 0.1.17
4
4
  Summary: CLI tool for Code Theme and Integration
5
5
  Author: codedthemes
6
6
  Requires-Python: >=3.10
@@ -46,15 +46,21 @@ Log out from the current device to free up a license slot on the server.
46
46
  codedthemes logout
47
47
  ```
48
48
 
49
- ### 3. Initialization (Optional)
50
-
49
+ ### 3. Initialization
51
50
  Initialize your repository to establish a baseline for synchronization. This is recommended for first-time use in a project.
52
51
 
53
52
  ```bash
54
53
  codedthemes init
55
54
  ```
56
55
 
57
- ### 3. Applying Changes
56
+ ### 4. Reactivation
57
+ If your project workspace has been evicted due to inactivity (30+ minutes), use `reinit` to quickly restore it.
58
+
59
+ ```bash
60
+ codedthemes reinit
61
+ ```
62
+
63
+ ### 5. Applying Changes
58
64
 
59
65
  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.
60
66
 
@@ -36,15 +36,21 @@ Log out from the current device to free up a license slot on the server.
36
36
  codedthemes logout
37
37
  ```
38
38
 
39
- ### 3. Initialization (Optional)
40
-
39
+ ### 3. Initialization
41
40
  Initialize your repository to establish a baseline for synchronization. This is recommended for first-time use in a project.
42
41
 
43
42
  ```bash
44
43
  codedthemes init
45
44
  ```
46
45
 
47
- ### 3. Applying Changes
46
+ ### 4. Reactivation
47
+ If your project workspace has been evicted due to inactivity (30+ minutes), use `reinit` to quickly restore it.
48
+
49
+ ```bash
50
+ codedthemes reinit
51
+ ```
52
+
53
+ ### 5. Applying Changes
48
54
 
49
55
  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.
50
56
 
@@ -240,10 +240,81 @@ def handle_init():
240
240
  sys.exit(1)
241
241
 
242
242
 
243
+ def handle_reinit():
244
+ """
245
+ Reactivates an evicted workspace by re-uploading the repository to the cloud.
246
+ """
247
+ try:
248
+ repo_root = detect_repo_root()
249
+ print(f"🔍 Found repository at {repo_root}")
250
+
251
+ client = MCPClient()
252
+ if not client.token:
253
+ print("✖ Not logged in. Please run 'codedthemes login' first.")
254
+ sys.exit(1)
255
+
256
+ user_email = "unknown_user"
257
+ try:
258
+ decoded = jwt.decode(client.token, options={"verify_signature": False})
259
+ user_email = decoded.get("email", "unknown_user")
260
+ except:
261
+ pass
262
+
263
+ repo_abs_path = os.path.abspath(repo_root)
264
+ repo_key = repo_abs_path.lower().replace(os.sep, "/")
265
+
266
+ print("🚀 Reactivating repository (this may take a moment)...")
267
+ upload_result = client.upload_workspace(repo_abs_path, user_email)
268
+ workspace_id = upload_result.get("workspace_id")
269
+
270
+ if not workspace_id:
271
+ print("✖ Reactivation failed: No workspace ID returned.")
272
+ sys.exit(1)
273
+
274
+ config = load_config()
275
+ last_workspaces = config.get("workspaces", {})
276
+ last_workspaces[repo_key] = workspace_id
277
+ config["workspaces"] = last_workspaces
278
+ save_config(config)
279
+
280
+ sync_manager = SyncManager()
281
+ sync_manager.update_sync_state(repo_abs_path, workspace_id, user_email)
282
+
283
+ print(f"✔ Workspace reactivated successfully.")
284
+ print(f"✔ Workspace ID: {workspace_id}")
285
+ print("\n" + "="*60)
286
+ print("Your repository is now synced and active in the cloud!")
287
+ print("You can now return to your IDE and continue using the AI assistant.")
288
+ print("="*60)
289
+
290
+ except Exception as e:
291
+ print(f"✖ Error during reactivation: {e}")
292
+ sys.exit(1)
293
+
294
+
295
+ def clean_remote_path(p: str) -> str:
296
+ """
297
+ Strips the deeply nested remote container path if present.
298
+ e.g., 'home/runner/work/<owner>/<repo>/src/...' -> 'src/...'
299
+ """
300
+ if not p: return p
301
+ p = p.replace('\\', '/')
302
+ if p.startswith('/'): p = p[1:]
303
+ if p.startswith('home/runner/work/'):
304
+ parts = p.split('/')
305
+ if len(parts) > 4: # e.g. ['home', 'runner', 'work', 'owner', 'repo', 'src', ...]
306
+ return '/'.join(parts[5:])
307
+ return p
308
+
243
309
  def handle_apply(query: str):
244
310
  """
245
311
  Plans and executes changes based on a natural language query.
246
312
  """
313
+ if not query or not query.strip():
314
+ print("✖ Error: Query cannot be empty.")
315
+ print("💡 Example: codedthemes apply \"Update the primary color to deep purple\"")
316
+ return
317
+
247
318
  try:
248
319
  repo_root = detect_repo_root()
249
320
  repo_abs_path = os.path.abspath(repo_root)
@@ -335,10 +406,10 @@ def handle_apply(query: str):
335
406
  print(f"✖ Planning failed: {plan.get('message', 'Unknown error')}")
336
407
  return
337
408
 
338
- target_files = plan.get("files_to_modify", [])
409
+ target_files = [clean_remote_path(f) for f in plan.get("files_to_modify", [])]
339
410
  instructions = plan.get("plan_instructions", [])
340
- files_to_create = plan.get("files_to_create", [])
341
- files_to_delete = plan.get("files_to_delete", [])
411
+ files_to_create = [clean_remote_path(f) for f in plan.get("files_to_create", [])]
412
+ files_to_delete = [clean_remote_path(f) for f in plan.get("files_to_delete", [])]
342
413
 
343
414
  if not any([target_files, files_to_create, files_to_delete, instructions]):
344
415
  print("Information: No changes planned.")
@@ -396,41 +467,52 @@ def handle_apply(query: str):
396
467
  updates = res_data.get("updates_for_local", [])
397
468
  deletes = res_data.get("deleted_files", [])
398
469
 
399
- if updates or deletes:
400
- for item in updates:
401
- rel_path, code = item.get("path"), item.get("code")
402
- if not rel_path or code is None: continue
403
- abs_path = os.path.abspath(os.path.join(repo_abs_path, rel_path.replace('/', os.sep)))
470
+ # 1. Apply local updates
471
+ for item in updates:
472
+ rel_path = clean_remote_path(item.get("path"))
473
+ code = item.get("code")
474
+ if not rel_path or code is None: continue
475
+ abs_path = os.path.abspath(os.path.join(repo_abs_path, rel_path.replace('/', os.sep)))
476
+
477
+ try:
478
+ os.makedirs(os.path.dirname(abs_path), exist_ok=True)
479
+ with open(abs_path, 'w', encoding='utf-8') as f:
480
+ f.write(code)
481
+ print(f"✔ Updated: {rel_path}")
482
+ except Exception as e:
483
+ print(f"✖ Error writing {rel_path}: {e}")
404
484
 
485
+ # 2. Apply local deletions
486
+ for rel_path_raw in (deletes or []):
487
+ rel_path = clean_remote_path(rel_path_raw)
488
+ abs_path = os.path.normpath(os.path.join(repo_abs_path, rel_path.replace('/', os.sep)))
489
+ if os.path.exists(abs_path) and abs_path.startswith(os.path.abspath(repo_abs_path)):
405
490
  try:
406
- os.makedirs(os.path.dirname(abs_path), exist_ok=True)
407
- with open(abs_path, 'w', encoding='utf-8') as f:
408
- f.write(code)
409
- print(f"✔ Updated: {rel_path}")
410
- except Exception as e:
411
- print(f"✖ Error writing {rel_path}: {e}")
412
-
413
- for rel_path in (deletes or []):
414
- abs_path = os.path.normpath(os.path.join(repo_abs_path, rel_path.replace('/', os.sep)))
415
- if os.path.exists(abs_path) and abs_path.startswith(os.path.abspath(repo_abs_path)):
416
- try:
417
- if os.path.isfile(abs_path): os.remove(abs_path)
418
- elif os.path.isdir(abs_path): shutil.rmtree(abs_path)
419
- print(f"✔ Deleted: {rel_path}")
420
- except: pass
421
-
491
+ if os.path.isfile(abs_path): os.remove(abs_path)
492
+ elif os.path.isdir(abs_path): shutil.rmtree(abs_path)
493
+ print(f"✔ Deleted: {rel_path}")
494
+ except: pass
495
+
496
+ # 3. Synchronize state
497
+ if updates or deletes:
422
498
  sync_manager.update_sync_state(repo_abs_path, workspace_id, user_email)
423
499
  print("✔ Local sync state updated.")
424
500
 
425
- _details_raw = res_data.get("details", [])
426
- details_list = _details_raw.get("report", []) if isinstance(_details_raw, dict) else (_details_raw if isinstance(_details_raw, list) else [])
427
- if details_list:
428
- print("\n📊 Integration Report:")
429
- for item in details_list:
430
- status_icon = "✔" if item.get("success") else "✖"
431
- print(f" [{status_icon}] {item.get('file_path')}: {item.get('message', '')}")
432
- else:
433
- print(f"✔ {res_data.get('message', 'Changes applied successfully.')}")
501
+ # 4. Show Integration Report (Always show errors or details)
502
+ _details_raw = res_data.get("details", [])
503
+ details_list = _details_raw.get("report", []) if isinstance(_details_raw, dict) else (_details_raw if isinstance(_details_raw, list) else [])
504
+ if details_list:
505
+ print("\n📊 Integration Report:")
506
+ for item in details_list:
507
+ status_icon = "✔" if item.get("success") else "✖"
508
+ print(f" [{status_icon}] {item.get('file_path')}: {item.get('message', '')}")
509
+
510
+ # 5. Output Final Message if no updates were applied
511
+ if not updates and not deletes:
512
+ msg = res_data.get('message', 'Changes applied successfully.')
513
+ if isinstance(msg, str) and "AI MUST now sync" in msg:
514
+ msg = msg.split("AI MUST now sync")[0].strip()
515
+ print(f"✔ {msg}")
434
516
 
435
517
  except Exception as e:
436
518
  print(f"✖ Error: {e}")
@@ -457,9 +539,11 @@ def main():
457
539
  apply_parser.add_argument("query", help="Description of changes to make")
458
540
 
459
541
  subparsers.add_parser("init", help="Initialize repository and sync to cloud")
542
+ subparsers.add_parser("reinit", help="Reactivate an evicted workspace and sync to cloud")
460
543
  subparsers.add_parser("logout", help="Log out from current device")
461
544
 
462
545
 
546
+
463
547
  args = parser.parse_args()
464
548
 
465
549
  if not args.command:
@@ -470,6 +554,8 @@ def main():
470
554
  handle_login(args.server)
471
555
  elif args.command == "init":
472
556
  handle_init()
557
+ elif args.command == "reinit":
558
+ handle_reinit()
473
559
  elif args.command == "apply":
474
560
  handle_apply(args.query)
475
561
  elif args.command == "logout":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codedthemes-cli
3
- Version: 0.1.15
3
+ Version: 0.1.17
4
4
  Summary: CLI tool for Code Theme and Integration
5
5
  Author: codedthemes
6
6
  Requires-Python: >=3.10
@@ -46,15 +46,21 @@ Log out from the current device to free up a license slot on the server.
46
46
  codedthemes logout
47
47
  ```
48
48
 
49
- ### 3. Initialization (Optional)
50
-
49
+ ### 3. Initialization
51
50
  Initialize your repository to establish a baseline for synchronization. This is recommended for first-time use in a project.
52
51
 
53
52
  ```bash
54
53
  codedthemes init
55
54
  ```
56
55
 
57
- ### 3. Applying Changes
56
+ ### 4. Reactivation
57
+ If your project workspace has been evicted due to inactivity (30+ minutes), use `reinit` to quickly restore it.
58
+
59
+ ```bash
60
+ codedthemes reinit
61
+ ```
62
+
63
+ ### 5. Applying Changes
58
64
 
59
65
  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.
60
66
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "codedthemes-cli"
7
- version = "0.1.15"
7
+ version = "0.1.17"
8
8
  description = "CLI tool for Code Theme and Integration"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"