Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/agent-setup-skills-release-tag.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"slack": patch
---

Install the skills from this release's tag in the agent setup prompt's `npx skills` command, instead of from `main`. An agent following the prompt now gets the skills that shipped with it, and each release moves the tag.
3 changes: 2 additions & 1 deletion .github/maintainers_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ The repo also ships the `slack` Claude Code marketplace at
`.claude-plugin/marketplace.json`. Its entry installs the plugin from the latest
release tag, not from your checkout, so use `--plugin-dir` to test local changes.
The release PR moves the entry's `ref` to the new tag, through
`scripts/sync_versions.py`.
`scripts/sync_versions.py`. The same script points the agent setup prompt's
`npx skills add` command at the new tag.

### Testing in Cursor

Expand Down
2 changes: 1 addition & 1 deletion public/agent-setup/prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ The plugin loads in the user's next Codex session. Do not ask the user to restar
Install the Slack skills with [`npx skills`](https://github.com/vercel-labs/skills#supported-agents), passing your own agent identifier to `-a`:

```sh
npx -y skills@latest add slackapi/slack-skills-plugin -g -y -a <agent>
npx -y skills@latest add https://github.com/slackapi/slack-skills-plugin/tree/v1.7.0 -g -y -a <agent>
```

The `-g` flag installs the skills for the user rather than the current folder, so they are available in the new app's folder and every later session. If the skills are already installed, the same command replaces them with the latest version. For example, `-a cursor` for Cursor, `-a gemini-cli` for Gemini CLI, or `-a opencode` for OpenCode. See the [supported agents list](https://github.com/vercel-labs/skills#supported-agents) for every identifier.
Expand Down
14 changes: 14 additions & 0 deletions scripts/sync_versions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
import logging
import re
from pathlib import Path

logger = logging.getLogger(Path(__file__).stem)
Expand All @@ -13,6 +14,8 @@
CURSOR_PLUGIN_PATH = REPO_ROOT / ".cursor-plugin" / "plugin.json"
CODEX_PLUGIN_PATH = REPO_ROOT / ".codex-plugin" / "plugin.json"
CLAUDE_MARKETPLACE_PATH = REPO_ROOT / ".claude-plugin" / "marketplace.json"
AGENT_SETUP_PROMPT_PATH = REPO_ROOT / "public" / "agent-setup" / "prompt.md"
SKILLS_SOURCE_PATTERN = re.compile(r"(https://github\.com/slackapi/slack-skills-plugin/tree/)v[^\s)]+")


def read_version(package_path: Path) -> str:
Expand Down Expand Up @@ -40,13 +43,24 @@ def write_marketplace_ref(marketplace_path: Path, version: str) -> None:
logger.info(f"Set {marketplace_path} ref to {tag}")


def write_agent_setup_ref(prompt_path: Path, version: str) -> None:
"""Point the agent setup prompt's ``npx skills add`` source at the release tag for ``version``."""
tag = f"v{version}"
prompt, count = SKILLS_SOURCE_PATTERN.subn(rf"\g<1>{tag}", prompt_path.read_text())
if count == 0:
raise ValueError(f"{prompt_path} has no slack-skills-plugin tree URL to point at {tag}")
prompt_path.write_text(prompt)
logger.info(f"Set {prompt_path} skills source to {tag}")


def main() -> None:
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
version = read_version(PACKAGE_JSON_PATH)
write_version(CLAUDE_PLUGIN_PATH, version)
write_version(CURSOR_PLUGIN_PATH, version)
write_version(CODEX_PLUGIN_PATH, version)
write_marketplace_ref(CLAUDE_MARKETPLACE_PATH, version)
write_agent_setup_ref(AGENT_SETUP_PROMPT_PATH, version)


if __name__ == "__main__":
Expand Down
19 changes: 17 additions & 2 deletions tests/unit/test_agent_setup_prompt.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
import json
import re

from tests.config import AGENT_SETUP_PROMPT, README, SKILLS_ROOT

# `npx skills` installs the skills from the release tag that matches package.json, so an
# agent gets the skills that shipped with this prompt, not whatever is on main. The
# release PR moves the tag through scripts/sync_versions.py.
PACKAGE_JSON = AGENT_SETUP_PROMPT.parents[2] / "package.json"
RELEASE_TAG = f"v{json.loads(PACKAGE_JSON.read_text())['version']}"
SKILLS_SOURCE = f"https://github.com/slackapi/slack-skills-plugin/tree/{RELEASE_TAG}"

# The prompt runs commands from the agent's shell, while the README shows some as
# commands a person types. Each pair maps the prompt's form to the README's. The
# prompt also installs `npx skills` globally (`-g`), which the README documents as
# an option rather than in the command itself.
# prompt also installs `npx skills` globally (`-g`) from a release tag, which the
# README documents as an option and a repo name rather than in the command itself.
SHELL_TO_README = (
("claude plugin install ", "/plugin install "),
("claude plugin marketplace add ", "/plugin marketplace add "),
("npx -y skills@latest ", "npx skills "),
(SKILLS_SOURCE, "slackapi/slack-skills-plugin"),
(" -g ", " "),
)

Expand Down Expand Up @@ -51,6 +60,12 @@ def test_install_commands_match_readme(self) -> None:
"update both install sections together"
)

def test_npx_skills_installs_this_release(self) -> None:
assert f"npx -y skills@latest add {SKILLS_SOURCE} " in self.prompt, (
f"{AGENT_SETUP_PROMPT.name} does not install the skills from {SKILLS_SOURCE}; "
"run scripts/sync_versions.py to point it at the package.json version"
)

def test_prompt_compiles_as_mdx(self) -> None:
# docs.slack.dev builds with Docusaurus, which compiles this file as MDX.
# MDX reads `<https://...>` autolinks as JSX tags and fails the build.
Expand Down
Loading