feat(agent): add setup-skills command for AI assistant skills - #1881
Conversation
Add `speakeasy agent setup-skills` with an interactive multi-select UI for installing Speakeasy companion skills for AI coding assistants. Hook into install.sh as a post-install step for interactive terminals. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use --skill flags with single repo arg instead of per-skill repo paths. Add --agent '*' -y to skip agent selection prompt. Remove customize-sdk-runtime which doesn't exist in the skills repo. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Delegate skill selection to the skills CLI instead of maintaining a hardcoded catalog. Add confirmation prompt warning users that npx (Node.js) will be used before running any commands. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| # Offer to install agent skills if running in an interactive terminal | ||
| if [ -t 0 ] && [ -t 1 ]; then | ||
| echo "" | ||
| "$INSTALL_DIR/$BINARY_NAME" agent setup-skills 2>/dev/null || true |
There was a problem hiding this comment.
🔴 install.sh 2>/dev/null suppresses bubbletea TUI rendering, causing invisible prompt and potential hang
When install.sh invokes the setup-skills command, it redirects stderr to /dev/null. However, bubbletea (v1.3.5, used by huh via charm.NewForm) renders its TUI to os.Stderr by default. This means the interactive confirm dialog at cmd/agent_setup_skills.go:107-111 is rendered to /dev/null and is invisible to the user.
Root Cause and Impact
The flow is:
install.sh:284checks[ -t 0 ] && [ -t 1 ]— stdin and stdout are terminals ✓install.sh:286runs"$INSTALL_DIR/$BINARY_NAME" agent setup-skills 2>/dev/null- Inside the binary,
IsInteractive()atinternal/model/command.go:181checksos.Stdout— it's still a terminal → returnstrue runSetupSkillsInteractiveis called, which showsfmt.Printlnmessages on stdout (visible) then launches a bubbletea TUI confirm dialog- Bubbletea renders the confirm dialog to stderr (its default output), which is
/dev/null→ invisible to the user - The program blocks reading stdin for the user's answer to a prompt they cannot see
The user sees:
Speakeasy offers companion skills for AI coding assistants ...
This will use npx (Node.js) to install skills from github.com/speakeasy-api/skills
Then the terminal appears to hang with no visible prompt. The user must blindly press keys or Ctrl+C to continue.
Impact: The install script hangs after the ASCII art banner, breaking the installation experience for all interactive users.
| "$INSTALL_DIR/$BINARY_NAME" agent setup-skills 2>/dev/null || true | |
| "$INSTALL_DIR/$BINARY_NAME" agent setup-skills || true |
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Good catch — bubbletea renders the TUI to stderr, so 2>/dev/null would hide the interactive prompt and cause an apparent hang. Removed it; the || true alone is sufficient to prevent install.sh from failing. Fixed in 4bd15af.
| func runNpxSkills(ctx context.Context, args ...string) error { | ||
| fullArgs := append([]string{"--yes", "skills"}, args...) | ||
|
|
||
| log.From(ctx).Infof("Running: npx %s", fmt.Sprintf("skills %s", joinArgs(args))) |
There was a problem hiding this comment.
🟡 Log message omits --yes flag, misrepresenting the actual command executed
The runNpxSkills function logs a command that differs from the one it actually executes.
Detailed Explanation
At cmd/agent_setup_skills.go:47, fullArgs is constructed as ["--yes", "skills", ...args], so the actual command run is npx --yes skills <args>. However, at line 49, the log message is:
log.From(ctx).Infof("Running: npx %s", fmt.Sprintf("skills %s", joinArgs(args)))This logs Running: npx skills <args>, omitting the --yes flag. The --yes flag is significant because it auto-confirms installing the npm package without user consent. If a user tries to reproduce the logged command, they'll get a different behavior (npx will prompt for confirmation).
Impact: Misleading debug output; users attempting to reproduce or troubleshoot see a different command than what was actually run.
| log.From(ctx).Infof("Running: npx %s", fmt.Sprintf("skills %s", joinArgs(args))) | |
| log.From(ctx).Infof("Running: npx --yes skills %s", joinArgs(args)) |
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Fixed — log message now includes --yes to match the actual command being executed. Fixed in 4bd15af.
Remove 2>/dev/null from install.sh that would hide the bubbletea TUI. Fix log message to include the --yes flag matching the actual command. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Wonder if instead of depending on Pros:
Cons:
What do you think? |
|
Good call — agree on all the pros. The install logic is pretty simple under the hood:
We already have the skill content embedded via the |
…tion Drop the npx/Node.js requirement entirely. Skills are now fetched directly from the GitHub API and installed natively: - Fetches skill catalog from GitHub Contents API - Downloads SKILL.md via raw.githubusercontent.com - Writes to .agents/skills/<name>/SKILL.md (canonical location) - Creates symlinks from all known agent directories Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@danielkov Rewrote this to drop the npx dependency entirely — skills are now installed natively in Go:
No Node.js, no npx, no npm packages. Works even if the user has never installed Node. The interactive flow fetches the catalog live so there's no hardcoded skill list to maintain either. |
os.Remove fails silently on non-empty directories, which would cause the subsequent os.Symlink to fail. Use os.RemoveAll instead. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
||
| // agentDirs lists well-known agent skill directory paths (relative to project root). | ||
| // .agents/skills is the canonical location; the rest are symlinked to it. | ||
| var agentDirs = []string{ |
There was a problem hiding this comment.
Might be nice to just re-use this form https://github.com/vercel-labs/skills/blob/82dd76bda58bedde5ddbf8f86fe63a538db20c19/src/agents.ts#L13
Such that we can offer to install on specific agents (and by default, pick just a few like claude, windsurf, cursor, mistral etc).
Otherwise there's a lot of noise in the git repo but usually teams all use the same 2-3 agents.
There was a problem hiding this comment.
Good idea — added an agent selection step to the interactive flow. The flow is now:
- Confirm prompt ("Would you like to install agent skills?")
- Agent multi-select — Claude Code, Cursor, Windsurf, and Copilot preselected; all others available but unchecked
- Skill multi-select — speakeasy-context preselected
--auto mode installs for the same 4 default agents. Also updated agent directory paths to match the upstream registry (e.g. .codeium/windsurf/skills, .pi/agent/skills). Pushed in 8ec82ba.
| if err := os.MkdirAll(agentSkillsDir, 0o755); err != nil { | ||
| return fmt.Errorf("failed to create directory %s: %w", agentSkillsDir, err) | ||
| } |
There was a problem hiding this comment.
Is this what skills does right now? I'd probably go with existence check vs creating all of these directories by default.
Address PR feedback from Thomas: let users pick which agents to install for instead of creating dirs for all 31 agents. Claude Code, Cursor, Windsurf, and Copilot are preselected by default. Also fixes agent directory paths to match upstream skills registry. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
They all share the canonical .agents/skills directory, so show them as one entry instead of three separate options. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
) ## Summary - After `speakeasy quickstart` configures the project but **before** running SDK generation, prompts the user to install the `speakeasy-context` agent skill for AI coding assistants - Reuses the existing `speakeasy agent setup-skills --auto` command (`runSetupSkills`) — no duplicated logic - Skips the prompt if skills are already installed or running in non-interactive mode Resolves GEN-2504 Supersedes #1887 (rebased onto main to remove already-merged #1881 commits from the diff) ## How to verify locally ### 1. Build and run quickstart (interactive, accept skill install) ```bash go build -o speakeasy . && ./speakeasy quickstart ``` After project setup (schema, target, output dir) but **before generation starts**, you should see: ``` Install Speakeasy agent skills for AI coding assistants (Claude Code, Cursor, etc.)? (Y/n) ``` Press Enter (default yes) → verify `.agents/skills/speakeasy-context/SKILL.md` and agent-specific symlinks are created in the output directory, then generation proceeds normally. ### 2. Decline skill install Run quickstart again in a fresh directory, select "no" at the prompt → verify no `.agents/` directory is created and generation proceeds normally. ### 3. Already-installed (idempotent) Run quickstart in a directory that already has `.agents/skills/speakeasy-context/` → the prompt should be **skipped entirely**. ### 4. Non-interactive mode ```bash go build -o speakeasy . && ./speakeasy quickstart --skip-compile --target typescript --schema https://petstore3.swagger.io/api/v3/openapi.json --out-dir /tmp/test-sdk --name TestSDK --package-name test-sdk ``` No skill install prompt should appear. ## Test plan - [x] Interactive quickstart → accept → skill files and symlinks created before generation runs - [x] Interactive quickstart → decline → no files created, generation proceeds - [x] Re-run in same dir → prompt skipped (already installed) - [x] Non-interactive mode → no prompt appears - [x] `go build ./...` passes 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/speakeasy-api/speakeasy/pull/1888" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Summary
speakeasy agent setup-skillscommand for installing Speakeasy companion skills for AI coding assistants (Claude Code, Cursor, etc.).agents/skills/<name>/SKILL.md(canonical), symlinked from all known agent directories--autoflag for non-interactive installation of default skills (speakeasy-context)install.shas a post-install step, only prompting when running in an interactive terminalTest plan
speakeasy agent setup-skillsin a terminal — verify confirm prompt, then multi-select with speakeasy-context preselectedspeakeasy agent setup-skills --auto— verify it installs speakeasy-context without prompts.agents/skills/speakeasy-context/SKILL.mdexists and agent dirs (.claude/skills/,.cursor/skills/, etc.) have symlinksecho | speakeasy agent setup-skills— verify no prompt, graceful skipbash install.shin an interactive terminal — verify the setup-skills prompt appears after ASCII artcat install.sh | sh— verify setup-skills is NOT prompted (non-interactive stdin)🤖 Generated with Claude Code