diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 96ce6bd5..10791cde 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -11,7 +11,7 @@ "name": "chorus", "source": "./public/chorus-plugin", "description": "Chorus AI-DLC collaboration platform plugin. Automates session lifecycle, provides MCP tools for PM/Developer/Admin workflows, and enables multi-agent team observability.", - "version": "0.5.2", + "version": "0.6.0", "category": "project-management", "tags": ["ai-dlc", "collaboration", "mcp", "session", "multi-agent"] } diff --git a/public/chorus-plugin/.claude-plugin/plugin.json b/public/chorus-plugin/.claude-plugin/plugin.json index 900859b6..c3ecc412 100644 --- a/public/chorus-plugin/.claude-plugin/plugin.json +++ b/public/chorus-plugin/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "chorus", "description": "Chorus AI-DLC collaboration platform plugin for Claude Code. Automates session lifecycle, provides MCP tools for PM/Developer/Admin workflows, and enables multi-agent team observability.", - "version": "0.5.2", + "version": "0.6.0", "author": { "name": "Chorus-AIDLC" }, @@ -16,5 +16,20 @@ "session", "multi-agent", "collaboration" - ] + ], + "userConfig": { + "CHORUS_URL": { + "type": "string", + "title": "Chorus Server URL", + "description": "Your Chorus instance URL (e.g., https://chorus.example.com). Get this from your Chorus admin.", + "required": true + }, + "CHORUS_API_KEY": { + "type": "string", + "title": "Chorus API Key", + "description": "Agent API key starting with cho_ (get it from Chorus Settings > API Keys)", + "required": true, + "sensitive": true + } + } } diff --git a/public/chorus-plugin/.mcp.json b/public/chorus-plugin/.mcp.json index 73db258a..b1878309 100644 --- a/public/chorus-plugin/.mcp.json +++ b/public/chorus-plugin/.mcp.json @@ -2,9 +2,9 @@ "mcpServers": { "chorus": { "type": "http", - "url": "${CHORUS_URL}/api/mcp", + "url": "${user_config.CHORUS_URL}/api/mcp", "headers": { - "Authorization": "Bearer ${CHORUS_API_KEY}" + "Authorization": "Bearer ${user_config.CHORUS_API_KEY}" } } } diff --git a/public/chorus-plugin/bin/chorus-api.sh b/public/chorus-plugin/bin/chorus-api.sh index d6385d82..e7468326 100755 --- a/public/chorus-plugin/bin/chorus-api.sh +++ b/public/chorus-plugin/bin/chorus-api.sh @@ -2,9 +2,12 @@ # chorus-api.sh — Lightweight REST API wrapper for Chorus session management # Used by hook scripts to communicate with Chorus backend. # -# Environment variables: +# Environment variables (either source works, env vars take priority): # CHORUS_URL — Chorus base URL (e.g., http://localhost:3000) # CHORUS_API_KEY — Agent API key (cho_xxx) +# Or via Claude Code plugin userConfig (auto-injected as CLAUDE_PLUGIN_OPTION_*): +# CLAUDE_PLUGIN_OPTION_CHORUS_URL +# CLAUDE_PLUGIN_OPTION_CHORUS_API_KEY # # State file: $CLAUDE_PROJECT_DIR/.chorus/state.json (gitignored) @@ -12,8 +15,9 @@ set -euo pipefail # ===== Configuration ===== -CHORUS_URL="${CHORUS_URL:-}" -CHORUS_API_KEY="${CHORUS_API_KEY:-}" +# Resolve config: env var > userConfig (CLAUDE_PLUGIN_OPTION_*) > empty +CHORUS_URL="${CHORUS_URL:-${CLAUDE_PLUGIN_OPTION_CHORUS_URL:-}}" +CHORUS_API_KEY="${CHORUS_API_KEY:-${CLAUDE_PLUGIN_OPTION_CHORUS_API_KEY:-}}" STATE_DIR="${CLAUDE_PROJECT_DIR:-.}/.chorus" STATE_FILE="${STATE_DIR}/state.json" @@ -25,8 +29,16 @@ die() { } require_env() { - [ -n "$CHORUS_URL" ] || die "CHORUS_URL is not set" - [ -n "$CHORUS_API_KEY" ] || die "CHORUS_API_KEY is not set" + [ -n "$CHORUS_URL" ] || die "CHORUS_URL is not set. Run /plugin to configure Chorus, or export CHORUS_URL." + [ -n "$CHORUS_API_KEY" ] || die "CHORUS_API_KEY is not set. Run /plugin to configure Chorus, or export CHORUS_API_KEY." +} + +# Resolve configuration from env vars or userConfig. +# Callable as subcommand: chorus-api.sh resolve-config +# Outputs shell assignments that can be eval'd by hook scripts. +chorus_resolve_config() { + echo "CHORUS_URL=\"${CHORUS_URL}\"" + echo "CHORUS_API_KEY=\"${CHORUS_API_KEY}\"" } # Make an authenticated API request to Chorus @@ -341,6 +353,7 @@ cmd="${1:-}" shift || true case "$cmd" in + resolve-config) chorus_resolve_config ;; checkin) cmd_checkin "$@" ;; mcp-tool) cmd_mcp_tool "$@" ;; state-get) state_get "${1:-}" ;; diff --git a/public/chorus-plugin/bin/on-pre-enter-plan.sh b/public/chorus-plugin/bin/on-pre-enter-plan.sh index d3b145fc..e5551077 100755 --- a/public/chorus-plugin/bin/on-pre-enter-plan.sh +++ b/public/chorus-plugin/bin/on-pre-enter-plan.sh @@ -6,7 +6,9 @@ set -euo pipefail -[ -z "${CHORUS_URL:-}" ] && exit 0 +# Resolve config: env var > userConfig (CLAUDE_PLUGIN_OPTION_*) +CHORUS_URL="${CHORUS_URL:-${CLAUDE_PLUGIN_OPTION_CHORUS_URL:-}}" +[ -z "$CHORUS_URL" ] && exit 0 SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" API="${SCRIPT_DIR}/chorus-api.sh" diff --git a/public/chorus-plugin/bin/on-pre-exit-plan.sh b/public/chorus-plugin/bin/on-pre-exit-plan.sh index 9622f104..606d4ef9 100755 --- a/public/chorus-plugin/bin/on-pre-exit-plan.sh +++ b/public/chorus-plugin/bin/on-pre-exit-plan.sh @@ -6,7 +6,9 @@ set -euo pipefail -[ -z "${CHORUS_URL:-}" ] && exit 0 +# Resolve config: env var > userConfig (CLAUDE_PLUGIN_OPTION_*) +CHORUS_URL="${CHORUS_URL:-${CLAUDE_PLUGIN_OPTION_CHORUS_URL:-}}" +[ -z "$CHORUS_URL" ] && exit 0 SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" API="${SCRIPT_DIR}/chorus-api.sh" diff --git a/public/chorus-plugin/bin/on-pre-spawn-agent.sh b/public/chorus-plugin/bin/on-pre-spawn-agent.sh index 2c3ac342..9a4b875b 100755 --- a/public/chorus-plugin/bin/on-pre-spawn-agent.sh +++ b/public/chorus-plugin/bin/on-pre-spawn-agent.sh @@ -12,7 +12,9 @@ set -euo pipefail -[ -z "${CHORUS_URL:-}" ] && exit 0 +# Resolve config: env var > userConfig (CLAUDE_PLUGIN_OPTION_*) +CHORUS_URL="${CHORUS_URL:-${CLAUDE_PLUGIN_OPTION_CHORUS_URL:-}}" +[ -z "$CHORUS_URL" ] && exit 0 SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" API="${SCRIPT_DIR}/chorus-api.sh" diff --git a/public/chorus-plugin/bin/on-session-start.sh b/public/chorus-plugin/bin/on-session-start.sh index eb3b66b5..1726c874 100755 --- a/public/chorus-plugin/bin/on-session-start.sh +++ b/public/chorus-plugin/bin/on-session-start.sh @@ -17,11 +17,15 @@ if [ ! -t 0 ]; then EVENT=$(cat) fi +# Resolve config: env var > userConfig (CLAUDE_PLUGIN_OPTION_*) +CHORUS_URL="${CHORUS_URL:-${CLAUDE_PLUGIN_OPTION_CHORUS_URL:-}}" +CHORUS_API_KEY="${CHORUS_API_KEY:-${CLAUDE_PLUGIN_OPTION_CHORUS_API_KEY:-}}" + # Check if Chorus environment is configured -if [ -z "${CHORUS_URL:-}" ] || [ -z "${CHORUS_API_KEY:-}" ]; then +if [ -z "$CHORUS_URL" ] || [ -z "$CHORUS_API_KEY" ]; then "$API" hook-output \ - "Chorus plugin: not configured (set CHORUS_URL and CHORUS_API_KEY)" \ - "Chorus environment not configured. Set CHORUS_URL and CHORUS_API_KEY to enable Chorus integration." \ + "Chorus plugin: not configured. Run /plugin to set up, or export CHORUS_URL and CHORUS_API_KEY." \ + "Chorus not configured. Run /plugin to configure Chorus, or set CHORUS_URL and CHORUS_API_KEY env vars." \ "SessionStart" exit 0 fi diff --git a/public/chorus-plugin/bin/on-subagent-start.sh b/public/chorus-plugin/bin/on-subagent-start.sh index 4f0c2144..6809bb8f 100755 --- a/public/chorus-plugin/bin/on-subagent-start.sh +++ b/public/chorus-plugin/bin/on-subagent-start.sh @@ -20,8 +20,12 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" API="${SCRIPT_DIR}/chorus-api.sh" +# Resolve config: env var > userConfig (CLAUDE_PLUGIN_OPTION_*) +CHORUS_URL="${CHORUS_URL:-${CLAUDE_PLUGIN_OPTION_CHORUS_URL:-}}" +CHORUS_API_KEY="${CHORUS_API_KEY:-${CLAUDE_PLUGIN_OPTION_CHORUS_API_KEY:-}}" + # Check environment -if [ -z "${CHORUS_URL:-}" ] || [ -z "${CHORUS_API_KEY:-}" ]; then +if [ -z "$CHORUS_URL" ] || [ -z "$CHORUS_API_KEY" ]; then exit 0 fi diff --git a/public/chorus-plugin/bin/on-subagent-stop.sh b/public/chorus-plugin/bin/on-subagent-stop.sh index 4a2f94b5..cc0bb4bb 100755 --- a/public/chorus-plugin/bin/on-subagent-stop.sh +++ b/public/chorus-plugin/bin/on-subagent-stop.sh @@ -11,8 +11,12 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" API="${SCRIPT_DIR}/chorus-api.sh" +# Resolve config: env var > userConfig (CLAUDE_PLUGIN_OPTION_*) +CHORUS_URL="${CHORUS_URL:-${CLAUDE_PLUGIN_OPTION_CHORUS_URL:-}}" +CHORUS_API_KEY="${CHORUS_API_KEY:-${CLAUDE_PLUGIN_OPTION_CHORUS_API_KEY:-}}" + # Check environment -if [ -z "${CHORUS_URL:-}" ] || [ -z "${CHORUS_API_KEY:-}" ]; then +if [ -z "$CHORUS_URL" ] || [ -z "$CHORUS_API_KEY" ]; then exit 0 fi diff --git a/public/chorus-plugin/bin/on-task-completed.sh b/public/chorus-plugin/bin/on-task-completed.sh index 15e1b21f..a4d310cd 100755 --- a/public/chorus-plugin/bin/on-task-completed.sh +++ b/public/chorus-plugin/bin/on-task-completed.sh @@ -11,8 +11,12 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" API="${SCRIPT_DIR}/chorus-api.sh" +# Resolve config: env var > userConfig (CLAUDE_PLUGIN_OPTION_*) +CHORUS_URL="${CHORUS_URL:-${CLAUDE_PLUGIN_OPTION_CHORUS_URL:-}}" +CHORUS_API_KEY="${CHORUS_API_KEY:-${CLAUDE_PLUGIN_OPTION_CHORUS_API_KEY:-}}" + # Check environment -if [ -z "${CHORUS_URL:-}" ] || [ -z "${CHORUS_API_KEY:-}" ]; then +if [ -z "$CHORUS_URL" ] || [ -z "$CHORUS_API_KEY" ]; then exit 0 fi diff --git a/public/chorus-plugin/bin/on-teammate-idle.sh b/public/chorus-plugin/bin/on-teammate-idle.sh index 65b9a5fd..ced04888 100755 --- a/public/chorus-plugin/bin/on-teammate-idle.sh +++ b/public/chorus-plugin/bin/on-teammate-idle.sh @@ -10,8 +10,12 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" API="${SCRIPT_DIR}/chorus-api.sh" +# Resolve config: env var > userConfig (CLAUDE_PLUGIN_OPTION_*) +CHORUS_URL="${CHORUS_URL:-${CLAUDE_PLUGIN_OPTION_CHORUS_URL:-}}" +CHORUS_API_KEY="${CHORUS_API_KEY:-${CLAUDE_PLUGIN_OPTION_CHORUS_API_KEY:-}}" + # Check environment -if [ -z "${CHORUS_URL:-}" ] || [ -z "${CHORUS_API_KEY:-}" ]; then +if [ -z "$CHORUS_URL" ] || [ -z "$CHORUS_API_KEY" ]; then exit 0 fi diff --git a/public/chorus-plugin/bin/on-user-prompt.sh b/public/chorus-plugin/bin/on-user-prompt.sh index e8a5a2d9..eb351aa0 100755 --- a/public/chorus-plugin/bin/on-user-prompt.sh +++ b/public/chorus-plugin/bin/on-user-prompt.sh @@ -12,8 +12,12 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" STATE_DIR="${CLAUDE_PROJECT_DIR:-.}/.chorus" SESSIONS_DIR="${STATE_DIR}/sessions" +# Resolve config: env var > userConfig (CLAUDE_PLUGIN_OPTION_*) +CHORUS_URL="${CHORUS_URL:-${CLAUDE_PLUGIN_OPTION_CHORUS_URL:-}}" +CHORUS_API_KEY="${CHORUS_API_KEY:-${CLAUDE_PLUGIN_OPTION_CHORUS_API_KEY:-}}" + # Skip entirely if Chorus is not configured -if [ -z "${CHORUS_URL:-}" ] || [ -z "${CHORUS_API_KEY:-}" ]; then +if [ -z "$CHORUS_URL" ] || [ -z "$CHORUS_API_KEY" ]; then exit 0 fi diff --git a/public/chorus-plugin/bin/test-syntax.sh b/public/chorus-plugin/bin/test-syntax.sh index 2eba6a33..3e879b3f 100644 --- a/public/chorus-plugin/bin/test-syntax.sh +++ b/public/chorus-plugin/bin/test-syntax.sh @@ -22,6 +22,8 @@ echo "" # we only care about bash syntax/substitution errors before the API call. export CHORUS_URL="http://localhost:0" export CHORUS_API_KEY="cho_test" +export CLAUDE_PLUGIN_OPTION_CHORUS_URL="http://localhost:0" +export CLAUDE_PLUGIN_OPTION_CHORUS_API_KEY="cho_test" export CLAUDE_PROJECT_DIR="/tmp/chorus-test-$$" mkdir -p "$CLAUDE_PROJECT_DIR" diff --git a/public/chorus-plugin/skills/chorus/SKILL.md b/public/chorus-plugin/skills/chorus/SKILL.md index 5f2c46d4..8b501275 100644 --- a/public/chorus-plugin/skills/chorus/SKILL.md +++ b/public/chorus-plugin/skills/chorus/SKILL.md @@ -221,47 +221,54 @@ Use @mentions to notify specific users or agents. Mention syntax: `@[DisplayName ## Setup -### 1. Obtain API Key +### Option A: Plugin Install (Recommended) -API Keys must be created manually by the user in the Chorus Web UI. +When the Chorus plugin is installed or enabled, Claude Code automatically prompts for: +- **Chorus Server URL** — your Chorus instance (e.g., `https://chorus.example.com`) +- **Chorus API Key** — agent API key starting with `cho_` (stored securely in system keychain) -**Ask the user to:** -1. Open the Chorus settings page (e.g., `http://localhost:3000/settings`) +To obtain an API Key, **ask the user to:** +1. Open the Chorus settings page (e.g., `https://chorus.example.com/settings`) 2. Click **Create API Key** 3. Enter Agent name, select role (Developer / PM / Admin) 4. Click create and **immediately copy the key** (shown only once) -**Security notes:** -- Each Agent should have its own API Key with the minimum required role -- API Keys should not be committed to version control +To reconfigure later, use `/plugin` in Claude Code. -### 2. MCP Server Configuration +### Option B: Environment Variables (Headless / CI / Docker) -Config file: `.mcp.json` in the project root (or globally at `~/.claude/.mcp.json`). +For environments without a system keychain, set environment variables directly: -```json -{ - "mcpServers": { - "chorus": { - "type": "http", - "url": "/api/mcp", - "headers": { - "Authorization": "Bearer " - } - } - } -} +```bash +export CHORUS_URL="https://chorus.example.com" +export CHORUS_API_KEY="cho_your_key_here" +``` + +Environment variables always take priority over plugin userConfig. This is the recommended approach for CI/CD pipelines, Docker containers, and remote servers. + +### Optional: Default Project + +Set `CHORUS_DEFAULT_PROJECT` to auto-select a project (per working directory): + +```bash +export CHORUS_DEFAULT_PROJECT="" ``` -Restart Claude Code after configuration. +This is an environment variable only — not part of plugin userConfig, since it varies per project directory. + +### Security Notes + +- Each Agent should have its own API Key with the minimum required role +- API Keys should not be committed to version control +- With plugin userConfig, the API Key is stored in the system keychain (not in settings files) -### 3. Verify Connection +### Verify Connection ``` chorus_checkin() ``` -If it fails, check: API Key correct (`cho_` prefix)? URL reachable? Claude Code restarted? +If it fails, check: API Key correct (`cho_` prefix)? URL reachable? Claude Code restarted after config? ### 4. Role-Specific Tool Access