From 8642bb539aab48acb71ed9dfc7e1badef194ba20 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Wed, 25 Feb 2026 18:59:49 -0600 Subject: [PATCH] Migrate Gitea ops to MCP, update Paper Dynasty skill, sync plugins - CLAUDE.md + commit-push-pr: prefer gitea-mcp over tea CLI - Paper Dynasty: updated api_client, cli, distribute_packs - New skill: resume-tailoring - Plugins: updated marketplaces, blocklist, install counts - Settings and MCP config updates Co-Authored-By: Claude Opus 4.6 --- .mcp.json | 16 +- CLAUDE.md | 12 +- commands/commit-push-pr.md | 4 +- mcp-needs-auth-cache.json | 1 + plugins/blocklist.json | 2 +- plugins/install-counts-cache.json | 662 ++++++++++-------- plugins/installed_plugins.json | 16 +- plugins/known_marketplaces.json | 4 +- plugins/marketplaces/claude-code-plugins | 2 +- plugins/marketplaces/claude-plugins-official | 2 +- settings.json | 40 +- skills/paper-dynasty/api_client.py | 321 +++++---- skills/paper-dynasty/cli.py | 300 +++++--- .../paper-dynasty/scripts/distribute_packs.py | 53 +- skills/resume-tailoring | 1 + 15 files changed, 853 insertions(+), 583 deletions(-) create mode 100644 mcp-needs-auth-cache.json create mode 160000 skills/resume-tailoring diff --git a/.mcp.json b/.mcp.json index 56cfc15..8e62c85 100644 --- a/.mcp.json +++ b/.mcp.json @@ -5,19 +5,5 @@ "type": "stdio", "args": [], "env": {} - }, - "n8n-mcp": { - "command": "npx", - "type": "stdio", - "args": ["n8n-mcp"], - "env": { - "MCP_MODE": "stdio", - "N8N_API_URL": "http://10.10.0.210:5678", - "N8N_API_KEY": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIwNGQ1MTNhYi1lOGI1LTQxZjktYmNjNi05MTM1MzgyYzQ0YWEiLCJpc3MiOiJuOG4iLCJhdWQiOiJwdWJsaWMtYXBpIiwiaWF0IjoxNzcxNTU3ODMwfQ.PF6AatWZh8hwyUydT8UsJ16ML61XFTixj2IvuP9MRzo", - "N8N_MCP_TELEMETRY_DISABLED": "true", - "DISABLE_CONSOLE_OUTPUT": "true", - "LOG_LEVEL": "error" - } - } - } + } } } diff --git a/CLAUDE.md b/CLAUDE.md index f115e17..73cb064 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,12 +14,12 @@ Automatic loads are NOT enough — Read loads required CLAUDE.md context along t - Applies to: git commit, git add, git tag, git push, deploy scripts ## Gitea Operations -**ALWAYS use `tea` CLI for Gitea.** Never use `gh api --hostname`. -- Authenticated: `cal@homelab` (https://git.manticorum.com) -- **Always pass `--repo owner/name`** — tea can't auto-detect the repo from git remotes and fails with "path segment is empty" -- Common: `tea repos list`, `tea pulls list --repo cal/repo`, `tea issues list --repo cal/repo` -- Create PR: `tea pulls create --repo cal/repo --head --base main --title "Title" --description "Desc"` -- Common repos: cal/major-domo-v2, cal/major-domo-database, cal/major-domo-bot, cal/paper-dynasty, cal/paper-dynasty-database +**Prefer the `gitea-mcp` MCP server** for all Gitea operations (PRs, issues, branches, labels, releases, Actions). +- MCP tools use `owner` + `repo` params (e.g. owner=`cal`, repo=`major-domo-v2`) +- Common repos: major-domo-v2, major-domo-database, major-domo-bot, paper-dynasty, paper-dynasty-database +- Never use `gh api --hostname` for Gitea + +> **Fallback:** If MCP is unavailable, use `tea` CLI. Always pass `--repo owner/name`. ## Tech Preferences - Python with uv for package/environment management diff --git a/commands/commit-push-pr.md b/commands/commit-push-pr.md index aff4eba..afdd436 100644 --- a/commands/commit-push-pr.md +++ b/commands/commit-push-pr.md @@ -1,5 +1,5 @@ --- -allowed-tools: Bash(git checkout:*), Bash(git add:*), Bash(git status:*), Bash(git push:*), Bash(git commit:*), Bash(git branch:*), Bash(git remote:*), Bash(git symbolic-ref:*), Bash(git log:*), Bash(gh pr create:*), Bash(tea pulls create:*) +allowed-tools: Bash(git checkout:*), Bash(git add:*), Bash(git status:*), Bash(git push:*), Bash(git commit:*), Bash(git branch:*), Bash(git remote:*), Bash(git symbolic-ref:*), Bash(git log:*), Bash(gh pr create:*), mcp__gitea-mcp__create_pull_request description: Commit, push, and open a PR --- @@ -25,7 +25,7 @@ Based on the above changes: 6. Push the branch to origin with `-u` flag 7. Create a pull request: - If remote URL contains `github.com` → use `gh pr create --base --title "Title" --body "..."` - - If remote URL contains `git.manticorum.com` or other Gitea host → use `tea pulls create --head --base --title "Title" --description "..."` + - If remote URL contains `git.manticorum.com` or other Gitea host → use `mcp__gitea-mcp__create_pull_request` with `owner`, `repo`, `title`, `body`, `head`, `base` 8. Include a summary section and test plan in the PR body 9. Return the PR URL diff --git a/mcp-needs-auth-cache.json b/mcp-needs-auth-cache.json new file mode 100644 index 0000000..9b17309 --- /dev/null +++ b/mcp-needs-auth-cache.json @@ -0,0 +1 @@ +{"claude.ai Google Calendar":{"timestamp":1772062089205},"claude.ai Gmail":{"timestamp":1772062089218}} \ No newline at end of file diff --git a/plugins/blocklist.json b/plugins/blocklist.json index 971301d..dc6894e 100644 --- a/plugins/blocklist.json +++ b/plugins/blocklist.json @@ -1,5 +1,5 @@ { - "fetchedAt": "2026-02-20T04:07:24.860Z", + "fetchedAt": "2026-02-25T22:40:22.318Z", "plugins": [ { "plugin": "code-review@claude-plugins-official", diff --git a/plugins/install-counts-cache.json b/plugins/install-counts-cache.json index 28fc4b3..e34cbc6 100644 --- a/plugins/install-counts-cache.json +++ b/plugins/install-counts-cache.json @@ -1,230 +1,234 @@ { "version": 1, - "fetchedAt": "2026-02-19T04:03:05.754Z", + "fetchedAt": "2026-02-22T05:53:29.726Z", "counts": [ { "plugin": "frontend-design@claude-plugins-official", - "unique_installs": 183759 + "unique_installs": 211218 }, { "plugin": "context7@claude-plugins-official", - "unique_installs": 115503 + "unique_installs": 127061 }, { "plugin": "code-review@claude-plugins-official", - "unique_installs": 91933 - }, - { - "plugin": "github@claude-plugins-official", - "unique_installs": 83345 - }, - { - "plugin": "feature-dev@claude-plugins-official", - "unique_installs": 80588 - }, - { - "plugin": "code-simplifier@claude-plugins-official", - "unique_installs": 73439 + "unique_installs": 103962 }, { "plugin": "superpowers@claude-plugins-official", - "unique_installs": 72147 + "unique_installs": 92356 + }, + { + "plugin": "github@claude-plugins-official", + "unique_installs": 92248 + }, + { + "plugin": "feature-dev@claude-plugins-official", + "unique_installs": 89510 + }, + { + "plugin": "code-simplifier@claude-plugins-official", + "unique_installs": 84564 }, { "plugin": "ralph-loop@claude-plugins-official", - "unique_installs": 67562 + "unique_installs": 74996 }, { "plugin": "typescript-lsp@claude-plugins-official", - "unique_installs": 61765 + "unique_installs": 69574 }, { "plugin": "playwright@claude-plugins-official", - "unique_installs": 60378 + "unique_installs": 69512 }, { "plugin": "commit-commands@claude-plugins-official", - "unique_installs": 52976 - }, - { - "plugin": "serena@claude-plugins-official", - "unique_installs": 47364 + "unique_installs": 58414 }, { "plugin": "security-guidance@claude-plugins-official", - "unique_installs": 47274 + "unique_installs": 53585 + }, + { + "plugin": "serena@claude-plugins-official", + "unique_installs": 49897 }, { "plugin": "pr-review-toolkit@claude-plugins-official", - "unique_installs": 35736 - }, - { - "plugin": "pyright-lsp@claude-plugins-official", - "unique_installs": 32249 - }, - { - "plugin": "figma@claude-plugins-official", - "unique_installs": 31936 - }, - { - "plugin": "supabase@claude-plugins-official", - "unique_installs": 31746 + "unique_installs": 39827 }, { "plugin": "claude-md-management@claude-plugins-official", - "unique_installs": 28868 + "unique_installs": 39554 + }, + { + "plugin": "figma@claude-plugins-official", + "unique_installs": 36609 + }, + { + "plugin": "pyright-lsp@claude-plugins-official", + "unique_installs": 35872 + }, + { + "plugin": "supabase@claude-plugins-official", + "unique_installs": 34374 }, { "plugin": "agent-sdk-dev@claude-plugins-official", - "unique_installs": 27597 - }, - { - "plugin": "ralph-wiggum@claude-plugins-official", - "unique_installs": 27127 + "unique_installs": 29685 }, { "plugin": "atlassian@claude-plugins-official", - "unique_installs": 26859 + "unique_installs": 29214 }, { - "plugin": "plugin-dev@claude-plugins-official", - "unique_installs": 21681 - }, - { - "plugin": "explanatory-output-style@claude-plugins-official", - "unique_installs": 21571 - }, - { - "plugin": "greptile@claude-plugins-official", - "unique_installs": 20163 + "plugin": "ralph-wiggum@claude-plugins-official", + "unique_installs": 27164 }, { "plugin": "claude-code-setup@claude-plugins-official", - "unique_installs": 19541 + "unique_installs": 25066 + }, + { + "plugin": "plugin-dev@claude-plugins-official", + "unique_installs": 24060 + }, + { + "plugin": "explanatory-output-style@claude-plugins-official", + "unique_installs": 23970 + }, + { + "plugin": "greptile@claude-plugins-official", + "unique_installs": 22699 }, { "plugin": "Notion@claude-plugins-official", - "unique_installs": 18738 + "unique_installs": 20513 }, { "plugin": "hookify@claude-plugins-official", - "unique_installs": 18502 - }, - { - "plugin": "linear@claude-plugins-official", - "unique_installs": 15749 + "unique_installs": 20396 }, { "plugin": "vercel@claude-plugins-official", - "unique_installs": 15454 + "unique_installs": 17352 + }, + { + "plugin": "linear@claude-plugins-official", + "unique_installs": 17226 }, { "plugin": "learning-output-style@claude-plugins-official", - "unique_installs": 14537 + "unique_installs": 16023 }, { "plugin": "slack@claude-plugins-official", - "unique_installs": 12797 + "unique_installs": 14454 }, { "plugin": "sentry@claude-plugins-official", - "unique_installs": 12586 + "unique_installs": 13925 }, { "plugin": "gopls-lsp@claude-plugins-official", - "unique_installs": 12195 + "unique_installs": 13515 }, { "plugin": "csharp-lsp@claude-plugins-official", - "unique_installs": 11692 + "unique_installs": 13187 }, { "plugin": "gitlab@claude-plugins-official", - "unique_installs": 10720 - }, - { - "plugin": "rust-analyzer-lsp@claude-plugins-official", - "unique_installs": 10634 + "unique_installs": 11947 }, { "plugin": "stripe@claude-plugins-official", - "unique_installs": 10475 + "unique_installs": 11832 }, { - "plugin": "laravel-boost@claude-plugins-official", - "unique_installs": 9552 - }, - { - "plugin": "php-lsp@claude-plugins-official", - "unique_installs": 9213 - }, - { - "plugin": "jdtls-lsp@claude-plugins-official", - "unique_installs": 9152 + "plugin": "rust-analyzer-lsp@claude-plugins-official", + "unique_installs": 11762 }, { "plugin": "playground@claude-plugins-official", - "unique_installs": 8414 + "unique_installs": 11389 + }, + { + "plugin": "php-lsp@claude-plugins-official", + "unique_installs": 10298 + }, + { + "plugin": "laravel-boost@claude-plugins-official", + "unique_installs": 10204 + }, + { + "plugin": "jdtls-lsp@claude-plugins-official", + "unique_installs": 10186 }, { "plugin": "clangd-lsp@claude-plugins-official", - "unique_installs": 8112 - }, - { - "plugin": "swift-lsp@claude-plugins-official", - "unique_installs": 7732 - }, - { - "plugin": "firebase@claude-plugins-official", - "unique_installs": 7707 + "unique_installs": 8996 }, { "plugin": "huggingface-skills@claude-plugins-official", - "unique_installs": 7615 + "unique_installs": 8946 + }, + { + "plugin": "firebase@claude-plugins-official", + "unique_installs": 8581 + }, + { + "plugin": "swift-lsp@claude-plugins-official", + "unique_installs": 8511 }, { "plugin": "kotlin-lsp@claude-plugins-official", - "unique_installs": 5312 - }, - { - "plugin": "lua-lsp@claude-plugins-official", - "unique_installs": 4824 + "unique_installs": 6192 }, { "plugin": "coderabbit@claude-plugins-official", - "unique_installs": 4356 + "unique_installs": 5806 }, { - "plugin": "asana@claude-plugins-official", - "unique_installs": 3331 + "plugin": "lua-lsp@claude-plugins-official", + "unique_installs": 5368 }, { "plugin": "circleback@claude-plugins-official", - "unique_installs": 3253 + "unique_installs": 3806 + }, + { + "plugin": "asana@claude-plugins-official", + "unique_installs": 3804 }, { "plugin": "pinecone@claude-plugins-official", - "unique_installs": 2999 + "unique_installs": 3449 + }, + { + "plugin": "firecrawl@claude-plugins-official", + "unique_installs": 3336 + }, + { + "plugin": "posthog@claude-plugins-official", + "unique_installs": 2827 }, { "plugin": "claude-opus-4-5-migration@claude-plugins-official", "unique_installs": 2714 }, - { - "plugin": "posthog@claude-plugins-official", - "unique_installs": 2258 - }, - { - "plugin": "firecrawl@claude-plugins-official", - "unique_installs": 1486 - }, { "plugin": "sonatype-guide@claude-plugins-official", - "unique_installs": 931 + "unique_installs": 2111 + }, + { + "plugin": "skill-creator@claude-plugins-official", + "unique_installs": 327 }, { "plugin": "figma-mcp@claude-plugins-official", - "unique_installs": 99 + "unique_installs": 101 }, { "plugin": "artifact@claude-plugins-official", @@ -235,7 +239,11 @@ "unique_installs": 31 }, { - "plugin": "pm@claude-plugins-official", + "plugin": "document-skills@claude-plugins-official", + "unique_installs": 2 + }, + { + "plugin": "agent-browser@claude-plugins-official", "unique_installs": 2 }, { @@ -243,187 +251,11 @@ "unique_installs": 2 }, { - "plugin": "codex-skills@claude-plugins-official", - "unique_installs": 1 + "plugin": "pm@claude-plugins-official", + "unique_installs": 2 }, { - "plugin": "dev-workflow@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "datadog@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "omnisharp-lsp@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "perlnavigator-lsp@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "bun-typescript@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "typescript-native-lsp@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "docs-search-tool@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "feature-ears@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "n8n@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "lorikeet-qa@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "vectorhub-memory@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "project-collaboration-system@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "openspec@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "claude-memory@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "hardworking@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "terraform-ls@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "design-principles@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "creative-music-output-style@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "freshservice@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "n8n-skills@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "context@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "dj-content-creator@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "csharp-roslyn-lsp@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "memory-agent@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "my-time-plugin@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "miro@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "prototyper@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "lean-lsp@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "agent-browser@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "gdscript-lsp@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "dev-sandbox@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "ccpm@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "gitlab-mr-review@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "review-submission@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "document-skills@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "forge-security@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "ocpm@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "pyrefly-lsp@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "hosts-db@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "test-automation-generator@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "frontend-lab@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "dune@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "dokploy@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "monday@claude-plugins-official", - "unique_installs": 1 - }, - { - "plugin": "microsoft-learn@claude-plugins-official", + "plugin": "hello-world@claude-plugins-official", "unique_installs": 1 }, { @@ -431,11 +263,43 @@ "unique_installs": 1 }, { - "plugin": "backend-specialist@claude-plugins-official", + "plugin": "ocpm@claude-plugins-official", "unique_installs": 1 }, { - "plugin": "autonomous-loop@claude-plugins-official", + "plugin": "omnisharp-lsp@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "dev-sandbox@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "feature-ears@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "context@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "creative-music-output-style@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "latex2cn@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "pdf2latex@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "n8n-skills@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "user-journey-analysis@claude-plugins-official", "unique_installs": 1 }, { @@ -443,43 +307,235 @@ "unique_installs": 1 }, { - "plugin": "it-triage-system@claude-plugins-official", + "plugin": "memory-agent@claude-plugins-official", "unique_installs": 1 }, { - "plugin": "airtable@claude-plugins-official", + "plugin": "lean-lsp@claude-plugins-official", "unique_installs": 1 }, { - "plugin": "claude-rules-generator@claude-plugins-official", + "plugin": "forge-security@claude-plugins-official", "unique_installs": 1 }, { - "plugin": "beast-plan@claude-plugins-official", + "plugin": "typescript-native-lsp@claude-plugins-official", "unique_installs": 1 }, { "plugin": "aws-diagram@claude-plugins-official", "unique_installs": 1 }, + { + "plugin": "freshservice@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "design-principles@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "project-collaboration-system@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "n8n@claude-plugins-official", + "unique_installs": 1 + }, { "plugin": "rs-commands@claude-plugins-official", "unique_installs": 1 }, + { + "plugin": "docs-search-tool@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "dune@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "perlnavigator-lsp@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "claude-memory@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "agent-teams@claude-plugins-official", + "unique_installs": 1 + }, { "plugin": "vertical-builder@claude-plugins-official", "unique_installs": 1 }, + { + "plugin": "gemini-consult@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "codeceptjs-e2e-tests@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "csharp-roslyn-lsp@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "dev-workflow@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "dokploy@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "autonomous-loop@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "spec-writer@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "my-time-plugin@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "hardworking@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "monday@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "lorikeet-qa@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "miro@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "gdscript-lsp@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "datadog@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "airtable@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "beast-plan@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "vectorhub-memory@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "pyrefly-lsp@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "jira@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "openspec@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "review-submission@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "microsoft-learn@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "it-triage-system@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "prototype@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "backend-specialist@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "dj-content-creator@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "gitlab-mr-review@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "frontend-lab@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "prototyper@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "universal-dev@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "ccpm@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "terraform-ls@claude-plugins-official", + "unique_installs": 1 + }, { "plugin": "silince-gutnebrg-builder@claude-plugins-official", "unique_installs": 1 }, + { + "plugin": "codex-skills@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "ai-pm-copilot@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "bun-typescript@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "claude-rules-generator@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "test-automation-generator@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "plan-guardian@claude-plugins-official", + "unique_installs": 1 + }, + { + "plugin": "hosts-db@claude-plugins-official", + "unique_installs": 1 + }, { "plugin": "dart-lsp@claude-plugins-official", "unique_installs": 1 }, { - "plugin": "plan-guardian@claude-plugins-official", + "plugin": "amber-electric@claude-plugins-official", "unique_installs": 1 } ] diff --git a/plugins/installed_plugins.json b/plugins/installed_plugins.json index 231c5db..b93b077 100644 --- a/plugins/installed_plugins.json +++ b/plugins/installed_plugins.json @@ -15,10 +15,10 @@ "playground@claude-plugins-official": [ { "scope": "user", - "installPath": "/home/cal/.claude/plugins/cache/claude-plugins-official/playground/8deab8460a9d", - "version": "8deab8460a9d", + "installPath": "/home/cal/.claude/plugins/cache/claude-plugins-official/playground/55b58ec6e564", + "version": "55b58ec6e564", "installedAt": "2026-02-18T19:51:28.422Z", - "lastUpdated": "2026-02-19T00:58:15.373Z", + "lastUpdated": "2026-02-25T18:35:41.447Z", "gitCommitSha": "261ce4fba4f2c314c490302158909a32e5889c88" } ], @@ -31,6 +31,16 @@ "lastUpdated": "2026-02-19T04:07:27.304Z", "gitCommitSha": "8deab8460a9d4df5a01315ef722a5ca6b061c074" } + ], + "frontend-design@claude-plugins-official": [ + { + "scope": "user", + "installPath": "/home/cal/.claude/plugins/cache/claude-plugins-official/frontend-design/55b58ec6e564", + "version": "55b58ec6e564", + "installedAt": "2026-02-22T05:53:45.091Z", + "lastUpdated": "2026-02-25T18:35:41.440Z", + "gitCommitSha": "aa296ec81e8ccb49c9784f167c2c0aa625a86cec" + } ] } } \ No newline at end of file diff --git a/plugins/known_marketplaces.json b/plugins/known_marketplaces.json index 58e8e27..cf367cd 100644 --- a/plugins/known_marketplaces.json +++ b/plugins/known_marketplaces.json @@ -5,7 +5,7 @@ "url": "https://github.com/anthropics/claude-plugins-official.git" }, "installLocation": "/home/cal/.claude/plugins/marketplaces/claude-plugins-official", - "lastUpdated": "2026-02-19T11:00:01.550Z" + "lastUpdated": "2026-02-22T05:54:15.140Z" }, "claude-code-plugins": { "source": { @@ -13,6 +13,6 @@ "repo": "anthropics/claude-code" }, "installLocation": "/home/cal/.claude/plugins/marketplaces/claude-code-plugins", - "lastUpdated": "2026-02-20T04:08:05.428Z" + "lastUpdated": "2026-02-25T23:28:10.907Z" } } \ No newline at end of file diff --git a/plugins/marketplaces/claude-code-plugins b/plugins/marketplaces/claude-code-plugins index 0d996a7..76c0cba 160000 --- a/plugins/marketplaces/claude-code-plugins +++ b/plugins/marketplaces/claude-code-plugins @@ -1 +1 @@ -Subproject commit 0d996a7c346e2f3aca34e3488e49f1da537d7811 +Subproject commit 76c0cbaeb563cd548b11898ba3892812f1ea510f diff --git a/plugins/marketplaces/claude-plugins-official b/plugins/marketplaces/claude-plugins-official index 8deab84..55b58ec 160000 --- a/plugins/marketplaces/claude-plugins-official +++ b/plugins/marketplaces/claude-plugins-official @@ -1 +1 @@ -Subproject commit 8deab8460a9d4df5a01315ef722a5ca6b061c074 +Subproject commit 55b58ec6e5649104f926ba7558b567dc8d33c5ff diff --git a/settings.json b/settings.json index a8744ef..bb9f28f 100644 --- a/settings.json +++ b/settings.json @@ -2,7 +2,8 @@ "$schema": "https://json.schemastore.org/claude-code-settings.json", "env": { "MCP_API_KEY": "${MCP_API_KEY}", - "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1" + "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1", + "ENABLE_TOOL_SEARCH": "true" }, "permissions": { "allow": [ @@ -56,7 +57,39 @@ "mcp__memorygraph__get_memory", "Skill(notediscovery)", "mcp__cognitive-memory__*", - "mcp__n8n-mcp__*" + "mcp__n8n-mcp__*", + "WebFetch(domain:custom-system-builder.gitlab.io)", + "WebFetch(domain:foundryvtt.com)", + "WebFetch(domain:10.10.0.174)", + "WebFetch(domain:docs.openclaw.ai)", + "WebFetch(domain:pve.proxmox.com)", + "WebFetch(domain:git.manticorum.com)", + "WebFetch(domain:developers.cloudflare.com)", + "WebFetch(domain:uptime-kuma-api.readthedocs.io)", + "WebFetch(domain:www.enworld.org)", + "WebFetch(domain:2minutetabletop.com)", + "WebFetch(domain:www.forgotten-adventures.net)", + "WebFetch(domain:www.doodlesanddragons.com)", + "WebFetch(domain:www.drivethrurpg.com)", + "WebFetch(domain:forums.rptools.net)", + "WebFetch(domain:www.fantasygrounds.com)", + "WebFetch(domain:gmkeros.wordpress.com)", + "WebFetch(domain:inkwellideas.com)", + "WebFetch(domain:itch.io)", + "WebFetch(domain:opengameart.org)", + "WebFetch(domain:store.paizo.com)", + "WebFetch(domain:forum.gitea.com)", + "WebFetch(domain:docs.gitea.com)", + "WebFetch(domain:gitea.com)", + "WebFetch(domain:docs.anthropic.com)", + "WebFetch(domain:raw.githubusercontent.com)", + "WebFetch(domain:ra-h.app)", + "WebFetch(domain:ollama.com)", + "mcp__cognitive-memory__memory_store", + "mcp__cognitive-memory__memory_episode", + "mcp__cognitive-memory__memory_recall", + "mcp__cognitive-memory__memory_search", + "mcp__cognitive-memory__memory_get" ], "deny": [ "Bash(diskutil partitionDisk)", @@ -130,7 +163,8 @@ }, "enabledPlugins": { "playground@claude-plugins-official": true, - "claude-code-setup@claude-plugins-official": true + "claude-code-setup@claude-plugins-official": true, + "frontend-design@claude-plugins-official": true }, "skipDangerousModePermissionPrompt": true, "effortLevel": "medium" diff --git a/skills/paper-dynasty/api_client.py b/skills/paper-dynasty/api_client.py index 6f90ad6..c7039d8 100755 --- a/skills/paper-dynasty/api_client.py +++ b/skills/paper-dynasty/api_client.py @@ -33,7 +33,12 @@ class PaperDynastyAPI: api.wipe_team_cards(team_id=464) """ - def __init__(self, environment: str = 'dev', token: Optional[str] = None, verbose: bool = False): + def __init__( + self, + environment: str = "dev", + token: Optional[str] = None, + verbose: bool = False, + ): """ Initialize API client @@ -44,16 +49,16 @@ class PaperDynastyAPI: """ self.env = environment.lower() self.base_url = ( - 'https://pd.manticorum.com/api' - if 'prod' in self.env - else 'https://pddev.manticorum.com/api' + "https://pd.manticorum.com/api" + if "prod" in self.env + else "https://pddev.manticorum.com/api" ) - self.token = token or os.getenv('API_TOKEN') + self.token = token or os.getenv("API_TOKEN") self.verbose = verbose - self.headers = {'Content-Type': 'application/json'} + self.headers = {"Content-Type": "application/json"} if self.token: - self.headers['Authorization'] = f'Bearer {self.token}' + self.headers["Authorization"] = f"Bearer {self.token}" def _require_token(self): """Raise if no API token is set (needed for write operations)""" @@ -68,16 +73,22 @@ class PaperDynastyAPI: if self.verbose: print(f"[API] {message}") - def _build_url(self, endpoint: str, api_ver: int = 2, object_id: Optional[int] = None, params: Optional[List] = None) -> str: + def _build_url( + self, + endpoint: str, + api_ver: int = 2, + object_id: Optional[int] = None, + params: Optional[List] = None, + ) -> str: """Build API URL with parameters""" - url = f'{self.base_url}/v{api_ver}/{endpoint}' + url = f"{self.base_url}/v{api_ver}/{endpoint}" if object_id is not None: - url += f'/{object_id}' + url += f"/{object_id}" if params: - param_strs = [f'{k}={v}' for k, v in params] - url += '?' + '&'.join(param_strs) + param_strs = [f"{k}={v}" for k, v in params] + url += "?" + "&".join(param_strs) return url @@ -85,7 +96,13 @@ class PaperDynastyAPI: # Low-level HTTP methods # ==================== - def get(self, endpoint: str, object_id: Optional[int] = None, params: Optional[List] = None, timeout: int = 10) -> Dict: + def get( + self, + endpoint: str, + object_id: Optional[int] = None, + params: Optional[List] = None, + timeout: int = 10, + ) -> Dict: """GET request to API""" url = self._build_url(endpoint, object_id=object_id, params=params) self._log(f"GET {url}") @@ -93,16 +110,22 @@ class PaperDynastyAPI: response.raise_for_status() return response.json() - def post(self, endpoint: str, payload: Optional[Dict] = None, timeout: int = 10) -> Any: + def post( + self, endpoint: str, payload: Optional[Dict] = None, timeout: int = 10 + ) -> Any: """POST request to API""" self._require_token() url = self._build_url(endpoint) self._log(f"POST {url}") - response = requests.post(url, headers=self.headers, json=payload, timeout=timeout) + response = requests.post( + url, headers=self.headers, json=payload, timeout=timeout + ) response.raise_for_status() return response.json() if response.text else {} - def patch(self, endpoint: str, object_id: int, params: List, timeout: int = 10) -> Dict: + def patch( + self, endpoint: str, object_id: int, params: List, timeout: int = 10 + ) -> Dict: """PATCH request to API""" self._require_token() url = self._build_url(endpoint, object_id=object_id, params=params) @@ -124,7 +147,9 @@ class PaperDynastyAPI: # Team Operations # ==================== - def get_team(self, team_id: Optional[int] = None, abbrev: Optional[str] = None) -> Dict: + def get_team( + self, team_id: Optional[int] = None, abbrev: Optional[str] = None + ) -> Dict: """ Get a team by ID or abbreviation @@ -136,17 +161,19 @@ class PaperDynastyAPI: Team dict """ if team_id: - return self.get('teams', object_id=team_id) + return self.get("teams", object_id=team_id) elif abbrev: - result = self.get('teams', params=[('abbrev', abbrev.upper())]) - teams = result.get('teams', []) + result = self.get("teams", params=[("abbrev", abbrev.upper())]) + teams = result.get("teams", []) if not teams: raise ValueError(f"Team '{abbrev}' not found") return teams[0] else: raise ValueError("Must provide team_id or abbrev") - def list_teams(self, season: Optional[int] = None, event_id: Optional[int] = None) -> List[Dict]: + def list_teams( + self, season: Optional[int] = None, event_id: Optional[int] = None + ) -> List[Dict]: """ List teams @@ -159,12 +186,12 @@ class PaperDynastyAPI: """ params = [] if season: - params.append(('season', season)) + params.append(("season", season)) if event_id: - params.append(('event', event_id)) + params.append(("event", event_id)) - result = self.get('teams', params=params if params else None) - return result.get('teams', []) + result = self.get("teams", params=params if params else None) + return result.get("teams", []) # ==================== # Card Operations @@ -180,9 +207,11 @@ class PaperDynastyAPI: Returns: API response """ - return self.post(f'cards/wipe-team/{team_id}') + return self.post(f"cards/wipe-team/{team_id}") - def list_cards(self, team_id: Optional[int] = None, player_id: Optional[int] = None) -> List[Dict]: + def list_cards( + self, team_id: Optional[int] = None, player_id: Optional[int] = None + ) -> List[Dict]: """ List cards. At least one filter is required to avoid massive unfiltered queries. @@ -194,16 +223,18 @@ class PaperDynastyAPI: List of card dicts """ if not team_id and not player_id: - raise ValueError("list_cards requires at least one filter (team_id or player_id)") + raise ValueError( + "list_cards requires at least one filter (team_id or player_id)" + ) params = [] if team_id: - params.append(('team_id', team_id)) + params.append(("team_id", team_id)) if player_id: - params.append(('player_id', player_id)) + params.append(("player_id", player_id)) - result = self.get('cards', params=params if params else None) - return result.get('cards', []) + result = self.get("cards", params=params if params else None) + return result.get("cards", []) # ==================== # Pack Operations @@ -215,7 +246,7 @@ class PaperDynastyAPI: opened: Optional[bool] = None, new_to_old: bool = False, limit: Optional[int] = None, - timeout: int = 10 + timeout: int = 10, ) -> List[Dict]: """ List packs @@ -241,20 +272,22 @@ class PaperDynastyAPI: packs = api.list_packs(opened=True, limit=2000, timeout=30) """ if team_id is None and opened is None: - raise ValueError("list_packs requires at least one filter (team_id or opened)") + raise ValueError( + "list_packs requires at least one filter (team_id or opened)" + ) params = [] if team_id: - params.append(('team_id', team_id)) + params.append(("team_id", team_id)) if opened is not None: - params.append(('opened', 'true' if opened else 'false')) + params.append(("opened", "true" if opened else "false")) if new_to_old: - params.append(('new_to_old', 'true')) + params.append(("new_to_old", "true")) if limit: - params.append(('limit', str(limit))) + params.append(("limit", str(limit))) - result = self.get('packs', params=params if params else None, timeout=timeout) - return result.get('packs', []) + result = self.get("packs", params=params if params else None, timeout=timeout) + return result.get("packs", []) def delete_pack(self, pack_id: int) -> str: """ @@ -266,10 +299,15 @@ class PaperDynastyAPI: Returns: Success message """ - return self.delete('packs', object_id=pack_id) + return self.delete("packs", object_id=pack_id) - def update_pack(self, pack_id: int, pack_cardset_id: Optional[int] = None, - pack_team_id: Optional[int] = None, pack_type_id: Optional[int] = None) -> Dict: + def update_pack( + self, + pack_id: int, + pack_cardset_id: Optional[int] = None, + pack_team_id: Optional[int] = None, + pack_type_id: Optional[int] = None, + ) -> Dict: """ Update pack properties (PATCH) @@ -288,13 +326,13 @@ class PaperDynastyAPI: """ params = [] if pack_cardset_id is not None: - params.append(('pack_cardset_id', pack_cardset_id)) + params.append(("pack_cardset_id", pack_cardset_id)) if pack_team_id is not None: - params.append(('pack_team_id', pack_team_id)) + params.append(("pack_team_id", pack_team_id)) if pack_type_id is not None: - params.append(('pack_type_id', pack_type_id)) + params.append(("pack_type_id", pack_type_id)) - return self.patch('packs', object_id=pack_id, params=params) + return self.patch("packs", object_id=pack_id, params=params) def create_packs(self, packs: List[Dict]) -> Any: """ @@ -313,8 +351,8 @@ class PaperDynastyAPI: for _ in range(5) ]) """ - payload = {'packs': packs} - return self.post('packs', payload=payload) + payload = {"packs": packs} + return self.post("packs", payload=payload) def get_packs_opened_today(self, limit: int = 2000, timeout: int = 30) -> Dict: """ @@ -338,57 +376,67 @@ class PaperDynastyAPI: from collections import defaultdict # Get recent opened packs - packs = self.list_packs(opened=True, new_to_old=True, limit=limit, timeout=timeout) + packs = self.list_packs( + opened=True, new_to_old=True, limit=limit, timeout=timeout + ) # Today's date (UTC) today = datetime.now(timezone.utc).date() # Count packs by team - teams_data = defaultdict(lambda: {'count': 0, 'abbrev': '', 'lname': '', 'first': None, 'last': None}) + teams_data = defaultdict( + lambda: {"count": 0, "abbrev": "", "lname": "", "first": None, "last": None} + ) total = 0 for pack in packs: - if pack.get('open_time'): + if pack.get("open_time"): try: - open_dt = datetime.fromtimestamp(pack['open_time'] / 1000, tz=timezone.utc) + open_dt = datetime.fromtimestamp( + pack["open_time"] / 1000, tz=timezone.utc + ) if open_dt.date() == today: total += 1 - team_id = pack['team']['id'] - teams_data[team_id]['abbrev'] = pack['team']['abbrev'] - teams_data[team_id]['lname'] = pack['team']['lname'] - teams_data[team_id]['count'] += 1 + team_id = pack["team"]["id"] + teams_data[team_id]["abbrev"] = pack["team"]["abbrev"] + teams_data[team_id]["lname"] = pack["team"]["lname"] + teams_data[team_id]["count"] += 1 - if teams_data[team_id]['first'] is None or open_dt < teams_data[team_id]['first']: - teams_data[team_id]['first'] = open_dt - if teams_data[team_id]['last'] is None or open_dt > teams_data[team_id]['last']: - teams_data[team_id]['last'] = open_dt + if ( + teams_data[team_id]["first"] is None + or open_dt < teams_data[team_id]["first"] + ): + teams_data[team_id]["first"] = open_dt + if ( + teams_data[team_id]["last"] is None + or open_dt > teams_data[team_id]["last"] + ): + teams_data[team_id]["last"] = open_dt except Exception: pass # Format results teams_list = [] for team_id, data in teams_data.items(): - teams_list.append({ - 'team_id': team_id, - 'abbrev': data['abbrev'], - 'name': data['lname'], - 'packs': data['count'], - 'first_pack': data['first'].isoformat() if data['first'] else None, - 'last_pack': data['last'].isoformat() if data['last'] else None - }) + teams_list.append( + { + "team_id": team_id, + "abbrev": data["abbrev"], + "name": data["lname"], + "packs": data["count"], + "first_pack": data["first"].isoformat() if data["first"] else None, + "last_pack": data["last"].isoformat() if data["last"] else None, + } + ) # Sort by pack count - teams_list.sort(key=lambda x: x['packs'], reverse=True) + teams_list.sort(key=lambda x: x["packs"], reverse=True) - result = { - 'total': total, - 'teams': teams_list, - 'date': today.isoformat() - } + result = {"total": total, "teams": teams_list, "date": today.isoformat()} if len(packs) == limit: - result['note'] = f'Hit limit of {limit} packs - actual count may be higher' + result["note"] = f"Hit limit of {limit} packs - actual count may be higher" return result @@ -397,7 +445,8 @@ class PaperDynastyAPI: num_packs: int = 5, exclude_team_abbrev: Optional[List[str]] = None, pack_type_id: int = 1, - season: Optional[int] = None + season: Optional[int] = None, + cardset_id: Optional[int] = None, ) -> Dict: """ Distribute packs to all human-controlled teams @@ -407,6 +456,7 @@ class PaperDynastyAPI: exclude_team_abbrev: List of team abbreviations to exclude (default: None) pack_type_id: Pack type ID (default: 1 = Standard packs) season: Season to distribute for (default: current season) + cardset_id: Cardset ID for pack types that require it (e.g., Promo Choice = type 9) Returns: Dict with keys: @@ -429,8 +479,8 @@ class PaperDynastyAPI: # Get current season if not specified if season is None: - current = self.get('current') - season = current['season'] + current = self.get("current") + season = current["season"] self._log(f"Distributing {num_packs} packs to season {season} teams") @@ -440,9 +490,9 @@ class PaperDynastyAPI: # Filter for human-controlled teams only qualifying_teams = [] for team in all_teams: - if not team['is_ai'] and 'gauntlet' not in team['abbrev'].lower(): + if not team["is_ai"] and "gauntlet" not in team["abbrev"].lower(): # Check if team is in exclusion list - if team['abbrev'].upper() in exclude_team_abbrev: + if team["abbrev"].upper() in exclude_team_abbrev: self._log(f"Excluding team {team['abbrev']}: {team['sname']}") continue qualifying_teams.append(team) @@ -457,27 +507,34 @@ class PaperDynastyAPI: self._log(f"Giving {num_packs} packs to {team['abbrev']} ({team['sname']})") # Create pack payload - packs = [{ - 'team_id': team['id'], - 'pack_type_id': pack_type_id, - 'pack_cardset_id': None - } for _ in range(num_packs)] + packs = [ + { + "team_id": team["id"], + "pack_type_id": pack_type_id, + "pack_cardset_id": cardset_id, + } + for _ in range(num_packs) + ] try: self.create_packs(packs) total_packs += num_packs - self._log(f" ✓ Successfully gave {num_packs} packs to {team['abbrev']}") + self._log( + f" ✓ Successfully gave {num_packs} packs to {team['abbrev']}" + ) except Exception as e: self._log(f" ✗ Failed to give packs to {team['abbrev']}: {e}") raise result = { - 'total_packs': total_packs, - 'teams_count': len(qualifying_teams), - 'teams': qualifying_teams + "total_packs": total_packs, + "teams_count": len(qualifying_teams), + "teams": qualifying_teams, } - self._log(f"Distribution complete: {total_packs} packs to {len(qualifying_teams)} teams") + self._log( + f"Distribution complete: {total_packs} packs to {len(qualifying_teams)} teams" + ) return result @@ -485,7 +542,12 @@ class PaperDynastyAPI: # Gauntlet Operations # ==================== - def list_gauntlet_runs(self, event_id: Optional[int] = None, team_id: Optional[int] = None, active_only: bool = False) -> List[Dict]: + def list_gauntlet_runs( + self, + event_id: Optional[int] = None, + team_id: Optional[int] = None, + active_only: bool = False, + ) -> List[Dict]: """ List gauntlet runs @@ -499,14 +561,14 @@ class PaperDynastyAPI: """ params = [] if event_id: - params.append(('gauntlet_id', event_id)) + params.append(("gauntlet_id", event_id)) if team_id: - params.append(('team_id', team_id)) + params.append(("team_id", team_id)) if active_only: - params.append(('is_active', 'true')) + params.append(("is_active", "true")) - result = self.get('gauntletruns', params=params if params else None) - return result.get('runs', []) + result = self.get("gauntletruns", params=params if params else None) + return result.get("runs", []) def end_gauntlet_run(self, run_id: int) -> Dict: """ @@ -518,7 +580,7 @@ class PaperDynastyAPI: Returns: Updated run dict """ - return self.patch('gauntletruns', object_id=run_id, params=[('ended', 'true')]) + return self.patch("gauntletruns", object_id=run_id, params=[("ended", "true")]) # ==================== # Player Operations @@ -534,9 +596,14 @@ class PaperDynastyAPI: Returns: Player dict """ - return self.get('players', object_id=player_id) + return self.get("players", object_id=player_id) - def list_players(self, cardset_id: Optional[int] = None, rarity: Optional[str] = None, timeout: int = 30) -> List[Dict]: + def list_players( + self, + cardset_id: Optional[int] = None, + rarity: Optional[str] = None, + timeout: int = 30, + ) -> List[Dict]: """ List players. At least one filter is required to avoid massive unfiltered queries. @@ -549,22 +616,26 @@ class PaperDynastyAPI: List of player dicts """ if not cardset_id and not rarity: - raise ValueError("list_players requires at least one filter (cardset_id or rarity)") + raise ValueError( + "list_players requires at least one filter (cardset_id or rarity)" + ) params = [] if cardset_id: - params.append(('cardset', cardset_id)) + params.append(("cardset", cardset_id)) if rarity: - params.append(('rarity', rarity)) + params.append(("rarity", rarity)) - result = self.get('players', params=params, timeout=timeout) - return result.get('players', []) + result = self.get("players", params=params, timeout=timeout) + return result.get("players", []) # ==================== # Result/Stats Operations # ==================== - def list_results(self, season: Optional[int] = None, team_id: Optional[int] = None) -> List[Dict]: + def list_results( + self, season: Optional[int] = None, team_id: Optional[int] = None + ) -> List[Dict]: """ List game results. At least one filter is required to avoid massive unfiltered queries. @@ -576,22 +647,26 @@ class PaperDynastyAPI: List of result dicts """ if not season and not team_id: - raise ValueError("list_results requires at least one filter (season or team_id)") + raise ValueError( + "list_results requires at least one filter (season or team_id)" + ) params = [] if season: - params.append(('season', season)) + params.append(("season", season)) if team_id: - params.append(('team_id', team_id)) + params.append(("team_id", team_id)) - result = self.get('results', params=params if params else None) - return result.get('results', []) + result = self.get("results", params=params if params else None) + return result.get("results", []) # ==================== # Helper Methods # ==================== - def find_gauntlet_teams(self, event_id: Optional[int] = None, active_only: bool = False) -> List[Dict]: + def find_gauntlet_teams( + self, event_id: Optional[int] = None, active_only: bool = False + ) -> List[Dict]: """ Find gauntlet teams (teams with 'Gauntlet' in abbrev) @@ -607,22 +682,22 @@ class PaperDynastyAPI: runs = self.list_gauntlet_runs(event_id=event_id, active_only=True) teams_with_runs = [] for run in runs: - team = run['team'] - team['active_run'] = run + team = run["team"] + team["active_run"] = run teams_with_runs.append(team) return teams_with_runs else: # Get all teams with 'Gauntlet' in name all_teams = self.list_teams() - gauntlet_teams = [t for t in all_teams if 'Gauntlet' in t.get('abbrev', '')] + gauntlet_teams = [t for t in all_teams if "Gauntlet" in t.get("abbrev", "")] # Optionally add run info if event_id: runs = self.list_gauntlet_runs(event_id=event_id) - run_by_team = {r['team']['id']: r for r in runs} + run_by_team = {r["team"]["id"]: r for r in runs} for team in gauntlet_teams: - if team['id'] in run_by_team: - team['run'] = run_by_team[team['id']] + if team["id"] in run_by_team: + team["run"] = run_by_team[team["id"]] return gauntlet_teams @@ -631,9 +706,11 @@ def main(): """Example usage""" import argparse - parser = argparse.ArgumentParser(description='Paper Dynasty API Client') - parser.add_argument('--env', choices=['prod', 'dev'], default='dev', help='Environment') - parser.add_argument('--verbose', '-v', action='store_true', help='Verbose output') + parser = argparse.ArgumentParser(description="Paper Dynasty API Client") + parser.add_argument( + "--env", choices=["prod", "dev"], default="dev", help="Environment" + ) + parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output") args = parser.parse_args() try: @@ -650,5 +727,5 @@ def main(): sys.exit(1) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/skills/paper-dynasty/cli.py b/skills/paper-dynasty/cli.py index fda09b1..9e6f74c 100755 --- a/skills/paper-dynasty/cli.py +++ b/skills/paper-dynasty/cli.py @@ -56,6 +56,7 @@ console = Console() class State: """Global state for API client and settings""" + api: Optional[PaperDynastyAPI] = None json_output: bool = False @@ -67,14 +68,19 @@ state = State() # Output Helpers # ============================================================================ + def output_json(data): """Output data as formatted JSON""" console.print_json(json.dumps(data, indent=2, default=str)) -def output_table(title: str, columns: List[str], rows: List[List], show_lines: bool = False): +def output_table( + title: str, columns: List[str], rows: List[List], show_lines: bool = False +): """Output data as a rich table""" - table = Table(title=title, show_header=True, header_style="bold cyan", show_lines=show_lines) + table = Table( + title=title, show_header=True, header_style="bold cyan", show_lines=show_lines + ) for col in columns: table.add_column(col) for row in rows: @@ -90,7 +96,9 @@ def handle_error(e: Exception, context: str = ""): elif "404" in error_str: console.print(f"[red]Error:[/red] Not found. {context}") elif "Connection" in error_str or "ConnectionError" in error_str: - console.print("[red]Error:[/red] Cannot connect to API. Check network and --env setting.") + console.print( + "[red]Error:[/red] Cannot connect to API. Check network and --env setting." + ) else: console.print(f"[red]Error:[/red] {e}") raise typer.Exit(1) @@ -100,11 +108,16 @@ def handle_error(e: Exception, context: str = ""): # Main Callback (Global Options) # ============================================================================ + @app.callback() def main( - env: Annotated[str, typer.Option("--env", help="Environment: prod or dev")] = "prod", + env: Annotated[ + str, typer.Option("--env", help="Environment: prod or dev") + ] = "prod", json_output: Annotated[bool, typer.Option("--json", help="Output as JSON")] = False, - verbose: Annotated[bool, typer.Option("--verbose", "-v", help="Verbose output")] = False, + verbose: Annotated[ + bool, typer.Option("--verbose", "-v", help="Verbose output") + ] = False, ): """Paper Dynasty Baseball Card Game CLI""" state.api = PaperDynastyAPI(environment=env, verbose=verbose) @@ -115,6 +128,7 @@ def main( # Status & Health Commands # ============================================================================ + @app.command() def status(): """Show packs opened today summary""" @@ -125,18 +139,20 @@ def status(): output_json(result) return - console.print(f"\n[bold cyan]Packs Opened Today ({result['date']})[/bold cyan]\n") + console.print( + f"\n[bold cyan]Packs Opened Today ({result['date']})[/bold cyan]\n" + ) console.print(f"[bold]Total:[/bold] {result['total']} packs\n") - if result['teams']: + if result["teams"]: rows = [] - for t in result['teams']: - rows.append([t['abbrev'], t['name'], t['packs']]) + for t in result["teams"]: + rows.append([t["abbrev"], t["name"], t["packs"]]) output_table("By Team", ["Abbrev", "Team", "Packs"], rows) else: console.print("[dim]No packs opened today[/dim]") - if result.get('note'): + if result.get("note"): console.print(f"\n[yellow]Note:[/yellow] {result['note']}") except Exception as e: @@ -159,9 +175,12 @@ def health(): # Team Commands # ============================================================================ + @team_app.command("list") def team_list( - season: Annotated[Optional[int], typer.Option("--season", "-s", help="Filter by season")] = None, + season: Annotated[ + Optional[int], typer.Option("--season", "-s", help="Filter by season") + ] = None, ): """List all teams""" try: @@ -176,23 +195,27 @@ def team_list( return # Filter out gauntlet teams for cleaner display - regular_teams = [t for t in teams if 'Gauntlet' not in t.get('abbrev', '')] + regular_teams = [t for t in teams if "Gauntlet" not in t.get("abbrev", "")] rows = [] for t in regular_teams: - rows.append([ - t['abbrev'], - t.get('sname', ''), - t.get('season', ''), - t.get('wallet', 0), - t.get('ranking', 'N/A'), - 'AI' if t.get('is_ai') else 'Human' - ]) + rows.append( + [ + t["abbrev"], + t.get("sname", ""), + t.get("season", ""), + t.get("wallet", 0), + t.get("ranking", "N/A"), + "AI" if t.get("is_ai") else "Human", + ] + ) title = "Teams" if season: title += f" - Season {season}" - output_table(title, ["Abbrev", "Name", "Season", "Wallet", "Rank", "Type"], rows) + output_table( + title, ["Abbrev", "Name", "Season", "Wallet", "Rank", "Type"], rows + ) except Exception as e: handle_error(e) @@ -239,7 +262,7 @@ def team_cards( """List team's cards""" try: team = state.api.get_team(abbrev=abbrev.upper()) - cards = state.api.list_cards(team_id=team['id']) + cards = state.api.list_cards(team_id=team["id"]) if state.json_output: output_json(cards) @@ -251,22 +274,26 @@ def team_cards( rows = [] for c in cards[:limit]: - player = c.get('player', {}) - rows.append([ - c['id'], - player.get('p_name', 'Unknown'), - player.get('rarity', ''), - c.get('value', 0) - ]) + player = c.get("player", {}) + rows.append( + [ + c["id"], + player.get("p_name", "Unknown"), + player.get("rarity", ""), + c.get("value", 0), + ] + ) output_table( f"Cards for {team.get('lname', abbrev)} ({len(cards)} total)", ["Card ID", "Player", "Rarity", "Value"], - rows + rows, ) if len(cards) > limit: - console.print(f"\n[dim]Showing {limit} of {len(cards)} cards. Use --limit to see more.[/dim]") + console.print( + f"\n[dim]Showing {limit} of {len(cards)} cards. Use --limit to see more.[/dim]" + ) except ValueError as e: console.print(f"[red]Error:[/red] {e}") @@ -279,10 +306,16 @@ def team_cards( # Pack Commands # ============================================================================ + @pack_app.command("list") def pack_list( - team: Annotated[Optional[str], typer.Option("--team", "-t", help="Filter by team abbrev")] = None, - opened: Annotated[Optional[bool], typer.Option("--opened/--unopened", help="Filter by opened status")] = None, + team: Annotated[ + Optional[str], typer.Option("--team", "-t", help="Filter by team abbrev") + ] = None, + opened: Annotated[ + Optional[bool], + typer.Option("--opened/--unopened", help="Filter by opened status"), + ] = None, limit: Annotated[int, typer.Option("--limit", "-n", help="Max packs to show")] = 50, ): """List packs""" @@ -291,10 +324,12 @@ def pack_list( team_name = None if team: team_obj = state.api.get_team(abbrev=team.upper()) - team_id = team_obj['id'] - team_name = team_obj.get('sname', team) + team_id = team_obj["id"] + team_name = team_obj.get("sname", team) - packs = state.api.list_packs(team_id=team_id, opened=opened, new_to_old=True, limit=limit) + packs = state.api.list_packs( + team_id=team_id, opened=opened, new_to_old=True, limit=limit + ) if state.json_output: output_json(packs) @@ -306,15 +341,17 @@ def pack_list( rows = [] for p in packs: - pack_team = p.get('team', {}) - pack_type = p.get('pack_type', {}) - is_opened = "Yes" if p.get('open_time') else "No" - rows.append([ - p['id'], - pack_team.get('abbrev', 'N/A'), - pack_type.get('name', 'Unknown'), - is_opened - ]) + pack_team = p.get("team", {}) + pack_type = p.get("pack_type", {}) + is_opened = "Yes" if p.get("open_time") else "No" + rows.append( + [ + p["id"], + pack_team.get("abbrev", "N/A"), + pack_type.get("name", "Unknown"), + is_opened, + ] + ) title = "Packs" if team_name: @@ -342,30 +379,54 @@ def pack_today(): @pack_app.command("distribute") def pack_distribute( - num: Annotated[int, typer.Option("--num", "-n", help="Number of packs per team")] = 5, - exclude: Annotated[Optional[List[str]], typer.Option("--exclude", "-x", help="Team abbrevs to exclude")] = None, - pack_type: Annotated[int, typer.Option("--pack-type", help="1=Standard, 2=Starter, 3=Premium, 4=Check-In, 5=MVP, 6=All Star, 7=Mario, 8=Team Choice, 9=Promo Choice")] = 1, - dry_run: Annotated[bool, typer.Option("--dry-run", help="Show what would be done")] = False, + num: Annotated[ + int, typer.Option("--num", "-n", help="Number of packs per team") + ] = 5, + exclude: Annotated[ + Optional[List[str]], + typer.Option("--exclude", "-x", help="Team abbrevs to exclude"), + ] = None, + pack_type: Annotated[ + int, + typer.Option( + "--pack-type", + help="1=Standard, 2=Starter, 3=Premium, 4=Check-In, 5=MVP, 6=All Star, 7=Mario, 8=Team Choice, 9=Promo Choice", + ), + ] = 1, + cardset: Annotated[ + Optional[int], + typer.Option( + "--cardset", "-c", help="Cardset ID (required for Promo Choice packs)" + ), + ] = None, + dry_run: Annotated[ + bool, typer.Option("--dry-run", help="Show what would be done") + ] = False, ): """Distribute packs to all human teams""" try: if dry_run: # Get qualifying teams to show preview - current = state.api.get('current') - season = current['season'] + current = state.api.get("current") + season = current["season"] all_teams = state.api.list_teams(season=season) exclude_upper = [e.upper() for e in (exclude or [])] qualifying = [ - t for t in all_teams - if not t['is_ai'] - and 'gauntlet' not in t['abbrev'].lower() - and t['abbrev'].upper() not in exclude_upper + t + for t in all_teams + if not t["is_ai"] + and "gauntlet" not in t["abbrev"].lower() + and t["abbrev"].upper() not in exclude_upper ] - console.print(f"\n[bold cyan]Pack Distribution Preview (DRY RUN)[/bold cyan]\n") + console.print( + f"\n[bold cyan]Pack Distribution Preview (DRY RUN)[/bold cyan]\n" + ) console.print(f"[bold]Packs per team:[/bold] {num}") console.print(f"[bold]Pack type:[/bold] {pack_type}") + if cardset is not None: + console.print(f"[bold]Cardset ID:[/bold] {cardset}") console.print(f"[bold]Teams:[/bold] {len(qualifying)}") console.print(f"[bold]Total packs:[/bold] {num * len(qualifying)}") @@ -381,7 +442,8 @@ def pack_distribute( result = state.api.distribute_packs( num_packs=num, exclude_team_abbrev=exclude, - pack_type_id=pack_type + pack_type_id=pack_type, + cardset_id=cardset, ) if state.json_output: @@ -403,10 +465,15 @@ def pack_distribute( # Gauntlet Commands # ============================================================================ + @gauntlet_app.command("list") def gauntlet_list( - event_id: Annotated[Optional[int], typer.Option("--event-id", "-e", help="Filter by event ID")] = None, - active: Annotated[bool, typer.Option("--active", "-a", help="Only active runs")] = False, + event_id: Annotated[ + Optional[int], typer.Option("--event-id", "-e", help="Filter by event ID") + ] = None, + active: Annotated[ + bool, typer.Option("--active", "-a", help="Only active runs") + ] = False, ): """List gauntlet runs""" try: @@ -422,17 +489,19 @@ def gauntlet_list( rows = [] for r in runs: - team = r.get('team', {}) - is_active = "Active" if r.get('ended') is None else "Ended" - gauntlet = r.get('gauntlet', {}) - rows.append([ - r['id'], - team.get('abbrev', 'N/A'), - r.get('wins', 0), - r.get('losses', 0), - gauntlet.get('id', 'N/A'), - is_active - ]) + team = r.get("team", {}) + is_active = "Active" if r.get("ended") is None else "Ended" + gauntlet = r.get("gauntlet", {}) + rows.append( + [ + r["id"], + team.get("abbrev", "N/A"), + r.get("wins", 0), + r.get("losses", 0), + gauntlet.get("id", "N/A"), + is_active, + ] + ) title = "Gauntlet Runs" if event_id: @@ -448,8 +517,12 @@ def gauntlet_list( @gauntlet_app.command("teams") def gauntlet_teams( - event_id: Annotated[Optional[int], typer.Option("--event-id", "-e", help="Filter by event ID")] = None, - active: Annotated[bool, typer.Option("--active", "-a", help="Only teams with active runs")] = False, + event_id: Annotated[ + Optional[int], typer.Option("--event-id", "-e", help="Filter by event ID") + ] = None, + active: Annotated[ + bool, typer.Option("--active", "-a", help="Only teams with active runs") + ] = False, ): """List gauntlet teams""" try: @@ -465,16 +538,10 @@ def gauntlet_teams( rows = [] for t in teams: - run = t.get('active_run') or t.get('run', {}) - wins = run.get('wins', '-') if run else '-' - losses = run.get('losses', '-') if run else '-' - rows.append([ - t['id'], - t['abbrev'], - t.get('sname', ''), - wins, - losses - ]) + run = t.get("active_run") or t.get("run", {}) + wins = run.get("wins", "-") if run else "-" + losses = run.get("losses", "-") if run else "-" + rows.append([t["id"], t["abbrev"], t.get("sname", ""), wins, losses]) title = "Gauntlet Teams" if active: @@ -488,29 +555,37 @@ def gauntlet_teams( @gauntlet_app.command("cleanup") def gauntlet_cleanup( - team_abbrev: Annotated[str, typer.Argument(help="Team abbreviation (e.g., Gauntlet-SKB)")], - event_id: Annotated[int, typer.Option("--event-id", "-e", help="Event ID (required)")], + team_abbrev: Annotated[ + str, typer.Argument(help="Team abbreviation (e.g., Gauntlet-SKB)") + ], + event_id: Annotated[ + int, typer.Option("--event-id", "-e", help="Event ID (required)") + ], yes: Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation")] = False, ): """Clean up a gauntlet team (wipe cards, delete packs, end run)""" try: # Find the team team = state.api.get_team(abbrev=team_abbrev) - team_id = team['id'] + team_id = team["id"] # Get cards and packs count cards = state.api.list_cards(team_id=team_id) packs = state.api.list_packs(team_id=team_id, opened=False) # Find active run - runs = state.api.list_gauntlet_runs(event_id=event_id, team_id=team_id, active_only=True) + runs = state.api.list_gauntlet_runs( + event_id=event_id, team_id=team_id, active_only=True + ) active_run = runs[0] if runs else None console.print(f"\n[bold cyan]Gauntlet Cleanup: {team_abbrev}[/bold cyan]\n") console.print(f"[bold]Team ID:[/bold] {team_id}") console.print(f"[bold]Cards to wipe:[/bold] {len(cards)}") console.print(f"[bold]Packs to delete:[/bold] {len(packs)}") - console.print(f"[bold]Active run:[/bold] {'Yes (ID: ' + str(active_run['id']) + ')' if active_run else 'No'}") + console.print( + f"[bold]Active run:[/bold] {'Yes (ID: ' + str(active_run['id']) + ')' if active_run else 'No'}" + ) if not yes: console.print("\n[yellow]This is a destructive operation![/yellow]") @@ -527,13 +602,13 @@ def gauntlet_cleanup( # 2. Delete packs for pack in packs: - state.api.delete_pack(pack['id']) + state.api.delete_pack(pack["id"]) if packs: results.append(f"Deleted {len(packs)} packs") # 3. End gauntlet run if active_run: - state.api.end_gauntlet_run(active_run['id']) + state.api.end_gauntlet_run(active_run["id"]) results.append(f"Ended run {active_run['id']}") console.print(f"\n[green]Cleanup complete![/green]") @@ -551,6 +626,7 @@ def gauntlet_cleanup( # Player Commands # ============================================================================ + @player_app.command("get") def player_get( player_id: Annotated[int, typer.Argument(help="Player ID")], @@ -566,13 +642,13 @@ def player_get( # Get positions positions = [] for i in range(1, 9): - pos = player.get(f'pos_{i}') + pos = player.get(f"pos_{i}") if pos: positions.append(pos) - cardset = player.get('cardset', {}) - rarity = player.get('rarity', {}) - rarity_name = rarity.get('name', 'N/A') if isinstance(rarity, dict) else rarity + cardset = player.get("cardset", {}) + rarity = player.get("rarity", {}) + rarity_name = rarity.get("name", "N/A") if isinstance(rarity, dict) else rarity panel = Panel( f"[bold]ID:[/bold] {player['player_id']}\n" @@ -593,9 +669,15 @@ def player_get( @player_app.command("list") def player_list( - rarity: Annotated[Optional[str], typer.Option("--rarity", "-r", help="Filter by rarity")] = None, - cardset: Annotated[Optional[int], typer.Option("--cardset", "-c", help="Filter by cardset ID")] = None, - limit: Annotated[int, typer.Option("--limit", "-n", help="Max players to show")] = 50, + rarity: Annotated[ + Optional[str], typer.Option("--rarity", "-r", help="Filter by rarity") + ] = None, + cardset: Annotated[ + Optional[int], typer.Option("--cardset", "-c", help="Filter by cardset ID") + ] = None, + limit: Annotated[ + int, typer.Option("--limit", "-n", help="Max players to show") + ] = 50, ): """List players""" try: @@ -611,16 +693,18 @@ def player_list( rows = [] for p in players[:limit]: - cs = p.get('cardset', {}) - rarity = p.get('rarity', {}) - rarity_name = rarity.get('name', '') if isinstance(rarity, dict) else rarity - rows.append([ - p['player_id'], - p.get('p_name', 'Unknown'), - rarity_name, - p.get('cost', 0), - cs.get('name', 'N/A') - ]) + cs = p.get("cardset", {}) + rarity = p.get("rarity", {}) + rarity_name = rarity.get("name", "") if isinstance(rarity, dict) else rarity + rows.append( + [ + p["player_id"], + p.get("p_name", "Unknown"), + rarity_name, + p.get("cost", 0), + cs.get("name", "N/A"), + ] + ) title = "Players" if rarity: @@ -631,7 +715,9 @@ def player_list( output_table(title, ["ID", "Name", "Rarity", "Cost", "Cardset"], rows) if len(players) > limit: - console.print(f"\n[dim]Showing {limit} of {len(players)} players. Use --limit to see more.[/dim]") + console.print( + f"\n[dim]Showing {limit} of {len(players)} players. Use --limit to see more.[/dim]" + ) except Exception as e: handle_error(e) diff --git a/skills/paper-dynasty/scripts/distribute_packs.py b/skills/paper-dynasty/scripts/distribute_packs.py index 33a76bb..cba217c 100755 --- a/skills/paper-dynasty/scripts/distribute_packs.py +++ b/skills/paper-dynasty/scripts/distribute_packs.py @@ -7,6 +7,7 @@ Works with both production and development environments. This script uses the Paper Dynasty API client for all operations. """ + import argparse import logging import os @@ -18,23 +19,31 @@ sys.path.insert(0, str(Path(__file__).parent.parent)) from api_client import PaperDynastyAPI # Set up logging -logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') -logger = logging.getLogger('pack_distribution') +logging.basicConfig( + level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" +) +logger = logging.getLogger("pack_distribution") -def distribute_packs(num_packs: int = 5, exclude_team_abbrev: list[str] = None, pack_type_id: int = 1): +def distribute_packs( + num_packs: int = 5, + exclude_team_abbrev: list[str] = None, + pack_type_id: int = 1, + cardset_id: int = None, +): """Distribute packs to all human-controlled teams using Paper Dynasty API client Args: num_packs: Number of packs to give to each team (default: 5) exclude_team_abbrev: List of team abbreviations to exclude (default: None) pack_type_id: Pack type ID (default: 1 = Standard packs) + cardset_id: Cardset ID for pack types that require it (e.g., Promo Choice = type 9) """ if exclude_team_abbrev is None: exclude_team_abbrev = [] # Get environment - database_env = os.getenv('DATABASE', 'dev').lower() + database_env = os.getenv("DATABASE", "dev").lower() try: # Initialize API client @@ -44,11 +53,14 @@ def distribute_packs(num_packs: int = 5, exclude_team_abbrev: list[str] = None, result = api.distribute_packs( num_packs=num_packs, exclude_team_abbrev=exclude_team_abbrev, - pack_type_id=pack_type_id + pack_type_id=pack_type_id, + cardset_id=cardset_id, ) # Log final summary - logger.info(f"\n🎉 All done! Distributed {result['total_packs']} packs to {result['teams_count']} teams") + logger.info( + f"\n🎉 All done! Distributed {result['total_packs']} packs to {result['teams_count']} teams" + ) except ValueError as e: logger.error(f"Configuration error: {e}") @@ -58,9 +70,9 @@ def distribute_packs(num_packs: int = 5, exclude_team_abbrev: list[str] = None, sys.exit(1) -if __name__ == '__main__': +if __name__ == "__main__": parser = argparse.ArgumentParser( - description='Distribute packs to all human-controlled teams', + description="Distribute packs to all human-controlled teams", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: @@ -85,25 +97,31 @@ Examples: Environment Variables: API_TOKEN - Required: API authentication token DATABASE - Optional: 'dev' (default) or 'prod' - """ + """, ) parser.add_argument( - '--num-packs', + "--num-packs", type=int, default=5, - help='Number of packs to give to each team (default: 5)' + help="Number of packs to give to each team (default: 5)", ) parser.add_argument( - '--exclude-team-abbrev', - nargs='*', + "--exclude-team-abbrev", + nargs="*", default=[], - help='Team abbreviations to exclude (space-separated, e.g., NYY BOS LAD)' + help="Team abbreviations to exclude (space-separated, e.g., NYY BOS LAD)", ) parser.add_argument( - '--pack-type-id', + "--pack-type-id", type=int, default=1, - help='Pack type ID (default: 1 = Standard packs)' + help="Pack type ID (default: 1 = Standard packs)", + ) + parser.add_argument( + "--cardset-id", + type=int, + default=None, + help="Cardset ID for pack types that require it (e.g., Promo Choice = type 9)", ) args = parser.parse_args() @@ -111,5 +129,6 @@ Environment Variables: distribute_packs( num_packs=args.num_packs, exclude_team_abbrev=args.exclude_team_abbrev, - pack_type_id=args.pack_type_id + pack_type_id=args.pack_type_id, + cardset_id=args.cardset_id, ) diff --git a/skills/resume-tailoring b/skills/resume-tailoring new file mode 160000 index 0000000..4773410 --- /dev/null +++ b/skills/resume-tailoring @@ -0,0 +1 @@ +Subproject commit 477341020c40fdb547ac2e4b50fa8e22cd36547b