feat: ship as a Claude Code plugin (and Codex marketplace)#13
Merged
Conversation
Adds the .claude-plugin/ directory with two files: - plugin.json: plugin manifest with name, version, description, author, repo, license, keywords. Defines the 'travel-hacker' plugin. - marketplace.json: marketplace catalog with one entry pointing at the repo root as the plugin source. Marketplace name 'borski-travel'. Codex also reads .claude-plugin/marketplace.json (per Anthropic docs), so the same file serves both ecosystems. Validates clean via 'claude plugin validate .'. Users install via: /plugin marketplace add borski/travel-hacking-toolkit /plugin install travel-hacker@borski-travel
The Claude plugin format expects an agents/ directory with .md files
that define subagents. CLAUDE.md is the existing source of truth for
the toolkit's system prompt (and for OpenCode/Codex via the AGENTS.md
symlink), so we add YAML frontmatter (name/description/model: opus) to
CLAUDE.md and copy it to agents/travel-hacker.md.
Symlinks and ${file} include syntax aren't honored in plugin agent
files, so it has to be a real file. To prevent drift between the two:
1. scripts/sync-agent.sh copies CLAUDE.md -> agents/travel-hacker.md.
Validates that CLAUDE.md starts with --- frontmatter.
2. scripts/hooks/pre-commit auto-syncs when CLAUDE.md is staged. The
hook is tagged with a sentinel comment so install-hooks.sh can tell
ours apart from a contributor's existing pre-commit hook.
3. scripts/install-hooks.sh copies hooks from scripts/hooks/ to
.git/hooks/. Refuses to overwrite a foreign (untagged) hook with a
clear skip message. Setup.sh runs it automatically on clone.
4. .github/workflows/smoke-test.yml runs scripts/smoke-test.sh --quick
on every PR. The smoke test fails if agents/travel-hacker.md drifts
from CLAUDE.md. This is the real safety net.
Layered defense: contributors who skip setup don't get the hook, but CI
catches them anyway.
Two user-invoked orchestration skills (disable-model-invocation: true) that surface as slash commands inside Claude Code: /travel-hacker:plan-trip — guided trip planner. The hero command. Asks for destination, dates, who, class, flexibility, points-or-cash. Then runs the full hero workflow: lessons-learned + flight-search-strategy + parallel cash search + parallel award search across all programs + cross-reference with transfer-partners + cpp math + sweet-spots. Output is a ranked markdown table with the winning option bolded and booking next steps via booking-guidance. /travel-hacker:getting-started — first-run setup detector and signpost. Detects which API keys are configured. Tells the user what they CAN do right now. Refuses to collect API keys via chat (keys typed into chat get retained in three logging surfaces: terminal scrollback, Claude Code session log, Anthropic API logs). Instead points users at scripts/setup-keys.sh (or .ps1 for PowerShell) for safe local setup. Shows sample prompts scaled to what they configured. Both skills end with declarative 'Next steps:' statements rather than action-offer questions, per the system prompt's PRE-OUTPUT GATE.
Anthropic's plugin userConfig is broken upstream for bash-based skills (anthropics/claude-code#11927, #39125), so plugin users have to set API keys as shell environment variables. We ship two scripts that do this safely: - scripts/setup-keys.sh (bash, handles zsh/bash/fish on macOS/Linux/WSL) - scripts/setup-keys.ps1 (PowerShell, with cmd setx guidance for cmd users) Design goals: 1. Keys never go through chat. The script runs in the user's terminal, prompts with masked input (read -s in bash, AsSecureString in PowerShell). Values never echo to stdout. 2. Shell-safe quoting. Exports are single-quoted (export KEY='value' or set -gx KEY 'value' or $env:KEY = 'value') so other metacharacters in the API key are inert. Single quotes themselves are explicitly rejected with a clear message. 3. Per-key minimum length validation. Default 10 chars. AwardWallet user IDs (numeric, 5-7 digits) and ENTUR_CLIENT_NAME (user-chosen short identifier) get a 3-char minimum. Prevents typos but doesn't reject legitimate short values. 4. Idempotent. Skips keys already exported in the rc file (per-shell regex). Never duplicates entries on repeated runs. 5. Backups before write. If the rc file already has content, copies it to <rcfile>.bak.YYYYMMDD-HHMMSS first. 6. Auto-creates missing rc files (zsh on a fresh macOS install, PowerShell $PROFILE on a fresh Windows box). 7. Persist-transient-key flow. If a key is set in the current shell but missing from the rc file, the script offers to persist it. Cautious default = no. Same validation applies (single-quote reject + min_len). 8. Tier-based prompting. Tier 1 high-value keys first (Seats.aero, Duffel, Ignav, AwardWallet). Tier 2 (extra sources) and Tier 3 (Scandinavia transit) prompt only if the user opts in. The /travel-hacker:getting-started skill points at these scripts and explicitly refuses to collect keys via chat.
Adds five new static checks to scripts/smoke-test.sh: 1. setup-keys.sh syntax (bash -n). Catches the curl|bash one-liner breaking silently if a future edit introduces a syntax error. 2. setup-keys.ps1 structure (brace-counting). Same idea for the PowerShell version since we don't have pwsh in the local CI image. 3. Claude plugin manifest + marketplace validation via 'claude plugin validate .'. Skips cleanly if the claude CLI isn't installed. 4. agents/travel-hacker.md is in sync with CLAUDE.md (diff -q) AND has the required frontmatter fields (name, description, model). Guards against contributors editing CLAUDE.md without running sync-agent.sh, and against frontmatter regressions. 5. Plugin component discovery: skills/ non-empty + .mcp.json valid JSON. Catches the case where a directory was deleted or a config corrupted. Also refactors the agent invocation test for the Claude path. Was calling claude --strict-mcp-config --mcp-config .mcp.json -p; now uses claude --plugin-dir 'REPO_DIR' -p. The plugin loads skills + MCPs + agent system prompt atomically so the old MCP-only flag is obsolete. The test_agent function is refactored to take an explicit binary parameter so 'claude' and 'claude-plugin' aren't conflated. Docker image manifest check now distinguishes network/auth errors from genuinely missing images. If GHCR is unreachable from the runtime environment (DNS, network, credentials), the test skips with a clear message. Restricted CI environments no longer false-fail this check. Updated header comment to enumerate the full check set (12 static + 3 agent invocations x 2 each = 18 total when all CLIs are installed).
Five changes to setup.sh, setup.ps1, and .gitignore, plus a deletion: 1. Drop the .claude/settings.local.json copy step. Anthropic's plugin userConfig was supposed to handle Claude Code key management but is broken upstream (anthropics/claude-code#11927). The settings.local.json path was the workaround pre-PR; now the canonical path is shell env vars set via scripts/setup-keys.sh. The example file is removed too. 2. Tell Claude users where to actually configure keys: shell rc + the guided /travel-hacker:getting-started skill. Replaces the obsolete 'edit .claude/settings.local.json' instruction. 3. Show install-hooks.sh output instead of redirecting to /dev/null. When the script skips a foreign pre-commit hook, the contributor sees the message instead of silently losing sync enforcement. 4. Recommend 'claude --plugin-dir .' instead of the old '--strict-mcp-config --mcp-config .mcp.json' as the launch command from inside a clone. The plugin loads skills + MCPs + agent system prompt atomically, so the old MCP-only flag pair is obsolete. 5. .gitignore: also ignore .env.backup* and .env.bak so timestamped backups from local key rotations don't accidentally get staged.
…t tree README quick-start rewritten around the no-clone install paths: - Claude Code: /plugin marketplace add + /plugin install. Two commands. After install, run /travel-hacker:getting-started for guided key setup. - Codex: codex plugin marketplace add. One command. Same marketplace catalog, different plugin format inside the repo (.codex-plugin vs .claude-plugin). - Cowork (Claude Desktop's agent panel): no /plugin slash commands of its own, but inherits anything installed via Claude Code from ~/.claude/plugins/. Documents the indirection clearly. - Clone path preserved for OpenCode users (no plugin format) and for contributors. claude --plugin-dir . loads skills + MCPs + agent atomically. API key setup section now leads with scripts/setup-keys.sh and .ps1. Manual setup with shell rc edits is preserved as a sub-heading. Includes a 1Password 'op run --env-file=.env -- claude' option. Notes the upstream userConfig bugs (anthropics/claude-code#11927, #39125) so readers understand why the toolkit reads from shell env vars instead of plugin settings. First-run verification: /travel-hacker:getting-started or shell-side 'claude plugin list | grep travel-hacker'. Project structure tree updated: adds .claude-plugin/, agents/, .github/workflows/, scripts/setup-keys.{sh,ps1}, scripts/sync-agent.sh, scripts/install-hooks.sh, scripts/hooks/pre-commit, plus the two new orchestration skills (getting-started, plan-trip). Smoke test description ('What it verifies') now lists all 12 static checks instead of the obsolete 8. Pre-existing miscount fixed: 'Six Docker-based skills' is actually five skills plus a shared base image. Both occurrences in the README updated. Description tweaks: '6 MCP servers (5 free + LiteAPI which needs a key)' clarifies what 'free' meant in the next paragraph. README claim that the script 'rejects values with shell metacharacters' was an overstatement; reworded to match what the validation actually does (reject single quotes + min length check, plus single-quoted exports defuse other metacharacters). llms.txt regenerated via gen-skill-tables.sh to match.
borski
added a commit
that referenced
this pull request
May 1, 2026
…verified The previous wording overstated what the cited issues prove. #11927 is about settings.json env blocks, #39125 is cowork-specific. Neither directly proves plugin userConfig is broken for bash-tool skills. The actual basis for the workaround is empirical testing during PR #13 build, not those upstream issues. Drop the implicit promise to migrate once they're fixed since they may not be the right tracker for our case.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a Claude Code plugin packaging on top of the existing repo. Users can install via
/plugin marketplace add borski/travel-hacking-toolkitinstead of cloning. Cuts the launch command fromclaude --strict-mcp-config --mcp-config .mcp.jsonto justclaude.The Codex marketplace already shipped before this PR; this PR doesn't change it. Both marketplaces coexist in the repo. The new Claude plugin sits at
.claude-plugin/.What users will run after this merges
Claude Code (new):
Then run the local key setup script (or
/travel-hacker:getting-startedwhich points at it).Codex (already worked, unchanged):
Cowork (Claude Desktop's agent panel): install via Claude Code first; Cowork picks it up automatically because both share
~/.claude/plugins/.OpenCode users still clone and run
./scripts/setup.sh. Unchanged.API key setup: now via local script, not chat
Both reviewers (fresh Claude opus + fresh Codex) flagged that an earlier version of this PR walked the user through key entry inside the chat. That was unsafe: pasted keys would land in terminal scrollback, Claude Code session logs, and Anthropic API logs. Three logging surfaces.
The current setup ships two scripts:
scripts/setup-keys.sh(POSIX, handles zsh/bash/fish) andscripts/setup-keys.ps1(PowerShell + cmd guidance). They prompt with masked input, validate, write to the user's shell rc with a backup, and never echo values. The/travel-hacker:getting-startedskill points at them and explicitly refuses to collect keys via chat.Why we don't use plugin
userConfigThe plugin spec includes
userConfigfor prompting/storing API keys at install. It's broken upstream for bash-based skills (anthropics/claude-code#11927 for Claude Code, #39125 for Cowork). Verified empirically that values stored inpluginConfigs.optionsnever reach the agent's bash tool. When that's fixed upstream we can revisit.Commits (13)
feat(plugin): add Claude Code plugin manifest and marketplacefeat(plugin): add travel-hacker agent definitiontest(plugin): add plugin validation to smoke test, simplify launch hintsci: prevent agent file from drifting from CLAUDE.mddocs(plugin): document no-clone install for Claude Code and Codexfeat(plugin): UX polish - userConfig prompts, guided skills, sample prompts(later reverted)feat(agent): graceful degradation when API keys are missingdocs(plugin): correct how Codex resolves the installdocs(plugin): document Cowork install via Claude Code CLIfix(plugin): drop userConfig, document shell env vars insteadfeat(plugin): expand getting-started into full setup flow(later replaced by setup-keys script)fix(plugin): never echo API key values in getting-started flow(interim hardening)fix(plugin): address review feedback from Claude + Codex code review← the big consolidationTests
bash scripts/smoke-test.shpasses 16/16:--plugin-dir, opencode)CI (GitHub Actions) runs the static checks on every PR push.
Manually verified end-to-end:
claude plugin install travel-hacker@borski-travelworks from a local marketplacecodex plugin marketplace add borski/travel-hacking-toolkitcontinues to work (uses the pre-existing Codex marketplace)/travel-hacker:<name>travel-hackersubagent loads with its full system promptscripts/install-hooks.shrefuses to overwrite a foreign pre-commit hook (verified end-to-end)scripts/setup-keys.shruns interactively with masked input, writes to ~/.zshrc with a backup, never echoes valuesPlugin format separation
plugins/travel-hacking-toolkit/.codex-plugin/plugin.json.agents/plugins/marketplace.json.claude-plugin/plugin.json(at repo root).claude-plugin/marketplace.jsonAnthropic's docs say Codex CAN read Claude-style marketplaces, but in practice when both exist Codex picks its own format. Both end users hit the same one-line install command.
Skill count
40 → 42. New:
plan-trip,getting-started. README and llms.txt regenerated viagen-skill-tables.sh.