Skip to content

[DX-1115] 1 command installer#145

Open
umair-ably wants to merge 7 commits intomainfrom
1-command-onboarding
Open

[DX-1115] 1 command installer#145
umair-ably wants to merge 7 commits intomainfrom
1-command-onboarding

Conversation

@umair-ably
Copy link
Copy Markdown
Collaborator

@umair-ably umair-ably commented Mar 3, 2026

Summary

Adds one-command onboarding so developers can go from zero to AI-assisted Ably development in a single step.

  • ably init — runs ably login (if needed) and installs Ably Agent Skills into detected AI coding tools (Claude Code, Cursor, etc.). Auto-detects installed editors, prompts to confirm, and prints next-steps when done.
  • ably skills install — install Ably Agent Skills into one or more editors. Supports --target for explicit selection, plugin-based install for Claude Code, and --json for scripting. (No uninstall subcommand — users manage their IDE skill directories directly.)
  • New supporting services: skills-downloader (fetches the published skills bundle), skills-installer (writes per-editor skill files), claude-plugin-installer (Claude Code plugin path), and tool-detector (auto-detects supported editors).

Intent

Today, getting Ably-aware AI assistance requires manually finding, downloading, and wiring skills into each editor. This PR collapses that into ably init so the CLI itself bootstraps the AI workflow — making Ably's first-run experience match the "AI-native" story we want to tell.

Decisions Made

Not written by Claude
  • Command can be used prior to CLI being installed so installing the CLI and skills happens with just 1 terminal command
  • Given we provide a way to install skills, we also provide a way to uninstall skills I've deferred this until later if needed, as uninstall would need some thinking around string matching skill names if we want to capture Ably skills not installed via the terminal - this in turn means we then need to update this CLI repo should we change or expand on our current skills. An alternative is to store a manifest, but we're delving into solving a problem that doesn't need to be addressed yet. It's safe to assume our target engineers know how to uninstall a skill should they wish to. If demand requires it, we can add this feature in isolation later.
  • Users can choose which editors/IDEs to install skills to (auto-detects which IDEs they have installed)
  • If a user chooses Claude, we prefer to use Claude Plugins (as these are autoupdated as the skills are updated)
  • We tell the user which skills were installed instead of just outlining the number of skills installed
  • A user can still run init if they are already logged in - the flow should account for this
  • JSON mode with applicable flags e.g. --target cursor allows agents to run this command non-interactively
  • Skills are installed globally and not on a project level (given our CLI is likely to be installed globally)
  • There needed to be some kind of welcome message after the skills had been installed. I've kept this simple for now e.g. this is how you send your first message, this is the help command, these are the docs. I imagine this to be revamped when further docs/product onboarding improvements are fleshed out.
  • JSON output needs to match conventions (single result envelope, terminal completed status with exitCode)
  • Skills are intentionally overwritten if command is re-ran at any later date (this allows skills to be updated if needed)

Test plan

For reviewer: test from both a logged in and logged out state using pnpm ably init or pnpm dev init, and then with a variety of flags e.g. --json, --target cursor etc

  • Unit tests for init, skills install, and the new services
  • E2E test exercises the real download/extract/install path against an isolated HOME
  • Manual: ably init on a fresh machine with Claude Code installed
  • Manual: ably init --target cursor on a fresh machine
  • Manual: ably init --json produces valid envelope output

🤖 Generated with Claude Code

@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 3, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cli-web-cli Ready Ready Preview, Comment May 6, 2026 10:51am

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 3, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 1-command-onboarding

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude-code-ably-assistant
Copy link
Copy Markdown

Walkthrough

This PR introduces a one-command onboarding flow (ably init) that authenticates the user and installs Ably Agent Skills into detected AI coding tools in a single step. It adds a full ably skills command group (install/uninstall) backed by four new services that handle tool detection, skill downloading from the upstream GitHub repo, file-copy installation, and Claude Code's native plugin protocol.

Changes

