Skip to content

Commit 2900d15

Browse files
zhujian0805James Zhu
authored andcommitted
adding prompt and skill sub commands
1 parent fdc9b33 commit 2900d15

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+5260
-358
lines changed

.github/copilot-instructions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ This file documents repository-level expectations and instructions intended to g
1010
rm -rf dist/*
1111
./install.sh uninstall
1212
./install.sh
13-
cp ~/.config/code-assistant-manager/settings.json.bak ~/.config/code-assistant-manager/settings.json
13+
cp ~/.config/code-assistant-manager/providers.json.bak ~/.config/code-assistant-manager/providers.json
1414
```

.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

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ This file documents repository-level expectations and instructions intended to g
1010
rm -rf dist/*
1111
./install.sh uninstall
1212
./install.sh
13-
cp ~/.config/code-assistant-manager/settings.json.bak ~/.config/code-assistant-manager/settings.json
13+
cp ~/.config/code-assistant-manager/providers.json.bak ~/.config/code-assistant-manager/providers.json
1414
```

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Unified Python CLI for AI coding assistants — a focused, concise guide.
44

55
Overview
66

7-
code-assistant-manager provides a single CLI to access multiple AI coding assistants (Claude, Codex, Qwen, GitHub Copilot, and more). It offers interactive model selection, configurable endpoints, secure environment management, MCP server integration, and a polished terminal UI.
7+
code-assistant-manager provides a single CLI to access multiple AI coding assistants (Claude, Codex, Qwen, GitHub Copilot, and more). It offers interactive model selection, configurable endpoints, secure environment management, MCP server integration, **prompt and skill management**, and a polished terminal UI.
88

99
Deep analysis of this repository (developer-focused)
1010

@@ -15,6 +15,8 @@ Repository layout
1515
- code_assistant_manager/ — main Python package implementing the CLI, tools registry, MCP support, menus and utilities.
1616
- cli.py: Main Typer-based CLI and legacy compatibility layer. Key commands: launch, upgrade/install, uninstall, doctor, completion. (file: code_assistant_manager/cli.py)
1717
- __init__.py: Package metadata and __version__.
18+
- prompts.py: Prompt management for AI assistants.
19+
- skills.py: Skill management for AI assistants.
1820
- tools/: Tool implementations and CLITool interface.
1921
- mcp/: MCP subsystem with manager, CLI, server registry JSONs. (files: code_assistant_manager/mcp/manager.py, server_commands.py, cli.py, registry/servers/*.json)
2022
- menu/: Terminal UI components and centered menus used to select tools interactively.
@@ -33,6 +35,8 @@ Key files and responsibilities
3335

3436
- README.md (this file): User-facing quick-start and developer deep analysis (this section).
3537

38+
- docs/PROMPTS_AND_SKILLS.md: Guide for managing prompts and skills.
39+
3640
- docs/INSTALL.md: Detailed installation options and scripts.
3741

3842
- docs/CLAUDE.md: Repository guidelines for AI-assisted edits. Important: follow these when creating AI-generated changes.
@@ -43,7 +47,7 @@ Entrypoints and CLI flow
4347

4448
- Console scripts: `code-assistant-manager` and `cam` map to code_assistant_manager.cli:main (pyproject.toml and setup.py).
4549

46-
- The CLI is implemented with Typer (click-based). Subcommands include `launch` (interactive menu or per-tool commands), `version` (show version information), `mcp` (MCP server management), `upgrade`/`install` (tool installers), `doctor` (diagnostics), `completion` (generate shell completion script).
50+
- The CLI is implemented with Typer (click-based). Subcommands include `launch` (interactive menu or per-tool commands), `version` (show version information), `mcp` (MCP server management), `prompt` (prompt management), `skill` (skill management), `upgrade`/`install` (tool installers), `doctor` (diagnostics), `completion` (generate shell completion script).
4751

4852
- `cli.py` also contains compatibility code that allows `code-assistant-manager <tool>` direct invocation and a legacy `main()` wrapper for backward compatibility.
4953

README_zh.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ cam launch codex
2828

2929
配置
3030

31-
1) settings.json — 端点配置(查找顺序:当前目录、~/.config/code-assistant-manager/、包内默认)
31+
1) providers.json — 端点配置(查找顺序:当前目录、~/.config/code-assistant-manager/、包内默认)
3232
2) .env — 存放 API 密钥等环境变量(不要提交到版本控制)
3333
3) tools.yaml — 工具/提供者定义
3434

3535
详细安装请参见 INSTALL.md。
3636

37-
示例 settings.json 片段
37+
示例 providers.json 片段
3838

3939
{
4040
"common": { "http_proxy": "http://proxy.corp:3128/", "cache_ttl_seconds": 86400 },

code_assistant_manager/cli/app.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import typer
99
from typer import Context
1010

11+
from code_assistant_manager.cli.prompts_commands import prompt_app
12+
from code_assistant_manager.cli.skills_commands import skill_app
1113
from code_assistant_manager.config import ConfigManager
1214
from code_assistant_manager.mcp.cli import app as mcp_app
1315
from code_assistant_manager.tools import (
@@ -198,6 +200,12 @@ def command(
198200
# Add the MCP app as a subcommand to the main app
199201
app.add_typer(mcp_app, name="mcp")
200202
app.add_typer(mcp_app, name="m", hidden=True)
203+
# Add the prompt app as a subcommand to the main app
204+
app.add_typer(prompt_app, name="prompt")
205+
app.add_typer(prompt_app, name="p", hidden=True)
206+
# Add the skill app as a subcommand to the main app
207+
app.add_typer(skill_app, name="skill")
208+
app.add_typer(skill_app, name="s", hidden=True)
201209

202210

203211
@config_app.command("validate")
@@ -253,9 +261,9 @@ def list_config():
253261
typer.echo(f"{Colors.CYAN}Code Assistant Manager (CAM):{Colors.RESET}")
254262
home = Path.home()
255263
cam_config_locations = [
256-
home / ".config" / "code-assistant-manager" / "settings.json",
257-
Path.cwd() / "settings.json",
258-
home / "settings.json",
264+
home / ".config" / "code-assistant-manager" / "providers.json",
265+
Path.cwd() / "providers.json",
266+
home / "providers.json",
259267
]
260268
for path in cam_config_locations:
261269
status = f"{Colors.GREEN}{Colors.RESET}" if path.exists() else " "

code_assistant_manager/cli/doctor.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,9 @@ def check_warning(message: str, suggestion: str = ""):
9494
env_file_paths.insert(0, Path(found_env))
9595

9696
config_file_paths = [
97-
Path.home() / ".config" / "code-assistant-manager" / "settings.json",
98-
Path.cwd() / "settings.json",
99-
Path.home() / "settings.json",
97+
Path.home() / ".config" / "code-assistant-manager" / "providers.json",
98+
Path.cwd() / "providers.json",
99+
Path.home() / "providers.json",
100100
]
101101
env_found = False
102102
config_found = False
@@ -120,18 +120,18 @@ def check_warning(message: str, suggestion: str = ""):
120120
except Exception as e:
121121
logger.debug(f"Error while checking env file {env_path}: {e}")
122122

123-
# Check for settings.json files
123+
# Check for providers.json files
124124
for config_path in config_file_paths:
125125
if config_path.exists():
126126
config_found = True
127-
check_passed(f"Settings file found: {config_path}")
127+
check_passed(f"Providers config file found: {config_path}")
128128
# Check permissions
129129
perms = oct(config_path.stat().st_mode)[-3:]
130130
if perms in ["600", "400"]:
131-
check_passed("Settings file has secure permissions")
131+
check_passed("Providers config file has secure permissions")
132132
else:
133133
check_warning(
134-
f"Settings file permissions: {perms}",
134+
f"Providers config file permissions: {perms}",
135135
"Consider setting permissions to 600 for security",
136136
)
137137
break
@@ -143,8 +143,8 @@ def check_warning(message: str, suggestion: str = ""):
143143

144144
if not config_found:
145145
check_warning(
146-
"No settings.json file found",
147-
"Create a settings.json file for configuration",
146+
"No providers.json file found",
147+
"Create a providers.json file for configuration",
148148
)
149149

150150
# 5. Tool Installation Check
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
"""Shared helpers for CLI option validation and normalization."""
2+
3+
from __future__ import annotations
4+
5+
from pathlib import Path
6+
from typing import List, Optional, Sequence
7+
8+
import typer
9+
10+
from code_assistant_manager.menu.base import Colors
11+
12+
13+
def _emit_error(message: str) -> None:
14+
"""Print a formatted error message and exit."""
15+
typer.echo(f"{Colors.RED}{message}{Colors.RESET}")
16+
raise typer.Exit(1)
17+
18+
19+
def _split_values(raw: str) -> List[str]:
20+
"""Split a comma-delimited option string into individual values."""
21+
parts = [part.strip() for part in raw.split(",")]
22+
values = [part for part in parts if part]
23+
return values or ([raw.strip()] if raw.strip() else [])
24+
25+
26+
def resolve_app_targets(
27+
value: Optional[str],
28+
valid_apps: Sequence[str],
29+
*,
30+
option_label: str = "--app",
31+
default: Optional[str] = None,
32+
allow_all: bool = True,
33+
fallback_to_all_if_none: bool = False,
34+
) -> List[str]:
35+
"""Resolve an app option into a list of normalized app identifiers."""
36+
selection = value if value is not None else default
37+
if selection is None and fallback_to_all_if_none:
38+
selection = "all"
39+
if selection is None:
40+
_emit_error(f"{option_label} is required")
41+
42+
tokens = _split_values(selection)
43+
if not tokens:
44+
_emit_error(f"{option_label} cannot be empty")
45+
46+
resolved: List[str] = []
47+
for token in tokens:
48+
normalized = token.lower()
49+
if allow_all and normalized == "all":
50+
return list(valid_apps)
51+
if normalized not in valid_apps:
52+
valid_list = ", ".join(valid_apps)
53+
all_hint = " or 'all'" if allow_all else ""
54+
_emit_error(
55+
f"Invalid value '{token}' for {option_label}. Valid: {valid_list}{all_hint}"
56+
)
57+
if normalized not in resolved:
58+
resolved.append(normalized)
59+
return resolved
60+
61+
62+
def resolve_single_app(
63+
value: Optional[str],
64+
valid_apps: Sequence[str],
65+
*,
66+
option_label: str = "--app",
67+
default: Optional[str] = None,
68+
) -> str:
69+
"""Resolve an app option that must target exactly one app."""
70+
apps = resolve_app_targets(
71+
value,
72+
valid_apps,
73+
option_label=option_label,
74+
default=default,
75+
allow_all=False,
76+
)
77+
if len(apps) != 1:
78+
_emit_error(f"{option_label} accepts a single app value")
79+
return apps[0]
80+
81+
82+
def resolve_level_targets(
83+
value: Optional[str],
84+
valid_levels: Sequence[str],
85+
*,
86+
option_label: str = "--level",
87+
default: Optional[str] = None,
88+
allow_all: bool = True,
89+
) -> List[str]:
90+
"""Resolve a level option into one or more levels."""
91+
selection = value if value is not None else default
92+
if selection is None:
93+
_emit_error(f"{option_label} is required")
94+
95+
tokens = _split_values(selection)
96+
if not tokens:
97+
_emit_error(f"{option_label} cannot be empty")
98+
99+
resolved: List[str] = []
100+
for token in tokens:
101+
normalized = token.lower()
102+
if allow_all and normalized == "all":
103+
return list(valid_levels)
104+
if normalized not in valid_levels:
105+
valid_list = ", ".join(valid_levels)
106+
all_hint = " or 'all'" if allow_all else ""
107+
_emit_error(
108+
f"Invalid value '{token}' for {option_label}. Valid: {valid_list}{all_hint}"
109+
)
110+
if normalized not in resolved:
111+
resolved.append(normalized)
112+
return resolved
113+
114+
115+
def resolve_single_level(
116+
value: Optional[str],
117+
valid_levels: Sequence[str],
118+
*,
119+
option_label: str = "--level",
120+
default: Optional[str] = None,
121+
) -> str:
122+
"""Resolve a level option that must target exactly one level."""
123+
levels = resolve_level_targets(
124+
value,
125+
valid_levels,
126+
option_label=option_label,
127+
default=default,
128+
allow_all=False,
129+
)
130+
if len(levels) != 1:
131+
_emit_error(f"{option_label} accepts a single level value")
132+
return levels[0]
133+
134+
135+
def ensure_project_dir(level: str, project_dir: Optional[Path]) -> Optional[Path]:
136+
"""Ensure a project directory exists when required for the provided level."""
137+
if level == "project" and project_dir is None:
138+
return Path.cwd()
139+
return project_dir

0 commit comments

Comments
 (0)