Skip to content

Commit 20f6dee

Browse files
committed
Relax pre-commit hooks
1 parent 590d613 commit 20f6dee

6 files changed

Lines changed: 220 additions & 89 deletions

File tree

.pre-commit-config.yaml

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -32,46 +32,3 @@ repos:
3232
hooks:
3333
- id: isort
3434
args: ['--profile=black', '--line-length=88']
35-
36-
# Linting
37-
- repo: https://github.com/pycqa/flake8
38-
rev: 7.1.2
39-
hooks:
40-
- id: flake8
41-
args: ['--config=.flake8']
42-
additional_dependencies: [
43-
'flake8-bugbear',
44-
'flake8-comprehensions',
45-
'flake8-simplify',
46-
]
47-
48-
# Type checking
49-
- repo: https://github.com/pre-commit/mirrors-mypy
50-
rev: v1.11.0
51-
hooks:
52-
- id: mypy
53-
args: ['--config-file=pyproject.toml']
54-
additional_dependencies: [
55-
'types-PyYAML',
56-
'types-requests',
57-
'pydantic>=2.6.0',
58-
'typer>=0.12.0',
59-
]
60-
exclude: '^tests/'
61-
62-
# Security checks
63-
- repo: https://github.com/PyCQA/bandit
64-
rev: 1.7.8
65-
hooks:
66-
- id: bandit
67-
args: ['-c', 'pyproject.toml']
68-
additional_dependencies: ['bandit[toml]']
69-
exclude: '^tests/'
70-
71-
# Docstring coverage
72-
- repo: https://github.com/econchick/interrogate
73-
rev: 1.7.0
74-
hooks:
75-
- id: interrogate
76-
args: ['--config=pyproject.toml']
77-
pass_filenames: false

code_assistant_manager/cli/prompts_commands.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
resolve_app_targets,
1313
resolve_level_targets,
1414
resolve_single_app,
15+
resolve_single_level,
1516
)
1617
from code_assistant_manager.menu.base import Colors
1718
from code_assistant_manager.prompts import (
@@ -232,15 +233,40 @@ def activate_prompt(
232233
"-a",
233234
help="App type to activate for (claude, codex, gemini)",
234235
),
236+
level: str = typer.Option(
237+
"user",
238+
"--level",
239+
"-l",
240+
help="Scope to sync: 'user' (~/.<app>/) or 'project' (current directory)",
241+
),
242+
project_dir: Optional[Path] = typer.Option(
243+
None,
244+
"--project-dir",
245+
help="Project directory when using project scope (defaults to current directory)",
246+
),
235247
):
236248
"""Activate a prompt and sync it to the app's prompt file."""
237249
target_app = resolve_single_app(app_type, VALID_APP_TYPES)
250+
target_level = resolve_single_level(level, VALID_LEVELS, default="user")
251+
level_project_dir = (
252+
ensure_project_dir(
253+
target_level,
254+
project_dir if target_level == "project" else None,
255+
)
256+
if target_level == "project"
257+
else None
258+
)
238259

239260
manager = _get_prompt_manager()
240261

241262
try:
242-
manager.activate(prompt_id, target_app)
243-
file_path = PROMPT_FILE_PATHS.get(target_app)
263+
manager.activate(
264+
prompt_id,
265+
target_app,
266+
level=target_level,
267+
project_dir=level_project_dir,
268+
)
269+
file_path = get_prompt_file_path(target_app, target_level, level_project_dir)
244270
typer.echo(f"{Colors.GREEN}✓ Prompt activated: {prompt_id}{Colors.RESET}")
245271
typer.echo(f" {Colors.CYAN}Synced to:{Colors.RESET} {file_path}")
246272
except ValueError as e:

code_assistant_manager/prompts.py

Lines changed: 59 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -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,

docs/PROMPTS_AND_SKILLS.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ cam prompt delete <prompt-id> --force # Skip confirmation
8888

8989
#### Activate/Deactivate prompts (syncs to app files)
9090
```bash
91-
# Activate a prompt and sync it to Claude's CLAUDE.md
91+
# Activate a prompt and sync it to Claude's CLAUDE.md (user scope)
9292
cam prompt activate my-prompt --app claude
9393

9494
# Activate for Codex (syncs to AGENTS.md)
@@ -97,6 +97,9 @@ cam prompt activate my-prompt --app codex
9797
# Activate for Gemini (syncs to GEMINI.md)
9898
cam prompt activate my-prompt --app gemini
9999

100+
# Activate at the project scope (writes ./CLAUDE.md, ./AGENTS.md, or ./GEMINI.md)
101+
cam prompt activate my-prompt --app claude --level project --project-dir $(pwd)
102+
100103
# Deactivate a prompt
101104
cam prompt deactivate my-prompt
102105
```

0 commit comments

Comments
 (0)