Area Files Summary
Commands src/commands/init.ts New interactive onboarding command — detects tools, runs auth if needed, delegates to skills:install, and shows a getting-started guide
Commands src/commands/skills/index.ts Topic root command for the skills group
Commands src/commands/skills/install.ts Downloads latest skills from upstream and installs them into each target IDE; supports --target auto|<tool> (multiple)
Commands src/commands/skills/uninstall.ts Removes only ably--prefixed skill directories; runs claude plugin uninstall for Claude Code targets
Services src/services/tool-detector.ts Detects installed AI coding tools (Claude Code, Cursor, VS Code, Windsurf, Zed, Continue, Copilot) via CLI probe and config-dir existence
Services src/services/skills-downloader.ts Fetches and extracts skill bundles from ably/agent-skills GitHub tarballs; parses SKILL.md frontmatter
Services src/services/skills-installer.ts Copies extracted skill bundles into per-tool directories under $HOME; tracks install/update/skip status
Services src/services/claude-plugin-installer.ts Installs/uninstalls skills via claude plugin CLI (plugin protocol) instead of file copy
Config src/base-command.ts Adds init and skills* to web-CLI restricted list and interactive-unsuitable list
Config package.json, pnpm-lock.yaml Adds tar@^7.5.9 runtime dependency for tarball extraction
Scripts scripts/postinstall-welcome.ts Updates post-install message to highlight ably init as the entry point instead of ably --help / ably login
Tests test/unit/commands/init.test.ts Unit tests for onboarding flow (auth skip, JSON mode, target selection)
Tests test/unit/commands/skills/install.test.ts Unit tests for install command (auto-detect, explicit targets, JSON output)
Tests test/unit/commands/skills/uninstall.test.ts Unit tests for uninstall command (force flag, plugin uninstall path, empty-dir cleanup)
Tests test/unit/services/tool-detector.test.ts Mocks execFile and fs.existsSync to test CLI-binary and config-dir detection paths
Tests test/unit/services/skills-downloader.test.ts Tests tarball fetch, extraction, and frontmatter parsing
Tests test/unit/services/skills-installer.test.ts Integration-style tests using real temp dirs; covers install, overwrite, recursive copy, unknown-target handling
Tests test/unit/services/claude-plugin-installer.test.ts Mocks the claude CLI to test plugin install/uninstall/marketplace-remove flows

Review Notes

  • New runtime dependency: tar@^7.5.9 — used to extract GitHub release tarballs in skills-downloader.ts. Worth checking whether Node's built-in zlib + tar stream approach or a lighter alternative was considered, given the project's stated preference for avoiding unnecessary deps.
  • Network call at install time: skills:install fetches a tarball from GitHub (api.github.com) on every run; there is no caching or offline fallback. Users behind firewalls or in restricted environments will see failures.
  • process.exit(130) in init.ts: The SIGINT handler inside promptForTargets calls process.exit(130) directly rather than this.exit(). This is intentional (comment explains the inquirer signal-exit race), but it bypasses oclif's test-mode exit handling and could cause issues in unit tests that simulate SIGINT.
  • skills* glob in restricted list: WEB_CLI_RESTRICTED_COMMANDS uses "skills*" as a string — verify that the base-command wildcard matching actually handles this glob rather than doing a literal string comparison.
  • No integration or E2E tests: The new commands make real network calls and filesystem writes; all tests are unit-level with mocks. The skills-installer tests use real temp dirs, which is good, but there is no end-to-end smoke test covering the full download → install path.
  • Claude plugin uninstall flow: uninstall.ts calls claude plugin marketplace remove in addition to claude plugin uninstall — reviewers should confirm both commands are necessary and that the ordering is correct for all Claude Code versions.

@claude-code-ably-assistant
Copy link
Copy Markdown

Walkthrough

This PR introduces a one-command onboarding flow (ably init) that authenticates the user and installs Ably Agent Skills into detected AI coding tools in a single step. It adds a full ably skills command group (install/uninstall) backed by four new services that handle tool detection, skill downloading from the upstream GitHub repo, file-copy installation, and Claude Code's native plugin protocol.

Changes

