mirror of
https://github.com/mims-harvard/ToolUniverse.git
synced 2026-09-19 07:31:47 +08:00
ac41c28fe8
* feat: add API key metadata catalog source * feat: add API key catalog generator * feat: generate API key catalog and regenerate .env.template * ci: sync API key catalog from tool configs * feat: load global ~/.tooluniverse/.env from any workspace * feat: add .env read/mask/merge helper for key setup * feat: add graphical API key setup server * feat: add /tooluniverse:setup-keys command and package it * feat: redesign setup-keys page with domain grouping and per-key context - Group keys by research domain (Genomics & Variants, Drugs & Chemistry, Proteins & Structure, Literature & Patents, Clinical & Safety, Models & Infrastructure) instead of Required/Optional/Endpoints. - Each key shows its purpose (what it unlocks) and a without-it note (blocked / demo mode / lower limits / leave blank unless self-hosting). - Light product UI: white cards in responsive grid, indigo accent, Hanken Grotesk + IBM Plex Mono, live progress meter, show/hide toggles. - Enrich api_key_metadata.json with domain, purpose, without fields; generator emits them in api_keys_catalog.json; .env.template regrouped by domain with per-key Without notes. * fix: exclude __pycache__ from plugin scripts dir during build cp -r was shipping stray .pyc files / __pycache__ dirs into the built plugin if a developer had run the scripts locally before building. Switch to rsync with explicit excludes, and add a regression test that plants a canary __pycache__ before the build and asserts dist/ stays clean. * fix: split one-line try/except in pycache regression test (ruff E701) * refactor: declare API key metadata inline in tool configs (api_key_info) Instead of a separate api_key_metadata.json, each tool config now carries an api_key_info block next to its required/optional_api_keys. The generator scans configs for both the key names and their info, so adding a new key is a one-file edit. Deletes api_key_metadata.json; runtime code is untouched (required_api_keys stays a list of strings). Catalog + .env.template output is byte-identical. * chore: sync API key catalog [skip ci] * docs: document api_key_info convention in custom-tool skill The JSON-tool authoring guide now explains declaring required/optional_api_keys and the inline api_key_info block (domain/purpose/without/register_url/type) that flows into the /tooluniverse:setup-keys UI and .env.template, plus the generator + CI auto-sync step. * fix: add UMLS, ICD-11, Gemini, OpenRouter keys to the catalog These keys are read by umls_tool / icd_tool / llm_clients via os.getenv but were never declared in any tool config, so the generated catalog and .env.template omitted them (UMLS_API_KEY + ICD_CLIENT_ID/SECRET regressed vs main). Declare them as optional_api_keys with api_key_info on the relevant tools so they appear in /tooluniverse:setup-keys and .env.template. Runtime load behavior unchanged (optional, not required). --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
117 lines
4.0 KiB
Bash
Executable File
117 lines
4.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# Build the ToolUniverse Claude Code plugin
|
|
# Assembles: manifest + MCP config + skills + commands + agents
|
|
|
|
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
PLUGIN_SRC="$REPO_ROOT/plugin"
|
|
DIST_DIR="$REPO_ROOT/dist/tooluniverse-plugin"
|
|
|
|
echo "Building ToolUniverse Claude Code plugin..."
|
|
|
|
# Clean previous build
|
|
rm -rf "$DIST_DIR"
|
|
mkdir -p "$DIST_DIR"
|
|
|
|
# 1. Copy plugin manifest (plugin.json only — marketplace.json is for
|
|
# local-dev marketplace at plugin source; it should NOT ship inside the
|
|
# distributed plugin. Per official guidance, .claude-plugin/ at a plugin
|
|
# root holds only plugin.json.)
|
|
mkdir -p "$DIST_DIR/.claude-plugin"
|
|
cp "$PLUGIN_SRC/.claude-plugin/plugin.json" "$DIST_DIR/.claude-plugin/"
|
|
echo " [+] Plugin manifest (plugin.json only)"
|
|
|
|
# 2. Copy MCP config
|
|
cp "$PLUGIN_SRC/.mcp.json" "$DIST_DIR/"
|
|
echo " [+] MCP server config"
|
|
|
|
# 3. Copy settings
|
|
cp "$PLUGIN_SRC/settings.json" "$DIST_DIR/"
|
|
echo " [+] Default settings"
|
|
|
|
# 4. Copy commands
|
|
cp -r "$PLUGIN_SRC/commands" "$DIST_DIR/"
|
|
echo " [+] Slash commands"
|
|
|
|
# 4b. Copy helper scripts + bundle the generated API key catalog next to them.
|
|
# Use rsync to exclude __pycache__ / *.pyc so stray local artifacts don't ship.
|
|
rsync -a --exclude='__pycache__' --exclude='*.pyc' \
|
|
"$PLUGIN_SRC/scripts/" "$DIST_DIR/scripts/"
|
|
cp "$REPO_ROOT/src/tooluniverse/data/api_keys_catalog.json" "$DIST_DIR/scripts/"
|
|
echo " [+] Setup scripts + API key catalog"
|
|
|
|
# 5. Copy agents
|
|
cp -r "$PLUGIN_SRC/agents" "$DIST_DIR/"
|
|
echo " [+] Research agent"
|
|
|
|
# 6. Copy hooks (SessionStart: clean up global skills)
|
|
if [ -d "$PLUGIN_SRC/hooks" ]; then
|
|
cp -r "$PLUGIN_SRC/hooks" "$DIST_DIR/"
|
|
echo " [+] Hooks (SessionStart cleanup)"
|
|
fi
|
|
|
|
# 7. Copy README, CHANGELOG, and LICENSE
|
|
cp "$PLUGIN_SRC/README.md" "$DIST_DIR/"
|
|
if [ -f "$PLUGIN_SRC/CHANGELOG.md" ]; then
|
|
cp "$PLUGIN_SRC/CHANGELOG.md" "$DIST_DIR/"
|
|
fi
|
|
# Apache-2.0 compliance: ship the LICENSE file with the distributed plugin.
|
|
# Source from the repo root since the plugin inherits the parent license.
|
|
if [ -f "$REPO_ROOT/LICENSE" ]; then
|
|
cp "$REPO_ROOT/LICENSE" "$DIST_DIR/"
|
|
fi
|
|
echo " [+] README + CHANGELOG + LICENSE"
|
|
|
|
# 7. Copy ALL tooluniverse skills
|
|
# The router is the only auto-matchable skill (visible description).
|
|
# All sub-skills have disable-model-invocation: true — their descriptions
|
|
# don't appear in context, so having 114 skills doesn't affect routing.
|
|
# They're available when the router dispatches via Skill('name').
|
|
#
|
|
# Exclude development artifacts that should NOT ship:
|
|
# test_*.py / *_test.py — internal skill tests (some reference internal
|
|
# benchmark names that would leak to end users)
|
|
# __pycache__/, *.pyc, .pytest_cache/ — Python build/cache artifacts
|
|
# .DS_Store — macOS folder metadata
|
|
mkdir -p "$DIST_DIR/skills"
|
|
skill_count=0
|
|
for skill_dir in "$REPO_ROOT/skills"/*/; do
|
|
dir_name=$(basename "$skill_dir")
|
|
if [ -f "$skill_dir/SKILL.md" ] && [[ "$dir_name" == tooluniverse* ]]; then
|
|
rsync -a \
|
|
--exclude='test_*.py' \
|
|
--exclude='*_test.py' \
|
|
--exclude='__pycache__/' \
|
|
--exclude='*.pyc' \
|
|
--exclude='.pytest_cache/' \
|
|
--exclude='.coverage' \
|
|
--exclude='.coverage.*' \
|
|
--exclude='coverage.xml' \
|
|
--exclude='htmlcov/' \
|
|
--exclude='.mypy_cache/' \
|
|
--exclude='.ruff_cache/' \
|
|
--exclude='.DS_Store' \
|
|
"$skill_dir" "$DIST_DIR/skills/$dir_name/"
|
|
skill_count=$((skill_count + 1))
|
|
fi
|
|
done
|
|
echo " [+] $skill_count skills (1 router + $((skill_count - 1)) sub-skills with disable-model-invocation)"
|
|
|
|
# 8. Summary
|
|
total_files=$(find "$DIST_DIR" -type f | wc -l | tr -d ' ')
|
|
total_size=$(du -sh "$DIST_DIR" | cut -f1)
|
|
|
|
echo ""
|
|
echo "Plugin built successfully!"
|
|
echo " Location: $DIST_DIR"
|
|
echo " Files: $total_files"
|
|
echo " Size: $total_size"
|
|
echo ""
|
|
echo "Install:"
|
|
echo " claude --plugin-dir $DIST_DIR"
|
|
echo ""
|
|
echo "Or test with:"
|
|
echo " ls $DIST_DIR/.claude-plugin/plugin.json"
|
|
echo " ls $DIST_DIR/skills/ | head -10"
|