From bb5e6be73eea501aeb62d05d9983fa929ec0fd84 Mon Sep 17 00:00:00 2001 From: John Qiu Date: Thu, 20 Aug 2026 12:07:10 +0930 Subject: [PATCH] feat(skills): make Codex the default install target --- CLAUDE.md | 6 +-- README.md | 24 ++++++------ SETUP.md | 6 ++- SYNC-GUIDE.md | 13 +++++-- init.sh | 7 ++-- install-skills.sh | 74 ++++++++++++++++++++++++++++-------- tests/install-skills.test.sh | 58 ++++++++++++++++++++++------ 7 files changed, 139 insertions(+), 49 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index e5c125b..c1ae585 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,14 +1,14 @@ # ai-proj-helper -Claude Code 技能市场 + MCP 配置管理工具。 +Codex 优先、兼容 Claude Code 的 Agent Skills 市场与 MCP 配置管理工具。 ## 快速开始 ```bash -./init.sh +./install-skills.sh ``` -交互式配置 MCP 连接(默认 SSE 模式)+ 自动注册技能市场到 `~/.claude/plugins/known_marketplaces.json`。支持命令行参数: +默认安装到 Codex 标准目录 `~/.agents/skills`。Claude Code 的 MCP 与 marketplace 初始化使用: ```bash ./init.sh --mode sse --token aiproj_pk_xxx diff --git a/README.md b/README.md index a8af43b..ce150be 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# ai-proj-helper — 让 Claude Code 变成你的项目经理 +# ai-proj-helper — 让 Codex / Claude Code 变成你的项目经理 -> 一套开箱即用的 Claude Code 技能包 + MCP 服务,帮你用自然语言管理需求、写代码、做评审、同步飞书,把 AI 助手变成真正的项目经理。 +> 一套遵循 Agent Skills 标准的技能包 + MCP 服务,支持 Codex,并兼容 Claude Code。 ## 它能帮你做什么 @@ -66,7 +66,7 @@ PRD 文档存储在思源笔记中,可以导出发送到飞书群,通过飞 ### 前置条件 -- **Claude Code** 已安装([安装指南](https://docs.anthropic.com/en/docs/claude-code/overview)) +- **Codex**([Skills 文档](https://developers.openai.com/codex/skills))或 **Claude Code** 已安装 - **ai-proj 账号 + MCP API Key**:联系管理员获取(Key 格式: `aiproj_pk_xxx`) ### 一键部署(2 步搞定) @@ -76,18 +76,16 @@ PRD 文档存储在思源笔记中,可以导出发送到飞书群,通过飞 git clone https://gitea.pipexerp.com/pipexerp/ai-proj-helper.git cd ai-proj-helper -# 2. 运行初始化(按提示输入 API Key 即可) -./init.sh +# 2. 默认安装到 Codex 的用户级标准目录 ~/.agents/skills +./install-skills.sh ``` -脚本会自动完成: -- 配置 MCP 服务器连接(`~/.claude/.mcp.json`) -- 注册技能市场到 Claude Code(`~/.claude/plugins/known_marketplaces.json`) -- 安装完整技能目录到 `~/.claude/skills/`(包括 references、scripts 和 assets) +安装器会复制完整技能目录,包括 `SKILL.md`、references、scripts 和 assets。Codex 会自动发现 `~/.agents/skills` 中的技能;若没有出现,重启 Codex。 -也支持命令行参数跳过交互: +Claude Code 用户显式选择 Claude 目标;需要同时配置 MCP 和 marketplace 时运行 `init.sh`: ```bash +./install-skills.sh --agent claude ./init.sh --mode sse --token aiproj_pk_xxx ``` @@ -172,13 +170,17 @@ skills: A: 需要先联系管理员获取 MCP API Key(格式 `aiproj_pk_xxx`),然后在提示处输入。 +**Q: 安装后 Codex 没有识别到技能?** + +A: 确认技能位于 `~/.agents/skills//SKILL.md`,然后重启 Codex。Codex CLI 也可用 `/skills` 查看。 + **Q: 安装后 Claude Code 没有识别到技能?** A: 重启 Claude Code 后生效。如果仍不生效,检查 `~/.claude/plugins/known_marketplaces.json` 中是否包含 `ai-proj-helper` 条目。 **Q: 如何更新到最新版本?** -A: 进入项目目录执行 `git pull`,然后重新运行 `./init.sh`。 +A: 进入项目目录执行 `git pull`,然后运行 `./install-skills.sh`;Claude Code 用户增加 `--agent claude`。 **Q: 如何禁用不需要的技能?** diff --git a/SETUP.md b/SETUP.md index dc29ed6..a412487 100644 --- a/SETUP.md +++ b/SETUP.md @@ -1,4 +1,6 @@ -# Setup Guide +# Claude Code Marketplace Setup + +本页只描述 Claude Code marketplace。Codex 用户直接运行 `./install-skills.sh`,技能默认安装到 `~/.agents/skills`。 ## 1. Clone the Gitea Repository @@ -114,7 +116,7 @@ ai-proj-helper/ - Check plugin name is correct - Ensure marketplace.json is valid: `cat .claude-plugin/marketplace.json | jq` -**"Skills not working"** +**"Skills not working in Claude Code"** - Skills are Agent Skills (auto-invoked by Claude when relevant) - They don't create slash commands - Check plugin installation: `/plugin list` diff --git a/SYNC-GUIDE.md b/SYNC-GUIDE.md index 3477e3a..6360413 100644 --- a/SYNC-GUIDE.md +++ b/SYNC-GUIDE.md @@ -1,6 +1,6 @@ # Skill Sync Guide -仓库中的插件是团队技能的发布源,本机 `~/.claude/skills/` 是安装目标。个人技能保留在 +仓库中的插件是团队技能的发布源。默认安装目标是 Codex 的用户级标准目录 `~/.agents/skills/`。个人技能保留在 `skills-personal/` 或其他本机目录,不会进入公开 marketplace。 ## 从仓库更新本机 @@ -11,6 +11,12 @@ git pull ./install-skills.sh ``` +Claude Code 需要显式选择目标: + +```bash +./install-skills.sh --agent claude +``` + 安装器会复制完整技能目录,包括 `SKILL.md`、`references/`、`scripts/` 和 `assets/`。它用内容摘要区分仓库升级和本地修改: - 目标未修改时,版本升级会自动安装。 @@ -23,11 +29,12 @@ git pull ```bash ./install-skills.sh --list ./install-skills.sh --category dev +./install-skills.sh --exclude ai-proj-cicd-release ``` ## 将本机技能发布到仓库 -不要批量复制整个 `~/.claude/skills/`。系统技能、第三方托管技能、包含机器路径或凭据的技能不应发布。 +不要批量复制整个 `~/.agents/skills/` 或其他 Agent 的安装目录。系统技能、第三方托管技能、包含机器路径或凭据的技能不应发布。 1. 选择确实属于本仓库、可供团队复用的技能。 2. 在对应 `skills-*/-plugin/` 下放置 `.claude-plugin/plugin.json` 和完整 `skills/` 目录。 @@ -51,7 +58,7 @@ git pull **本地修改被跳过怎么办?** -先比较仓库源和 `~/.claude/skills//`。保留本地修改时将其整理成插件变更;确认丢弃时再对该次安装使用 `--force`。 +先比较仓库源和 `~/.agents/skills//`。保留本地修改时将其整理成插件变更;确认丢弃时再对该次安装使用 `--force`。Claude 目标改查 `~/.claude/skills/`。 **marketplace 没更新?** diff --git a/init.sh b/init.sh index 4da8d29..0f0be98 100755 --- a/init.sh +++ b/init.sh @@ -1,6 +1,6 @@ #!/bin/bash # ai-proj-helper 初始化脚本 -# 配置 MCP 连接 + 安装技能到 ~/.claude/skills/ +# 配置 Claude MCP 连接 + 安装 Claude 技能 set -e @@ -187,7 +187,8 @@ fi # Use the versioned installer as the single installation path so references, # scripts and assets stay beside SKILL.md and local edits are not overwritten. echo "📦 安装技能到 ~/.claude/skills/ ..." -"$SCRIPT_DIR/install-skills.sh" +"$SCRIPT_DIR/install-skills.sh" --agent claude +SKILL_COUNT=$(python3 -c 'import json, os; p=os.path.expanduser("~/.claude/.installed-skills.json"); print(len(json.load(open(p))) if os.path.exists(p) else 0)' 2>/dev/null || echo 0) echo "✅ 技能安装完成" # ── Verify MCP connection ──────────────────────────────────────────── @@ -250,7 +251,7 @@ if $HAS_CLAUDE; then else echo " ✅ MCP 服务器 → $MCP_CONFIG" fi -echo " ✅ 技能 ($SKILL_COUNT 个) → $SKILLS_DIR" +echo " ✅ 技能 ($SKILL_COUNT 个) → ~/.claude/skills" echo "" echo "重启 Claude Code 即可使用。" echo "如需更改配置,编辑 claude-config.yaml 后重新运行 ./init.sh" diff --git a/install-skills.sh b/install-skills.sh index 370bba0..c516efd 100755 --- a/install-skills.sh +++ b/install-skills.sh @@ -1,13 +1,15 @@ #!/usr/bin/env bash -# install-skills.sh — Cross-machine Claude skill sync from ai-proj-helper +# install-skills.sh — Cross-agent skill sync from ai-proj-helper # # Usage: # ./install-skills.sh [options] # # Options: +# --agent Install target: codex (default) or claude # --dry-run Preview changes without writing anything # --category Only install plugins in dir_category= # Valid values: biz, core, dev, integration, personal, req +# --exclude Skip one install_name (repeatable) # --force Overwrite even if local files were modified # --cleanup Remove locally installed skills that are no longer in repo # --list List all available plugins without installing @@ -16,12 +18,14 @@ set -euo pipefail REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -SKILLS_DIR="${HOME}/.claude/skills" -COMMANDS_DIR="${HOME}/.claude/commands" -STATE_FILE="${HOME}/.claude/.installed-skills.json" +SKILLS_DIR="" +COMMANDS_DIR="" +STATE_FILE="" +AGENT_TARGET="codex" DRY_RUN=false CATEGORY_FILTER="" +EXCLUDED_NAMES=() FORCE=false CLEANUP=false LIST_ONLY=false @@ -43,11 +47,19 @@ dry() { echo -e "${YELLOW}[dry]${RESET} $*"; } # ── Argument parsing ─────────────────────────────────────────────────────────── while [[ $# -gt 0 ]]; do case "$1" in + --agent) + [[ $# -ge 2 ]] || { error "--agent requires codex or claude"; exit 1; } + AGENT_TARGET="$2"; shift ;; --dry-run) DRY_RUN=true ;; --force) FORCE=true ;; --cleanup) CLEANUP=true ;; --list) LIST_ONLY=true ;; - --category) CATEGORY_FILTER="$2"; shift ;; + --category) + [[ $# -ge 2 ]] || { error "--category requires a value"; exit 1; } + CATEGORY_FILTER="$2"; shift ;; + --exclude) + [[ $# -ge 2 ]] || { error "--exclude requires an install_name"; exit 1; } + EXCLUDED_NAMES+=("$2"); shift ;; --help|-h) grep '^#' "$0" | grep -v '!/usr' | sed 's/^# \?//' exit 0 ;; @@ -58,6 +70,24 @@ while [[ $# -gt 0 ]]; do shift done +case "$AGENT_TARGET" in + codex) + # ~/.agents/skills is the current user-level Codex discovery location and + # is intentionally agent-neutral. Commands are installed as normal skills. + SKILLS_DIR="${AI_PROJ_HELPER_SKILLS_DIR:-${HOME}/.agents/skills}" + STATE_FILE="${AI_PROJ_HELPER_STATE_FILE:-${HOME}/.agents/.ai-proj-helper-installed-skills.json}" + ;; + claude) + SKILLS_DIR="${AI_PROJ_HELPER_SKILLS_DIR:-${HOME}/.claude/skills}" + COMMANDS_DIR="${AI_PROJ_HELPER_COMMANDS_DIR:-${HOME}/.claude/commands}" + STATE_FILE="${AI_PROJ_HELPER_STATE_FILE:-${HOME}/.claude/.installed-skills.json}" + ;; + *) + error "Unsupported agent: $AGENT_TARGET (expected codex or claude)" + exit 1 + ;; +esac + # ── State helpers (plain JSON via python3) ───────────────────────────────────── state_get() { # state_get -> prints version or empty string @@ -94,7 +124,7 @@ state_set() { import json,os f='$STATE_FILE' d=json.load(open(f)) if os.path.exists(f) else {} -d['$name']={'version':'$ver','install_type':'$itype','content_digest':'$digest'} +d['$name']={'version':'$ver','install_type':'$itype','content_digest':'$digest','agent':'$AGENT_TARGET'} json.dump(d,open(f,'w'),indent=2) " 2>/dev/null } @@ -149,7 +179,9 @@ files = [target] if target.is_file() else sorted( path for path in target.rglob("*") if path.is_file() or path.is_symlink() ) for path in files: - relative = path.name if target.is_file() else path.relative_to(target).as_posix() + # A single-file command is renamed when installed for Claude. Hash its + # content under a stable logical name so source and target compare equally. + relative = "." if target.is_file() else path.relative_to(target).as_posix() digest.update(relative.encode("utf-8")) digest.update(b"\0") if path.is_symlink(): @@ -222,6 +254,11 @@ install_plugin() { dir_category="$(read_field "$json_path" dir_category)" version="$(read_field "$json_path" version)" + local excluded + for excluded in ${EXCLUDED_NAMES[@]+"${EXCLUDED_NAMES[@]}"}; do + [[ "$install_name" == "$excluded" ]] && return + done + # Skip if no install metadata (legacy plugin without our new fields) if [[ -z "$install_name" || -z "$install_type" ]]; then warn "$(basename "$plugin_dir"): missing install_name/install_type, skipping" @@ -239,8 +276,13 @@ install_plugin() { return fi + local effective_install_type="$install_type" + if [[ "$AGENT_TARGET" == "codex" ]]; then + effective_install_type="skill" + fi + if [[ "$LIST_ONLY" == true ]]; then - echo " [$dir_category] $install_type:$install_name v$version" + echo " [$dir_category] $effective_install_type:$install_name v$version" return fi @@ -250,7 +292,7 @@ install_plugin() { src_dir="$(resolve_skills_src "$skills_dir")" local source_path target_path - if [[ "$install_type" == "command" ]]; then + if [[ "$effective_install_type" == "command" ]]; then source_path="$src_dir/SKILL.md" target_path="$COMMANDS_DIR/${install_name}.md" else @@ -274,13 +316,13 @@ install_plugin() { if [[ "$FORCE" == false && -n "$target_digest" && "$target_digest" == "$source_digest" ]]; then if [[ "$DRY_RUN" == false && ( "$current_version" != "$version" || "$recorded_digest" != "$source_digest" ) ]]; then - state_set "$install_name" "$version" "$install_type" "$source_digest" + state_set "$install_name" "$version" "$effective_install_type" "$source_digest" fi return fi local legacy_subset=false - if [[ "$install_type" == "skill" && -z "$recorded_digest" && -n "$target_digest" ]]; then + if [[ "$effective_install_type" == "skill" && -z "$recorded_digest" && -n "$target_digest" ]]; then if is_compatible_subset "$source_path" "$target_path"; then legacy_subset=true fi @@ -294,7 +336,7 @@ install_plugin() { fi # Perform install - if [[ "$install_type" == "command" ]]; then + if [[ "$effective_install_type" == "command" ]]; then # Single-file command → ~/.claude/commands/.md local src_md="$src_dir/SKILL.md" if [[ ! -f "$src_md" ]]; then @@ -308,13 +350,13 @@ install_plugin() { else mkdir -p "$COMMANDS_DIR" cp "$src_md" "$COMMANDS_DIR/${install_name}.md" - state_set "$install_name" "$version" "$install_type" "$source_digest" + state_set "$install_name" "$version" "$effective_install_type" "$source_digest" ok "$install_name → command (v$version)" INSTALL_ACTION=true fi else - # Skill directory → ~/.claude/skills// + # Standard skill directory → the selected agent's discovery root. local dst_dir="$SKILLS_DIR/$install_name" if [[ "$DRY_RUN" == true ]]; then @@ -324,7 +366,7 @@ install_plugin() { mkdir -p "$dst_dir" # rsync resolved source (handles nested skills/ structures) rsync -a --delete "$src_dir/" "$dst_dir/" - state_set "$install_name" "$version" "$install_type" "$source_digest" + state_set "$install_name" "$version" "$effective_install_type" "$source_digest" ok "$install_name → skill (v$version)" INSTALL_ACTION=true fi @@ -387,7 +429,7 @@ main() { return fi - info "Installing Claude skills from: $REPO_DIR" + info "Installing skills for $AGENT_TARGET from: $REPO_DIR" [[ "$DRY_RUN" == true ]] && warn "DRY RUN — no files will be written" [[ -n "$CATEGORY_FILTER" ]] && info "Category filter: $CATEGORY_FILTER" diff --git a/tests/install-skills.test.sh b/tests/install-skills.test.sh index 3923916..722570f 100755 --- a/tests/install-skills.test.sh +++ b/tests/install-skills.test.sh @@ -13,10 +13,10 @@ mkdir -p "$TEST_HOME" cp "$PROJECT_DIR/install-skills.sh" "$FIXTURE_REPO/install-skills.sh" write_manifest() { - local plugin="$1" name="$2" version="$3" + local plugin="$1" name="$2" version="$3" install_type="${4:-skill}" mkdir -p "$FIXTURE_REPO/skills-dev/${plugin}-plugin/.claude-plugin" cat > "$FIXTURE_REPO/skills-dev/${plugin}-plugin/.claude-plugin/plugin.json" < "$FIXTURE_REPO/skills-dev/example-plugin/skills/references/guide.md" HOME="$TEST_HOME" "$FIXTURE_REPO/install-skills.sh" >/dev/null -test -f "$TEST_HOME/.claude/skills/example/references/guide.md" +test -f "$TEST_HOME/.agents/skills/example/references/guide.md" # A repository version upgrade replaces an unchanged prior install. write_manifest example example 2.0.0 @@ -43,17 +43,17 @@ description: Installer fixture version two. version two EOF HOME="$TEST_HOME" "$FIXTURE_REPO/install-skills.sh" >/dev/null -grep -q 'version two' "$TEST_HOME/.claude/skills/example/SKILL.md" -grep -q '"version": "2.0.0"' "$TEST_HOME/.claude/.installed-skills.json" +grep -q 'version two' "$TEST_HOME/.agents/skills/example/SKILL.md" +grep -q '"version": "2.0.0"' "$TEST_HOME/.agents/.ai-proj-helper-installed-skills.json" # A local edit is preserved even when the repository advances again. -printf 'local edit\n' >> "$TEST_HOME/.claude/skills/example/SKILL.md" +printf 'local edit\n' >> "$TEST_HOME/.agents/skills/example/SKILL.md" write_manifest example example 3.0.0 printf 'repository version three\n' >> "$FIXTURE_REPO/skills-dev/example-plugin/skills/SKILL.md" output="$(HOME="$TEST_HOME" "$FIXTURE_REPO/install-skills.sh")" grep -q 'local files were modified' <<<"$output" -grep -q 'local edit' "$TEST_HOME/.claude/skills/example/SKILL.md" -if grep -q 'repository version three' "$TEST_HOME/.claude/skills/example/SKILL.md"; then +grep -q 'local edit' "$TEST_HOME/.agents/skills/example/SKILL.md" +if grep -q 'repository version three' "$TEST_HOME/.agents/skills/example/SKILL.md"; then echo 'local modification was overwritten' >&2 exit 1 fi @@ -70,9 +70,45 @@ description: Legacy installation fixture. legacy content EOF printf 'legacy reference\n' > "$FIXTURE_REPO/skills-dev/legacy-plugin/skills/references/guide.md" -mkdir -p "$TEST_HOME/.claude/skills/legacy" -cp "$FIXTURE_REPO/skills-dev/legacy-plugin/skills/SKILL.md" "$TEST_HOME/.claude/skills/legacy/SKILL.md" +mkdir -p "$TEST_HOME/.agents/skills/legacy" +cp "$FIXTURE_REPO/skills-dev/legacy-plugin/skills/SKILL.md" "$TEST_HOME/.agents/skills/legacy/SKILL.md" HOME="$TEST_HOME" "$FIXTURE_REPO/install-skills.sh" >/dev/null -test -f "$TEST_HOME/.claude/skills/legacy/references/guide.md" +test -f "$TEST_HOME/.agents/skills/legacy/references/guide.md" + +# Command manifests become standard Codex skills, while explicit Claude +# installs retain the legacy single-file command layout. Both must be stable on +# a second run despite the Claude filename change. +write_manifest sample-command sample-command 1.0.0 command +mkdir -p "$FIXTURE_REPO/skills-dev/sample-command-plugin/skills" +cat > "$FIXTURE_REPO/skills-dev/sample-command-plugin/skills/SKILL.md" <<'EOF' +--- +name: sample-command +description: Command installation fixture. +--- +command content +EOF + +CODEX_TEST_HOME="$TEST_ROOT/codex-home" +mkdir -p "$CODEX_TEST_HOME" +HOME="$CODEX_TEST_HOME" "$FIXTURE_REPO/install-skills.sh" >/dev/null +test -f "$CODEX_TEST_HOME/.agents/skills/sample-command/SKILL.md" +test ! -e "$CODEX_TEST_HOME/.claude/commands/sample-command.md" +codex_output="$(HOME="$CODEX_TEST_HOME" "$FIXTURE_REPO/install-skills.sh" --dry-run)" +grep -q '0 plugins would be installed/updated' <<<"$codex_output" +if grep -q 'sample-command: local files were modified' <<<"$codex_output"; then + echo 'Codex command skill was reported as modified' >&2 + exit 1 +fi + +CLAUDE_TEST_HOME="$TEST_ROOT/claude-home" +mkdir -p "$CLAUDE_TEST_HOME" +HOME="$CLAUDE_TEST_HOME" "$FIXTURE_REPO/install-skills.sh" --agent claude >/dev/null +test -f "$CLAUDE_TEST_HOME/.claude/commands/sample-command.md" +claude_output="$(HOME="$CLAUDE_TEST_HOME" "$FIXTURE_REPO/install-skills.sh" --agent claude --dry-run)" +grep -q '0 plugins would be installed/updated' <<<"$claude_output" +if grep -q 'sample-command: local files were modified' <<<"$claude_output"; then + echo 'Claude command was reported as modified after filename conversion' >&2 + exit 1 +fi echo 'install-skills tests passed'