Area Files Summary
Commands src/commands/init.ts New interactive onboarding command — detects tools, runs auth if needed, delegates to skills:install, shows a getting-started guide
Commands src/commands/skills/index.ts Topic root for the skills group
Commands src/commands/skills/install.ts Downloads latest skills from upstream and installs into each target IDE; supports --target auto|<tool> (multiple)
Commands src/commands/skills/uninstall.ts Removes only ably--prefixed skill dirs; runs claude plugin uninstall for Claude Code targets
Services src/services/tool-detector.ts Detects AI coding tools (Claude Code, Cursor, VS Code, Windsurf, Zed, Continue, Copilot) via CLI probe and config-dir existence
Services src/services/skills-downloader.ts Fetches and extracts skill bundles from ably/agent-skills GitHub tarballs; parses SKILL.md frontmatter
Services src/services/skills-installer.ts Copies extracted skill bundles into per-tool directories under $HOME; tracks install/update/skip status
Services src/services/claude-plugin-installer.ts Installs/uninstalls skills via claude plugin CLI (plugin protocol) instead of file copy
Config src/base-command.ts Adds init and skills* to web-CLI restricted list and interactive-unsuitable list
Config package.json, pnpm-lock.yaml Adds tar@^7.5.9 runtime dependency for tarball extraction
Scripts scripts/postinstall-welcome.ts Updates post-install message to point at ably init as entry point
Tests test/unit/commands/init.test.ts Unit tests for onboarding flow (auth skip, JSON mode, target selection)
Tests test/unit/commands/skills/install.test.ts Unit tests for install command (auto-detect, explicit targets, JSON output)
Tests test/unit/commands/skills/uninstall.test.ts Unit tests for uninstall (force flag, plugin path, empty-dir cleanup)
Tests test/unit/services/tool-detector.test.ts Mocks execFile and fs.existsSync to test CLI-binary and config-dir detection
Tests test/unit/services/skills-downloader.test.ts Tests tarball fetch, extraction, and frontmatter parsing
Tests test/unit/services/skills-installer.test.ts Integration-style tests with real temp dirs covering install, overwrite, recursive copy
Tests test/unit/services/claude-plugin-installer.test.ts Mocks the claude CLI to test plugin install/uninstall/marketplace-remove flows

Review Notes

  • New runtime dependencytar@^7.5.9 is added to extract GitHub release tarballs in skills-downloader.ts. Worth confirming this meets the project's preference for avoiding unnecessary deps vs. using Node's built-in streams.
  • Network call at runtimeskills:install fetches a tarball from api.github.com on every run with no caching or offline fallback. Users behind firewalls or in restricted environments will get failures.
  • process.exit(130) in init.ts — The SIGINT handler inside promptForTargets calls process.exit(130) directly rather than this.exit(). The comment explains the inquirer race condition, but this bypasses oclif's test-mode exit handling and could affect unit tests that simulate SIGINT.
  • skills* glob in restricted listWEB_CLI_RESTRICTED_COMMANDS contains "skills*" as a plain string; verify the base-command wildcard matching supports this glob rather than doing a literal comparison.
  • No integration/E2E tests — All tests are unit-level with mocks. There is no end-to-end smoke test covering the full download → extract → install path against real GitHub or a local fixture server.
  • Claude plugin uninstall orderinguninstall.ts calls both claude plugin uninstall and claude plugin marketplace remove; reviewers should confirm both are necessary and that the ordering is correct across Claude Code versions.

Copy link
Copy Markdown

@claude-code-ably-assistant claude-code-ably-assistant Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: 1-command onboarding (ably init + ably skills install/uninstall)

This PR adds a nice onboarding flow. The service layer is well-structured (clean separation of concerns, proper cleanup in finally, good error-type enums). The command logic, flag architecture, and test setup are solid. Four things need attention:


1. Bug — isAlreadyAuthenticated() ignores ABLY_API_KEY and ABLY_TOKEN

src/commands/init.ts lines 177–180:

private isAlreadyAuthenticated(): boolean {
  if (process.env.ABLY_ACCESS_TOKEN) return true;
  return Boolean(this.configManager.getAccessToken());
}

This only checks ABLY_ACCESS_TOKEN and stored access token. It misses ABLY_API_KEY and ABLY_TOKEN — the most common auth methods in scripts and CI. A user with ABLY_API_KEY in their shell always sees the login prompt during ably init, even though they are already authenticated. The base command handles all three env vars correctly (base-command.ts line 459).

Fix:

private isAlreadyAuthenticated(): boolean {
  if (process.env.ABLY_ACCESS_TOKEN) return true;
  if (process.env.ABLY_API_KEY) return true;
  if (process.env.ABLY_TOKEN) return true;
  return Boolean(this.configManager.getAccessToken());
}

2. Bug — Imports after a const declaration in uninstall.ts

src/commands/skills/uninstall.ts lines 15–22:

import { BaseFlags } from "../../types/cli.js";

// Ably skills follow the `ably-` naming convention...
const ABLY_SKILL_PREFIX = "ably-";
import { formatHeading, formatResource } from "../../utils/output.js";
import { promptForConfirmation } from "../../utils/prompt-confirmation.js";

Two import statements appear after a const. Static imports are hoisted so there is no runtime crash, but this violates the import/first lint rule and is clearly accidental ordering. Move ABLY_SKILL_PREFIX to after all imports.


3. Architectural — JSON result does not follow domain key nesting convention

src/commands/skills/install.ts lines 172–183; src/commands/skills/uninstall.ts lines 86–91 and 136.

Project convention (CLAUDE.md): "All domain data must be nested under a domain key — never spread raw data fields at the top level alongside envelope fields." The envelope owns type, command, success. Everything else must sit under one named key.

