@@ -271,39 +271,61 @@ def export_to_file(self, file_path: Path) -> None:
271271 logger .error (f"Failed to export prompts: { e } " )
272272 raise
273273
274- def activate (self , prompt_id : str , app_type : str = "claude" ) -> None :
274+ def activate (
275+ self ,
276+ prompt_id : str ,
277+ app_type : str = "claude" ,
278+ level : str = "user" ,
279+ project_dir : Optional [Path ] = None ,
280+ ) -> None :
275281 """
276282 Activate a prompt by syncing it to the app's prompt file.
277283
278284 Args:
279285 prompt_id: The prompt identifier
280286 app_type: The app type (claude, codex, gemini)
287+ level: Target scope ("user" or "project")
288+ project_dir: Project directory when targeting project scope
281289 """
290+ if level not in ("user" , "project" ):
291+ raise ValueError (f"Invalid level: { level } " )
292+
282293 prompts = self ._load_prompts ()
283294 if prompt_id not in prompts :
284295 raise ValueError (f"Prompt with id '{ prompt_id } ' not found" )
285296
286297 prompt = prompts [prompt_id ]
298+ target_file = get_prompt_file_path (app_type , level , project_dir )
299+ if not target_file :
300+ raise ValueError (f"Unknown app type: { app_type } " )
287301
288302 # Backup existing prompt content from the live file first
289- self ._backup_live_prompt (app_type )
290-
291- # Disable all other prompts for this app type
292- for p in prompts .values ():
293- if p .app_type == app_type or p .app_type is None :
294- p .enabled = False
303+ self ._backup_live_prompt (app_type , level , project_dir )
295304
296- # Enable the selected prompt
297- prompt .enabled = True
298- prompt .app_type = app_type
299305 prompt .updated_at = int (datetime .now ().timestamp () * 1000 )
300306
301- # Sync to the app's prompt file
302- self ._sync_prompt_to_file (prompt .content , app_type )
307+ if level == "user" :
308+ # Disable all other prompts for this app type
309+ for p in prompts .values ():
310+ if p .app_type == app_type or p .app_type is None :
311+ p .enabled = False
303312
304- # Save changes
313+ # Enable the selected prompt for user scope tracking
314+ prompt .enabled = True
315+ prompt .app_type = app_type
316+ else :
317+ # Ensure app type is recorded even if not tracking enabled state
318+ if not prompt .app_type :
319+ prompt .app_type = app_type
320+
321+ # Sync to the target prompt file
322+ self ._sync_prompt_to_file (prompt .content , app_type , level , project_dir )
323+
324+ # Save changes (updates timestamps and activation state)
305325 self ._save_prompts (prompts )
306- logger .info (f"Activated prompt: { prompt_id } for { app_type } " )
326+ logger .info (
327+ f"Activated prompt: { prompt_id } for { app_type } ({ level } scope -> { target_file } )"
328+ )
307329
308330 def deactivate (self , prompt_id : str ) -> None :
309331 """
@@ -323,15 +345,23 @@ def deactivate(self, prompt_id: str) -> None:
323345 self ._save_prompts (prompts )
324346 logger .info (f"Deactivated prompt: { prompt_id } " )
325347
326- def _sync_prompt_to_file (self , content : str , app_type : str ) -> None :
348+ def _sync_prompt_to_file (
349+ self ,
350+ content : str ,
351+ app_type : str ,
352+ level : str = "user" ,
353+ project_dir : Optional [Path ] = None ,
354+ ) -> None :
327355 """
328356 Write prompt content to the app's prompt file.
329357
330358 Args:
331359 content: The prompt content
332360 app_type: The app type (claude, codex, gemini)
361+ level: Target scope ("user" or "project")
362+ project_dir: Project directory when targeting project scope
333363 """
334- file_path = PROMPT_FILE_PATHS . get (app_type )
364+ file_path = get_prompt_file_path (app_type , level , project_dir )
335365 if not file_path :
336366 raise ValueError (f"Unknown app type: { app_type } " )
337367
@@ -344,22 +374,29 @@ def _sync_prompt_to_file(self, content: str, app_type: str) -> None:
344374 temp_path .write_text (content , encoding = "utf-8" )
345375 temp_path .replace (file_path )
346376 logger .info (f"Synced prompt to: { file_path } " )
347- except Exception as e :
377+ except Exception :
348378 if temp_path .exists ():
349379 temp_path .unlink ()
350380 raise
351381
352- def _backup_live_prompt (self , app_type : str ) -> Optional [str ]:
382+ def _backup_live_prompt (
383+ self ,
384+ app_type : str ,
385+ level : str = "user" ,
386+ project_dir : Optional [Path ] = None ,
387+ ) -> Optional [str ]:
353388 """
354389 Backup the current live prompt file content.
355390
356391 Args:
357392 app_type: The app type (claude, codex, gemini)
393+ level: Target scope ("user" or "project")
394+ project_dir: Project directory when targeting project scope
358395
359396 Returns:
360397 The prompt ID if a backup was created, None otherwise
361398 """
362- file_path = PROMPT_FILE_PATHS . get (app_type )
399+ file_path = get_prompt_file_path (app_type , level , project_dir )
363400 if not file_path or not file_path .exists ():
364401 return None
365402
@@ -388,10 +425,11 @@ def _backup_live_prompt(self, app_type: str) -> Optional[str]:
388425
389426 # Create a backup prompt
390427 timestamp = int (datetime .now ().timestamp ())
391- backup_id = f"backup-{ app_type } -{ timestamp } "
428+ scope_label = f"{ level } " if level != "user" else ""
429+ backup_id = f"backup-{ level } -{ app_type } -{ timestamp } "
392430 backup_prompt = Prompt (
393431 id = backup_id ,
394- name = f"Backup from { app_type .capitalize ()} ({ datetime .now ().strftime ('%Y-%m-%d %H:%M' )} )" ,
432+ name = f"Backup from { scope_label } { app_type .capitalize ()} ({ datetime .now ().strftime ('%Y-%m-%d %H:%M' )} )" . strip () ,
395433 content = content ,
396434 description = f"Auto-backup of { file_path .name } " ,
397435 enabled = False ,
0 commit comments