Current install output spreads skills, installed, pluginInstalled as direct siblings of the envelope fields. Current uninstall output does the same with removed and plugin. This makes it impossible for agents and scripts to reliably distinguish envelope metadata from domain data.

Fix: wrap under a single domain key, e.g. { installation: { skills, installed: allResults, pluginInstalled } }. Update the early-return JSON paths and the test assertions (record.installed etc.) to match.


4. Missing coverage — uninstallClaudePlugin has no unit tests

test/unit/services/claude-plugin-installer.test.ts imports and exercises only installClaudePlugin. The uninstall function has distinct logic: different status states (not-installed, uninstalled, partial, error), a separate "not-installed" message detector, a per-plugin uninstall loop, and marketplace removal with its own error path. None of this is covered.


Overall: Architecture is sound. Issues 1 and 2 are functional bugs; 3 is a JSON contract consistency problem that affects agent and script consumers; 4 is a coverage gap for new production code.

@umair-ably umair-ably force-pushed the 1-command-onboarding branch 2 times, most recently from 99dc194 to 97b1d13 Compare May 6, 2026 10:31
@umair-ably
Copy link
Copy Markdown
Collaborator Author

@claude review

Copy link
Copy Markdown

@claude-code-ably-assistant claude-code-ably-assistant Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overview: This PR adds ably init and ably skills install commands. Architecture is solid, test coverage is good. Issues to fix before merge: (1) Double completed signal in JSON mode - init delegates to skills:install via runCommand which emits its own completed, then init emits a second one. Fix by calling service methods directly. (2) this.log(formatSuccess(...)) in skills/install.ts goes to stdout - should use logToStderr or logSuccessMessage instead. (3) process.exit(130) in init.ts - use this.exit(130) per CLAUDE.md. (4) Dead updated status check in e2e test - SkillResult.status is typed as installed|error, no updated variant exists. (5) Trailing newline in logSuccessMessage call - drop the backslash-n. Non-blocking notes: skills* wildcard works correctly, unversioned main branch download is intentional but worth documenting, TEST_MOCKS injection is consistent with existing pattern. Issue 1 is most impactful.

@umair-ably
Copy link
Copy Markdown
Collaborator Author

Addressed all claude review comments by amending appropriate commits

@umair-ably umair-ably requested review from AndyTWF May 6, 2026 10:55
@umair-ably umair-ably changed the title 1 command onboarding [DX-1115] 1 command installer May 6, 2026
name: "Windsurf",
relativeDir: path.join(".windsurf", "skills"),
},
zed: {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK Zed doesn't have any folder like this, whilst ~/.config/zed is where config is stored, it doesn't detect skills here

async run(): Promise<void> {
const { flags } = await this.parse(SkillsInstall);
try {
await runSkillsInstall(flags, this.buildInstallOutput(flags));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the init command this lets you select which skills you want to install, should we have the same pattern here?

Comment thread src/commands/init.ts
}),
};

async run(): Promise<void> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When running this you get a double Ably logo, probably should do just one?


static override examples = [
"<%= config.bin %> <%= command.id %>",
"<%= config.bin %> <%= command.id %> --target claude-code",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Examples for other options?

if (hasClaudePlugin) {
const outcome = await installClaudeCodePlugin(output);
if (
outcome === "installed" ||
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] Use an enum here

Comment thread src/base-command.ts
"config", // Config editing is not suitable for interactive mode
"version", // Version is shown at startup and available via --version
"init", // One-time setup; not meaningful inside an already-running session
"skills:install", // Filesystem install; not meaningful inside an interactive session
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be? Could I not run ably-interactive, and then instal my skills?


// Platform-specific app paths
const appPaths =
process.platform === "darwin"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] could enum

private tempDir: string | null = null;

async download(): Promise<DownloadedSkill[]> {
const tarballUrl = `https://github.com/${SKILLS_REPO}/archive/refs/heads/main.tar.gz`;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a supply-chain attack vector - if someone were to manage to commit something to the repo maliciously, it would affect anybody who installed the CLI. Worth pinning to a release / doing some signature verification? At the very least, print the commit SHA so users can audit

private walkForSkills(dir: string, skills: DownloadedSkill[]): void {
const entries = fs.readdirSync(dir, { withFileTypes: true });

for (const entry of entries) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unbounded, if the skill repo ever grows (e.g. node_modules gets added) it'll iterate absolutely everything. If a vendored dir ever contained a skill, we'd ship that. Any way we can narrow it down so that it's just known dirs that get iterated?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants